/* Handling of assorted minor windows for the Mac interface to Xconq.
   Copyright (C) 1992-1999 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"

#define unit_doctrine(unit)  \
  (unit->side->udoctrine[unit->type])

#define DEFAULT_RUN (99)

static void create_game_window(void);
static void draw_game_side(Side *side2);
static int feeling_towards(Side *side, Side *side2);

static void create_construction_window(void);
static void init_construction_lists(void);
static void reinit_construction_lists(void);
static void draw_construction_default(void);
static void calc_construction_rects(void);
static Unit *get_selected_construction_unit(void);
static int get_selected_construction_type(void);
static int get_selected_construction_advance(void);
static void select_advance_in_construction_window(int a);
static void maybe_add_unit_to_construction_list(Unit *unit);
static void update_unit_list_for_type(int u);
static void update_unit_list_for_advance(int a);
static void update_type_list_for_unit(Unit *unit);
static void update_construction_advance_list(void);
static void update_advance_list_for_unit(Unit *unit);
static void adjust_construction_controls(void);
static void adjust_construction_items(void);

static void hit_research_dialog(int ditem);

static void create_history_window(void);
static void calc_history_layout(void);
static void set_history_scrollbar(void);
static void draw_historical(HistEvent *hevt, int y, int drawevt);
static pascal void history_scroll_fn(ControlHandle control, short code);
static void move_history_scrollbar(int h, int v);

static void create_notice_window(void);
static void adjust_notice_scrollbar(void);
static pascal void notice_vscroll_fn(ControlHandle control, short code);

static void create_scores_window(void);
static void append_scores(char *str);
static void adjust_scores_scrollbar(void);
static pascal void scores_vscroll_fn(ControlHandle control, short code);

/* Globals for the research window. */

WindowPtr researchwin = nil;

/* Globals for the game window. */

WindowPtr gamewin = nil;

int gamewinw = 160;
int gamedatehgt = 18;
int gameclockoffset;
int gameclockhgt = 15;
int gamenoteoffset;
int gamenotehgt = 15;
int gametophgt;

int gamesidehgt = 24;
int gamesideclockhgt = 15;
int gamesideclockoffset;
int gamesidescorehgt = 15;
int gamesidescoreoffset;

int gamenumsides;

Handle aisicnhandle = nil;
Handle facesicnhandle[3];

char *game_progress_str = "";

time_t lastnow;

/* Globals for the construction window. */

WindowPtr constructionwin = nil;

static ListHandle construction_unit_list = nil;
static ListHandle construction_type_list = nil;
static ListHandle construction_advance_list = nil;

static int maxtypewid;
static int constructmargin = 5;
static int constructtop = 32;

/* This is the vector of units that can do construction or development. */

static UnitVector *constructor_units = NULL;
static UnitVector *research_units = NULL;

static int numposstypes;
static int *possibletypes = NULL;

static int numpossadvances;
static int *possibleadvances = NULL;

int currunlength;

int editedrunlength = -1;

/* (this is referenced from the end-turn command, so can't make static) */
ControlHandle constructbutton;
ControlHandle researchbutton;
static ControlHandle developbutton;

TEHandle run_length_text = nil;

static Rect runlengthrect;
static Rect unitlistrect;
static Rect typelistrect;
static Rect advancelistrect;

/* Globals for the history window. */

WindowPtr historywin = nil;

int maxvishistlines = 200;

HistEvent **histcontents = NULL;

int numhistcontents = 0;

ControlHandle histvscrollbar;

int history_line_spacing;

int numvishistlines;

int total_history_lines = 0;

ControlActionUPP history_scroll_proc;

/* Globals for the notice window. */

DialogPtr noticewin = nil;

static TEHandle notice_text = nil;

static ControlHandle notice_v_scrollbar;

ControlActionUPP notice_vscroll_proc;

static TextStyle *noticeStyle;

/* Globals for the scores window. */

DialogPtr scoreswin = nil;

static TEHandle scores_text = nil;

static ControlHandle scores_v_scrollbar;

ControlActionUPP scores_vscroll_proc;

/* The game progress window. */

/* This is the top-level access to bring up the game window, can be called
   anywhere, anytime. */

void
show_game_window()
{
	if (gamewin == nil) {
		create_game_window();
	} 
	SelectTheWindow(gamewin);
	ActivateWindow(gamewin, true);
}

/* Create the game progress window. */

void
create_game_window()
{
	int screenwidth;
	extern int numscores;

	/* Create the window, color if possible, since emblems may be in color. */
	if (hasColorQD) {	
		gamewin = GetNewCWindow(wFloatFixed, NULL, NULL);
	} else {
		gamewin = GetNewWindow(wFloatFixed, NULL, NULL);
	}
	SetWTitle(gamewin, "\pGame");
	gametophgt = gamedatehgt;
	if (g_rt_per_turn() > 0 || g_rt_for_game() > 0) {
		gameclockoffset = gametophgt;
		gametophgt += gameclockhgt;
	}
	gamenoteoffset = gametophgt;
	gametophgt += gamenotehgt;
	/* Add some space if sides have a per-turn and/or per-game clock. */
	if (g_rt_per_side() > 0) {
		gamesideclockoffset = gamesidehgt;
		gamesidehgt += gamesideclockhgt;
	}
	/* Add additional space for each two scorekeepers. */
	if (keeping_score()) {
		gamesidescoreoffset = gamesidehgt;
		gamesidehgt += gamesidescorehgt * ((numscorekeepers + 1) / 2);
	}
	/* This is not growable, so we have to ensure it's big enough to start with. */
	/* (Record the current numsides so we can grow the window later.) */
	if (side_has_ai(indepside) || side_has_display(indepside)) {
		gamenumsides = numtotsides;
	} else {
		gamenumsides = numsides;
	}
	SizeWindow(gamewin, gamewinw, gametophgt + gamenumsides * gamesidehgt, 1);
	if (first_windows) {
		get_main_screen_size(&screenwidth, NULL);
		/* Tweaked to fit Apple Platinum theme. */
		MoveWindow(gamewin, screenwidth - gamewinw - 3, 41 + 15, FALSE);
	}
	/* Get handles to useful sicns. */
	aisicnhandle = GetNamedResource('SICN', "\pmplayer");
	facesicnhandle[0] = GetNamedResource('SICN', "\phostile");
	facesicnhandle[1] = GetNamedResource('SICN', "\pneutral");
	facesicnhandle[2] = GetNamedResource('SICN', "\pfriendly");

	/* Finally make it floating. */
	MakeFloat(gamewin);
}

char *last_status;
char *last_status_left;
char *last_status_resv;
char *last_status_fini;
char **last_status_score1;

void
draw_game()
{
	Side *side2;
	GrafPtr oldport;
	int newnumsides = 0;
	
	if (gamewin == nil)
	  return;
	if (side_has_ai(indepside) || side_has_display(indepside)) {
		newnumsides = numtotsides;
	} else {
		newnumsides = numsides;
	}
	/* The window might need to get bigger. */
	if (newnumsides != gamenumsides) {
		last_status = NULL;
		last_status_left = NULL;
		last_status_resv = NULL;
		last_status_fini = NULL;
		last_status_score1 = NULL;
		gamenumsides = newnumsides;
		SizeWindow(gamewin, gamewinw, gametophgt + gamenumsides * gamesidehgt, 1);
	}
	if (last_status == NULL)
	  last_status = xmalloc(numsides + 1);
	if (last_status_left == NULL)
	  last_status_left = xmalloc(numsides + 1);
	if (last_status_resv == NULL)
	  last_status_resv = xmalloc(numsides + 1);
	if (last_status_fini == NULL)
	  last_status_fini = xmalloc(numsides + 1);
	if (last_status_score1 == NULL) {
		int i;
		last_status_score1 = (char **) xmalloc((numtotsides + 1) * sizeof(char *));
		for (i = 0; i <= numtotsides; ++i)
		  last_status_score1[i] = xmalloc(100);
	}
	GetPort(&oldport);
	SetPort(gamewin);
	TextFont(large_font_id);
	TextSize(large_font_size);
	if (endofgame)
	    RGBForeColor(&graycolor);
	draw_game_date();
	draw_game_progress();
	/* Draw a solid separating line between date info and side list. */
	RGBForeColor(&ltgraycolor);
	MoveTo(0, gametophgt);
	Line(gamewinw, 0);
	ForeColor(blackColor);
	for_all_sides(side2) {
		draw_game_side(side2);
	}
	SetPort(oldport);
}

/* Display the current time and date and any realtime countdowns. */

void
draw_game_date()
{
	Rect tmprect;

	SetRect(&tmprect, 0, 0, gamewinw, gamedatehgt - 1);
	EraseRect(&tmprect);
	MoveTo(tmprect.left + 10, tmprect.top + 12);
	TextFace(bold);
	DrawText(curdatestr, 0, strlen(curdatestr));
	TextFace(0);
	draw_game_clocks();
#ifdef DEBUGGING
    /* Indicate the state of all the debug flags (and profiling). */
	if (Debug || DebugM || DebugG || Profile) {
		sprintf(spbuf, "%c%c%c%c",
				(Debug ? 'D' : ' '), (DebugM ? 'M' : ' '), (DebugG ? 'G' : ' '),
				(Profile ? 'P' : ' '));
		MoveTo(tmprect.right - 40, tmprect.top + 12);
		DrawText(spbuf, 0, strlen(spbuf));
	}
#endif  /* DEBUGGING */
}

void
draw_game_clocks()
{
	int elapsed, s2, sy;
    	time_t now;
	Rect tmprect;
	Side *side2;

	/* Draw per-turn and per-game time limits that for the game as a whole. */
	SetRect(&tmprect, 0, gamedatehgt, gamewinw / 2, gamedatehgt + gameclockhgt - 1);
	if (g_rt_per_turn() > 0) {
		time(&now);
    	elapsed = (int) difftime(now, turn_play_start_in_real_time);
		time_desc(spbuf, g_rt_per_turn() - elapsed, g_rt_per_turn());
		EraseRect(&tmprect);
		MoveTo(tmprect.left + 20, tmprect.top + 10);
		DrawText(spbuf, 0, strlen(spbuf));
		lastnow = now;
	}
	OffsetRect(&tmprect, 100, 0);
	if (g_rt_for_game() > 0) {
		time(&now);
    	elapsed = (int) difftime(now, game_start_in_real_time);
		time_desc(spbuf, g_rt_for_game() - elapsed, g_rt_for_game());
		EraseRect(&tmprect);
		MoveTo(tmprect.left + 10, tmprect.top + 10);
		DrawText(spbuf, 0, strlen(spbuf));
		lastnow = now;
	}
	/* Draw per-side clocks if any limits defined. */
	if (g_rt_per_side() > 0) {
		for_all_sides(side2) {
			if (side2->ingame
			     && (side_has_ai(side2) || side_has_display(side2))) {
				s2 = side_number(side2);
				if (side_has_ai(indepside) || side_has_display(indepside))
				    ++s2;
				sy = gametophgt + (s2 - 1) * gamesidehgt + gamesideclockoffset;
				elapsed = 0;
				if (!side2->finishedturn)
    			  		/* should be side start */
    			  		elapsed = (int) difftime(now, turn_play_start_in_real_time); 
				time_desc(spbuf, g_rt_per_side() - side2->totaltimeused - elapsed, g_rt_per_side());
				SetRect(&tmprect, 0, sy, gamewinw, sy + gamesideclockhgt - 1);
				EraseRect(&tmprect);
				MoveTo(tmprect.left + 20, tmprect.top + 10);
				DrawText(spbuf, 0, strlen(spbuf));
				/* (should draw per-turn side usage) */
			}
		}
	}
}

void
draw_game_progress()
{
	Rect tmprect;

	SetRect(&tmprect, 0, gamenoteoffset, gamewinw, gamenoteoffset + gamenotehgt - 1);
	EraseRect(&tmprect);
	MoveTo(23, gamenoteoffset + 12);
	DrawText(game_progress_str, 0, strlen(game_progress_str));
}

/* Draw info about a given side. */

void
draw_game_side(Side *side2)
{
	int s2 = side_number(side2);
	int sx = 23, sy; 

	if (inactive_indepside(side2)) {
	    return;
	} else if (side_has_ai(indepside) || side_has_display(indepside)) {
		++s2;
	}
	sy = gametophgt + (s2 - 1) * gamesidehgt;
	draw_side_emblem(gamewin, 2, sy + 4, 16, 16, side_number(side2), shadow_emblem);
	strcpy(spbuf, short_side_title(side2));
	MoveTo(sx, sy + 13);
	/* Put the name of our side in boldface. */
	TextFace((side2 == dside ? bold : 0));
	if (!side2->ingame || endofgame)
	    RGBForeColor(&graycolor);
	DrawText(spbuf, 0, strlen(spbuf));
	TextFace(0);
	if (side_has_ai(side2)) {
		/* Show that the side is run by an AI. */
		plot_sicn(gamewin, 142, sy + 2, aisicnhandle, 0, TRUE, srcOr);
	}
	if (side2 != dside
	    && (side_has_ai(side2) || side_has_display(side2))) {
		/* Indicate attitude of other side. */
		plot_sicn(gamewin, 126, sy + 2,
			facesicnhandle[feeling_towards(side2, dside)], 0, TRUE, srcOr);
	}
	ForeColor(blackColor);
	last_status[s2] = -1;
	last_status_left[s2] = -1;
	last_status_resv[s2] = -1;
	last_status_fini[s2] = -1;
	*(last_status_score1[s2]) = '\0';
	draw_side_status(side2);
	/* Draw a separating line. */
	RGBForeColor(&ltgraycolor);
	MoveTo(0, sy + gamesidehgt);
	Line(gamewinw, 0);
	ForeColor(blackColor);
}

/* (should make this more generic) */

int
feeling_towards(Side *side, Side *side2)
{
	if (trusted_side(side2, side)) {
		return 2;
	} else if (side_has_ai(side) && should_try_to_win(side)) {
		return 0;
	} else {
		return 1;
	}
}

/* Draw the current details about a side. */

void
draw_side_status(Side *side2)
{
	int s2 = side_number(side2);
	int sx, sy, i;
	int totacp, resvacp, acpleft, percentleft, percentresv;
	int newstatus;
	char *scoredesc;
	Rect siderect, tmprect, progressrect;
	RGBColor tmpcolor;
    	Scorekeeper *sk;
    	extern int curpriority;

	if (side_has_ai(indepside) || side_has_display(indepside))
		++s2;
    /* Be safe.  It's probably not great to be passing through here before the game
       window is actually set up, but it's easier to just ignore the attempt than
       to figure out why it's attempting... */
	if (last_status == NULL)
	  return;
	/* If this is the first time we draw a side that lost, we also want to redraw the
	name etc. so that they are grayed out. The test for last_status[s2] != -1 is to
	prevent an infinite loop. */
	if (!side2->ingame 
	    && last_status[s2] != 3 && last_status[s2] != -1) {
		draw_game_side(side2);
		return;
	}
	sy = gametophgt + (s2 - 1) * gamesidehgt;
	newstatus = last_status[s2];
	percentleft = 0;
	SetRect(&siderect, 0, sy + 1, gamewinw, sy + gamesidehgt);
	/* Set up the area where we show progress. */
	SetRect(&progressrect, 24, sy + 13 + 4, 24 + 100, sy + 13 + 4 + 7);
	if (!side2->ingame || endofgame) {
		/* last_status == 3 now means that the side is out of the game. */
		if (last_status[s2] != 3) {
			if (side_won(side2)) {
				/* should indicate that this side has won. */
				/* draw like a trophy or flourishes or some such? */
			} else if (side_lost(side2)) {
				/* Draw a (solid) line crossing out the loser.  Simple and obvious. */
				tmpcolor.red = 0xFFFF;
				tmpcolor.green = 0; 
				tmpcolor.blue = 0;
				RGBForeColor(&tmpcolor);
				PenSize(2,2);
				MoveTo(1, sy + 8);
				Line(gamewin->portRect.right - 3, 0);
				ForeColor(blackColor);
				PenNormal();
			}
			newstatus = 3;
		}
#ifdef DESIGNERS
	} else if (numdesigners > 0) {
		if (side2->ingame && last_status[s2] != 0) {
			tmprect = progressrect;
			EraseRect(&tmprect);
			newstatus = 0;
		}
#endif /* DESIGNERS */
	} else {
		if (!g_use_side_priority() || curpriority == side2->priority) {
			/* Show the current acp totals/progress of the side. */
			/* This is not quite the security hole it might seem,
			   you don't get much advantage out of seeing how far along each side is,
			   and it gives you a feel for how the turn is progressing. */
			totacp = side_initacp(side2);
			if (totacp > 0) {
				acpleft = side_acp(side2);
				resvacp = side_acp_reserved(side2);
				if (totacp > 0) {
					percentleft = (100 * acpleft) / totacp;
					percentleft = max(0, min(99, percentleft));
					percentresv = (100 * resvacp) / totacp;
					/* Acp in reserve should be less than acp total. */
					percentresv = max(0, min(percentleft, percentresv));
				} else {
					percentleft = percentresv = 0;
				}
				/* Only draw if there's been any actual change. */
				if (last_status[s2] != 1
					|| last_status_left[s2] != percentleft
					|| last_status_resv[s2] != percentresv) {
					if (percentleft > 0) {
						tmprect = progressrect;
						InsetRect(&tmprect, 1, 1);
						EraseRect(&tmprect);
						tmprect.right = tmprect.left + percentleft - 1;
						tmpcolor.red = 0;
						tmpcolor.green = 0xFFFF; 
						tmpcolor.blue = 0;
						RGBForeColor(&tmpcolor);
						FillRect(&tmprect, QDPat(black));
						ForeColor(blackColor);
					}
					if (percentresv > 0) {
						tmprect = progressrect;
						InsetRect(&tmprect, 1, 1);
						tmprect.right = tmprect.left + percentresv - 1;
						tmpcolor.red = 0xFFFF;
						tmpcolor.green = 0; 
						tmpcolor.blue = 0xFFFF;
						RGBForeColor(&tmpcolor);
						FillRect(&tmprect, QDPat(black));
						ForeColor(blackColor);
					}
					last_status_left[s2] = percentleft;
					last_status_resv[s2] = percentresv;
				}
				newstatus = 1;
			} else {
				if (last_status[s2] != 2) {
					tmprect = progressrect;
					InsetRect(&tmprect, 1, 1);
					EraseRect(&tmprect);
					newstatus = 2;
				}
			}
		} else if (g_use_side_priority() && curpriority != side2->priority) {
			newstatus = 0;
		}
		/* (should this be a generic kernel test?) */
		if (side2->finishedturn || !(side_has_ai(side2) || side_has_display(side2))) {
			if (last_status_fini[s2] != 1) {
				tmprect = progressrect;
				InsetRect(&tmprect, 1, 1);
				EraseRect(&tmprect);
				tmpcolor.red = 0xFFFF;
				tmpcolor.green = 0xA000; 
				tmpcolor.blue = 0xFFFF;
				RGBForeColor(&tmpcolor);
				tmprect.right = tmprect.left + percentleft - 1;
				FillRect(&tmprect, QDPat(black));
				ForeColor(blackColor);
				last_status_fini[s2] = 1;
			}
		} else if (!side2->finishedturn) {
			last_status_fini[s2] = 0;
		}
	}
	/* Decide how to frame the progress bar - each shade indicates something. */
	if (newstatus != last_status[s2]) {
		if (newstatus == 0 || newstatus == 3) {
			EraseRect(&progressrect);
			PenPat(QDPat(white));
		} else if (/*newstatus == */1) {
			PenPat(QDPat(black));
		} else if (newstatus == 2) {
			PenPat(QDPat(gray));
		} else {
			PenPat(QDPat(ltGray));
		}
		FrameRect(&progressrect);
		PenNormal();
		last_status[s2] = newstatus;
	}
	/* Always draw the score. */
	if (keeping_score()) {
		siderect.top += gamesidescoreoffset;
		siderect.bottom = siderect.top + gamesidescorehgt - 1;
		TextFont(small_font_id);
		TextSize(small_font_size);
		if (!side2->ingame || endofgame)
		    RGBForeColor(&graycolor);
		i = 0;
		for_all_scorekeepers(sk) {
			scoredesc = side_score_desc(spbuf, side2, sk);
			if ((i == 0 && strcmp(scoredesc, last_status_score1[s2]) != 0)
				|| i > 0) {
				if ((i & 1) == 0)
				  EraseRect(&siderect);
				/* Draw two scorekeepers per line. */
				sx = (((i & 1) == 1) ? gamewinw / 2 : 0);
				/* Draw the scorekeeper's status. */
				MoveTo(sx + 24, siderect.top + 10);
				DrawText(scoredesc, 0, strlen(scoredesc));
				if (i == 0)
				  strcpy(last_status_score1[s2], scoredesc);
			}
			++i;
			/* Offset rectangle to next row. */
			if ((i & 1) == 0)
			  OffsetRect(&siderect, 0, gamesidescorehgt);
		}
		TextFont(large_font_id);
		TextSize(large_font_size);
		ForeColor(blackColor);
	}
}

void
do_mouse_down_game(Point mouse, int mods)
{
	beep();
}

/* Create and/or bring up the construction planning window. */

void
show_construction_window()
{
	if (constructionwin == nil) {
		create_construction_window();
	} else {
		reinit_construction_lists();
	}
	SelectTheWindow(constructionwin);
	ActivateWindow(constructionwin, true);
}

/* The construction planning window. */

void
create_construction_window()
{
	int done = FALSE, mainheight;
	Point cellsize;
	Rect listrect, tmprect;

	if (hasColorQD) {
		constructionwin = GetNewCWindow(wConstruction, NULL, NULL);
	} else {
		constructionwin = GetNewWindow(wConstruction, NULL, NULL);
	}
	constructbutton = GetNewControl(cConstructButton, constructionwin);
	developbutton = GetNewControl(cDevelopButton, constructionwin);
	researchbutton = GetNewControl(cResearchButton, constructionwin);
	SetPort(constructionwin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	calc_construction_rects();
	run_length_text = TENew(&runlengthrect, &runlengthrect);
	set_construction_run_length(DEFAULT_RUN);
	editedrunlength = -1;
	/* Set up the list of all constructing units. */
	tmprect = unitlistrect;
	tmprect.right -= sbarwid;
	SetRect(&listrect, 0, 0, 1, 0);
	SetPt(&cellsize, 300, 12);
	/* Create the list of units itself. */
	construction_unit_list =
		LNew(&tmprect, &listrect, cellsize, 128, constructionwin,
			 FALSE, FALSE, FALSE, TRUE);
	/* Now set up the list of types. */
	tmprect = typelistrect;
	tmprect.right -= sbarwid;
	SetRect(&listrect, 0, 0, 1, 0);
	/* (should calc this from the desired font) */
	SetPt(&cellsize, 300, 12);
	construction_type_list =
		LNew(&tmprect, &listrect, cellsize, 128, constructionwin,
			 FALSE, FALSE, FALSE, TRUE);
	/* Now set up the list of advances. */
	tmprect = advancelistrect;
	tmprect.right -= sbarwid;
	SetRect(&listrect, 0, 0, 1, 0);
	/* (should calc this from the desired font) */
	SetPt(&cellsize, 300, 12);
	construction_advance_list =
		LNew(&tmprect, &listrect, cellsize, 128, constructionwin,
			 FALSE, FALSE, FALSE, TRUE);
	init_construction_lists();
	/* Finally make it floating. */
	MakeFloat(constructionwin);
}

/* Build the list of constructing units and the list of constructible types. */

void
init_construction_lists()
{
	int a, u;
	Unit *unit;
	Cell tmpcell;

	/* Update the list of units. */
	LDoDraw(0, construction_unit_list);
	LDelRow(0, 0, construction_unit_list);
	SetPt(&tmpcell, 0, 0);
	/* Create the vector of constructing units, at a reasonable initial size. */
	if (constructor_units == NULL) {
		constructor_units = make_unit_vector(max(50, numunits));
	}
	clear_unit_vector(constructor_units);
	for_all_side_units(dside, unit) {
		maybe_add_unit_to_construction_list(unit);
	}
	LDoDraw(1, construction_unit_list);
	/* Update the list of types. */
	LDoDraw(0, construction_type_list);
	LDelRow(0, 0, construction_type_list);
	SetPt(&tmpcell, 0, 0);
	if (possibletypes == NULL)
	  possibletypes = (int *) xmalloc(numutypes * sizeof(int));
	numposstypes = 0;
	for_all_unit_types(u) {
		if (construction_possible(u) 
		    && side_can_build(dside, u)) {
			LAddRow(1, tmpcell.v, construction_type_list);
			constructible_desc(spbuf, dside, u, NULL);
			LSetCell(spbuf, strlen(spbuf), tmpcell, construction_type_list);
			++tmpcell.v;
			possibletypes[numposstypes++] = u;
		}
	}
	LDoDraw(1, construction_type_list);

	/* Update the list of advances. */
	LDoDraw(0, construction_advance_list);
	LDelRow(0, 0, construction_advance_list);
	SetPt(&tmpcell, 0, 0);
	if (possibleadvances == NULL && numatypes > 0)
	  possibleadvances = (int *) xmalloc(numatypes * sizeof(int));
	numpossadvances = 0;
	for_all_advance_types(a) {
		if (side_can_research(dside, a)) {
			LAddRow(1, tmpcell.v, construction_advance_list);
			researchible_desc(spbuf, get_selected_construction_unit(), a);
			LSetCell(spbuf, strlen(spbuf), tmpcell, construction_advance_list);
			++tmpcell.v;
			possibleadvances[numpossadvances++] = a;
		}
	}
	LDoDraw(1, construction_advance_list);

	adjust_construction_controls();
}

void
reinit_construction_lists()
{
	init_construction_lists();
}

void
set_construction_run_length(int len)
{
	/* Do nothing if no change. */
    if (len == currunlength)
      return;
    currunlength = len;
	TESetSelect(0, 32767, run_length_text);
	TEDelete(run_length_text);
	sprintf(tmpbuf, "%d", len);
	TEInsert(tmpbuf, strlen(tmpbuf), run_length_text);
}

/* Draw the construction window by updating the lists and framing them. */

void
draw_construction()
{
	Rect tmprect;

	calc_construction_rects();
	TEUpdate(&(constructionwin->portRect), run_length_text);
	tmprect = runlengthrect;
	InsetRect(&tmprect, -1, -1);
	FrameRect(&tmprect);
	LUpdate(constructionwin->visRgn, construction_unit_list);
	tmprect = unitlistrect;
	InsetRect(&tmprect, -1, -1);
	FrameRect(&tmprect);
	LUpdate(constructionwin->visRgn, construction_type_list);
	tmprect = typelistrect;
	InsetRect(&tmprect, -1, -1);
	FrameRect(&tmprect);
	LUpdate(constructionwin->visRgn, construction_advance_list);
	tmprect = advancelistrect;
	InsetRect(&tmprect, -1, -1);
	FrameRect(&tmprect);
	/* Maybe show the construct button as the default. */
	draw_construction_default();
}

/* Draw a heavy outline around the construction button. */

void
draw_construction_default()
{
	Rect tmprect;
	GrafPtr oldport;

	GetPort(&oldport);
	SetPort(constructionwin);
	tmprect = (*constructbutton)->contrlRect;
	PenSize(3, 3);
	InsetRect(&tmprect, -4, -4);
	if ((*constructbutton)->contrlHilite != 0) {
		PenMode(patBic);
	}
	FrameRoundRect(&tmprect, 16, 16);
	PenNormal();
	SetPort(oldport);
}

/* Figure out how to subdivide the construction window for the two lists. */

void
calc_construction_rects()
{
	int wid, hgt, divide1, divide2;
	Rect tmprect;

	/* (should compute based on num chars needed, plus font size) */
	maxtypewid = 250;
	tmprect = constructionwin->portRect;
	runlengthrect = tmprect;
	runlengthrect.left = runlengthrect.right - 100;  runlengthrect.top = 5;
	runlengthrect.right -= 20;  runlengthrect.bottom = 25;
	wid = tmprect.right - tmprect.left - sbarwid;
	hgt = tmprect.bottom - tmprect.top - sbarwid;
	if (wid / 3 > maxtypewid) {
		divide1 = wid - 2 * maxtypewid;
		divide2 = wid - maxtypewid;
	} else {
		divide1 = wid / 3;
		divide2 = 2 * wid / 3;
	}
	SetRect(&unitlistrect, 0, constructtop, divide1, hgt);
	InsetRect(&unitlistrect, constructmargin, constructmargin);
	SetRect(&typelistrect, divide1, constructtop, divide2, hgt);
	InsetRect(&typelistrect, constructmargin, constructmargin);
	SetRect(&advancelistrect, divide2, constructtop, wid, hgt);
	InsetRect(&advancelistrect, constructmargin, constructmargin);
}

void
activate_construction(int activate)
{
	if (activate)
	  TEActivate(run_length_text);
	else
	  TEDeactivate(run_length_text);
	LActivate(activate, construction_unit_list);
	LActivate(activate, construction_type_list);
	LActivate(activate, construction_advance_list);
}

Unit *
get_selected_construction_unit()
{
	Point tmpcell;
	Unit *unit;

	SetPt(&tmpcell, 0, 0);
	if (LGetSelect(TRUE, &tmpcell, construction_unit_list)) {				
		if (tmpcell.v < constructor_units->numunits) {
			unit = unit_in_vector(constructor_units, tmpcell.v);
			if (is_active(unit))
			  return unit;
		}
	}
	return NULL;
}

int
get_selected_construction_type()
{
	Point tmpcell;

	SetPt(&tmpcell, 0, 0);
	if (LGetSelect(TRUE, &tmpcell, construction_type_list)) {				
		if (tmpcell.v < numposstypes) {
			return possibletypes[tmpcell.v];
		}
	}
	return NONUTYPE;
}

int
get_selected_construction_advance()
{
	Point tmpcell;

	SetPt(&tmpcell, 0, 0);
	if (LGetSelect(TRUE, &tmpcell, construction_advance_list)) {				
		if (tmpcell.v < numpossadvances) {
			return possibleadvances[tmpcell.v];
		}
	}
	return NONATYPE;
}

void
scroll_to_selected_construction_unit()
{
	Unit *unit;

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

/* Handle a click anywhere within the construction window. */

void
do_mouse_down_construction(Point mouse, int mods)
{
	ControlHandle control;
	short part;
	int a, u;
	Unit *unit;

	part = FindControl(mouse, constructionwin, &control);
	if (control == constructbutton) {
		unit = get_selected_construction_unit();
		if (unit != NULL) {
			u = get_selected_construction_type();
			if (u != NONUTYPE) {
				net_push_build_task(unit, u, currunlength, 0, 0);
				update_construction_unit_list(unit);
				return;
			}
		}
	} else if (control == developbutton) {
		unit = get_selected_construction_unit();
		if (unit != NULL) {
			u = get_selected_construction_type();
			if (u != NONUTYPE) {
				net_push_develop_task(unit, u, u_tech_to_build(u));
				update_construction_unit_list(unit);
				return;
			}
		}
	} else if (control == researchbutton) {
		unit = get_selected_construction_unit();
		if (unit != NULL) {
			a = get_selected_construction_advance();
			if (a != NONATYPE) {
				net_set_unit_curadvance(unit->side, unit, a);
				update_construction_unit_list(unit);
				return;
			}
		}
	} else if (PtInRect(mouse, &runlengthrect)) {
		TEClick(mouse, mods, run_length_text);
		/* (should switch this to be current item) */
	} else if (PtInRect(mouse, &unitlistrect)) {
		LClick(mouse, mods, construction_unit_list);
		/* Update the type list to show what could be built and in how long. */
		update_type_list_for_unit(get_selected_construction_unit());
		update_advance_list_for_unit(get_selected_construction_unit());
	} else if (PtInRect(mouse, &typelistrect)) {
		LClick(mouse, mods, construction_type_list);
		/* Update the unit list to show what could build the type */
		update_unit_list_for_type(get_selected_construction_type());
	} else if (PtInRect(mouse, &advancelistrect)) {
		LClick(mouse, mods, construction_advance_list);
		/* Update the unit list to show what could build the type */
		update_unit_list_for_advance(get_selected_construction_advance());
	} else {
		/* Click was not in any useful part of the window. */ 
	}
}

int
do_key_down_construction(key)
int key;
{
	int len, runlength, i;
	char buffer[10];
	CharsHandle text;
	Unit *unit;
	short u;

	if (isdigit(key)) {
		/* Feed digits to the run length field. */
		TEKey(key, run_length_text);
		text = TEGetText(run_length_text);
		/* Pick out only the initial digits (up to 9). */
		len = min((*run_length_text)->teLength, 9);
		strncpy(buffer, *text, len);
		buffer[len] = '\0';
		runlength = atoi(buffer);
		if (between(1, runlength, 32767)) {
			currunlength = runlength;
			editedrunlength = runlength;
			return TRUE;
		}
	} else if (key == 8) {
		/* Also feed erase key the run length field. */
		TEKey(key, run_length_text);
		return TRUE;
	} else if (key == 13 || key == 3) {
		/* Set construction task and close the window if Enter or Return was hit. */
		unit = get_selected_construction_unit();
		if (unit != NULL) {
			u = get_selected_construction_type();
			if (u != NONUTYPE) {
				net_push_build_task(unit, u, currunlength, 0, 0);
				update_construction_unit_list(unit);
				close_window(constructionwin);
				return TRUE;
			}
		}
	} else {
		for (i = 0; i < numposstypes; ++i) {
			if (key == unitchars[possibletypes[i]]
				/* Skip over types that the selected unit can't build. */
				&& est_completion_time(get_selected_construction_unit(), possibletypes[i]) >= 0) {
				select_type_in_construction_window(possibletypes[i]);
				return TRUE;
			}
		}
	}
	return FALSE;
}

/* Highlight exactly one specific unit in the construction window, and unhighlight
   any others. */

void
select_unit_in_construction_window(Unit *unit)
{
	int i;
	Point tmpcell;

	for (i = 0; i < constructor_units->numunits; ++i) {
		SetPt(&tmpcell, 0, i);
		LSetSelect((unit == unit_in_vector(constructor_units, i)), tmpcell, construction_unit_list);
		LAutoScroll(construction_unit_list);
	}
	update_type_list_for_unit(get_selected_construction_unit());
}

void
select_type_in_construction_window(int u)
{
	int i;
	Point tmpcell;

	for (i = 0; i < numposstypes; ++i) {
		SetPt(&tmpcell, 0, i);
		LSetSelect((u == possibletypes[i]), tmpcell, construction_type_list);
		LAutoScroll(construction_type_list);
	}
	if (u == NONUTYPE)
	  return;
	update_unit_list_for_type(get_selected_construction_type());
}

void
select_advance_in_construction_window(int a)
{
	int i;
	Point tmpcell;

	for (i = 0; i < numpossadvances; ++i) {
		SetPt(&tmpcell, 0, i);
		LSetSelect((a == possibleadvances[i]), tmpcell, construction_advance_list);
		LAutoScroll(construction_advance_list);
	}
	if (a == NONATYPE)
	  return;
	update_unit_list_for_advance(get_selected_construction_advance());
}

/* Given a unit (which may be any unit), update the list of constructing units. */

void
update_construction_unit_list(Unit *unit)
{
	int i, u;
	Point tmpcell;

	if (constructionwin == nil)
	  return;
	u = get_selected_construction_type();
	/* We need to look for it even if it might not be ours, since it might
	   have been captured or otherwise lost, and needs to be removed. */
	for (i = 0; i < constructor_units->numunits; ++i) {
		if (unit == unit_in_vector(constructor_units, i)) {
			SetPt(&tmpcell, 0, i);
			if (is_active(unit)
				&& (can_build_or_help(unit) || can_research(unit))
				&& side_controls_unit(dside, unit)) {
				construction_desc(spbuf, unit, u);
				LSetCell(spbuf, strlen(spbuf), tmpcell, construction_unit_list);
			} else {
				remove_unit_from_vector(constructor_units, unit, i);
				LDelRow(1, tmpcell.v, construction_unit_list);
			}
			return;
		}
	}
	/* Unit was not found, try to add it to the list. */
	maybe_add_unit_to_construction_list(unit);
}

void
maybe_add_unit_to_construction_list(Unit *unit)
{
	Point tmpcell;

	if (is_active(unit)
		&& (can_build_or_help(unit) || can_research(unit))
		&& side_controls_unit(dside, unit)) {
		/* Add this unit to the vector of constructing units. */
		constructor_units = add_unit_to_vector(constructor_units, unit, 0);
		/* (should sort and maybe rearrange list here) */
		/* Add a row at the end of the list. */
		SetPt(&tmpcell, 0, constructor_units->numunits - 1);
		LAddRow(1, constructor_units->numunits - 1, construction_unit_list);
		construction_desc(spbuf, unit, get_selected_construction_type());
		LSetCell(spbuf, strlen(spbuf), tmpcell, construction_unit_list);
	}
}

void
update_unit_list_for_type(int u)
{
	int i;
	Point tmpcell;
	Unit *unit;

	for (i = 0; i < constructor_units->numunits; ++i) {
		unit = unit_in_vector(constructor_units, i);
		if (unit != NULL) {
			SetPt(&tmpcell, 0, i);
			if (is_active(unit) && unit->side == dside) {
				construction_desc(spbuf, unit, u);
				LSetCell(spbuf, strlen(spbuf), tmpcell, construction_unit_list);
			} else {
/*				LDelRow(1, tmpcell.v, construction_unit_list); */
				LSetCell("", 0, tmpcell, construction_unit_list);
			}
		}
	}
	adjust_construction_controls();
}

void
update_unit_list_for_advance(int a)
{
	int i;
	Point tmpcell;
	Unit *unit;

	for (i = 0; i < constructor_units->numunits; ++i) {
		unit = unit_in_vector(constructor_units, i);
		if (unit != NULL) {
			SetPt(&tmpcell, 0, i);
			if (is_active(unit) && unit->side == dside) {
				research_desc(spbuf, unit, a);
				LSetCell(spbuf, strlen(spbuf), tmpcell, construction_unit_list);
			} else {
/*				LDelRow(1, tmpcell.v, construction_unit_list); */
				LSetCell("", 0, tmpcell, construction_unit_list);
			}
		}
	}
	adjust_construction_controls();
}

void
update_construction_type_list()
{
	int u;

	if (constructionwin == nil)
	  return;
	u = get_selected_construction_type();
	update_type_list_for_unit(get_selected_construction_unit());
}

void
update_type_list_for_unit(Unit *unit)
{
	int i;
	Point tmpcell;

	for (i = 0; i < numposstypes; ++i) {
		constructible_desc(spbuf, dside, possibletypes[i], get_selected_construction_unit());
		SetPt(&tmpcell, 0, i);
		LSetCell(spbuf, strlen(spbuf), tmpcell, construction_type_list);
	}
	adjust_construction_controls();
}

void
update_construction_advance_list()
{
	int a;

	if (constructionwin == nil)
	  return;
	a = get_selected_construction_advance();
	update_advance_list_for_unit(get_selected_construction_unit());
}

void
update_advance_list_for_unit(Unit *unit)
{
	int i;
	Point tmpcell;

	for (i = 0; i < numpossadvances; ++i) {
		researchible_desc(spbuf, get_selected_construction_unit(), possibleadvances[i]);
		SetPt(&tmpcell, 0, i);
		LSetCell(spbuf, strlen(spbuf), tmpcell, construction_advance_list);
	}
	adjust_construction_controls();
}

/* Enable/disable controls according to whether the selected list elements can
   do construction activities. */

void
adjust_construction_controls()
{
	int a, u, canconstruct = FALSE, candevelop = FALSE, canresearch = FALSE, len;
	Unit *unit;

	unit = get_selected_construction_unit();
	if (unit != NULL) {
		u = get_selected_construction_type();
		if (u != NONUTYPE) {
			if (uu_acp_to_create(unit->type, u) > 0
	    		  && side_can_build(unit->side, u))
			     canconstruct = TRUE;
			if (uu_acp_to_develop(unit->type, u) > 0)
			     candevelop = TRUE;
		}
		a = get_selected_construction_advance();
		if (a != NONATYPE) {
			if (u_can_research(unit->type)
			    && side_can_research(dside, a)) {
				canresearch = TRUE;
			}
		}
	}
	HiliteControl(constructbutton, (canconstruct ? 0 : 255));
	HiliteControl(developbutton, (candevelop ? 0 : 255));
	HiliteControl(researchbutton, (canresearch ? 0 : 255));
	draw_construction_default();
	/* If there is doctrine on construction run length, use it to seed the
	   run length in the dialog. */
	if (editedrunlength < 0 && unit != NULL && u != NONUTYPE) {
		len = DEFAULT_RUN;
		if (construction_run_doctrine(unit, u) > 0) {
			len = construction_run_doctrine(unit, u);
		}
		set_construction_run_length(len);
	}
}

/* Resize the construction window to the given size. */

void
grow_construction(int h, int v)
{
	EraseRect(&constructionwin->portRect);
	SizeWindow(constructionwin, h, v, 1);
	adjust_construction_items();
	/* This will force a full redraw at the next update. */
	InvalRect(&constructionwin->portRect);
}					

/* Zooming "rightsizes" the window. */

void
zoom_construction(int part)
{
	int titleh, vislinesavail;
	Rect zoomrect;
	GDHandle zoomgd;

	EraseRect(&constructionwin->portRect);
	if (part == inZoomOut) {
		if (hasColorQD) {
			zoomgd = best_zoom_screen(&constructionwin->portRect);
			zoomrect = (*zoomgd)->gdRect;
			if (zoomgd == GetMainDevice()) {
				zoomrect.top += GetMBarHeight();
			}
		} else {
			/* If no Color QD, then there is only the one screen. */
			zoomrect = QD(screenBits).bounds;
			zoomrect.top += GetMBarHeight();
		}
		titleh = 20; /* (should calc) */
		zoomrect.top += titleh;
		InsetRect(&zoomrect, 4, 4);
		/* If not many units or types, shrink the zoomed window to fit. */
		vislinesavail = (zoomrect.bottom - zoomrect.top - sbarwid) / 15;
		if (0) {
			zoomrect.bottom = zoomrect.top + 20  * 15 + sbarwid;
		}
		(*((WStateDataHandle) ((WindowPeek) constructionwin)->dataHandle))->stdState = zoomrect;
	}
	ZoomWindow(constructionwin, part, false);
	adjust_construction_items();
	/* This will force a full redraw at the next update. */
	InvalRect(&constructionwin->portRect);
}

/* Move and resize the list and text objects in the construction window. */

static void
adjust_construction_items()
{
	int listwid;
	Point cellsize;

	/* Recalculate size and position. */
	calc_construction_rects();
	/* Resize the run length text item. */
	(*run_length_text)->viewRect = runlengthrect;
	(*run_length_text)->destRect = runlengthrect;
	TECalText(run_length_text);
	/* Resize the unit list. */
	listwid = unitlistrect.right - unitlistrect.left - sbarwid;
	LSize(listwid, unitlistrect.bottom - unitlistrect.top,
		  construction_unit_list);
	SetPt(&cellsize, max(300, listwid), 12);
	LCellSize(cellsize, construction_unit_list);
	/* Move the type list (is this the approved way to do it?) */
	(*construction_type_list)->rView.left = typelistrect.left;
	listwid = typelistrect.right - typelistrect.left - sbarwid;
	LSize(listwid, typelistrect.bottom - typelistrect.top,
		  construction_type_list);
	SetPt(&cellsize, max(300, listwid), 12);
	LCellSize(cellsize, construction_type_list);
	/* Move the advance list (is this the approved way to do it?) */
	(*construction_advance_list)->rView.left = advancelistrect.left;
	listwid = advancelistrect.right - advancelistrect.left - sbarwid;
	LSize(listwid, advancelistrect.bottom - advancelistrect.top,
		  construction_advance_list);
	SetPt(&cellsize, max(300, listwid), 12);
	LCellSize(cellsize, construction_advance_list);
}

/* This is the top-level access to bring up the research window, can be called
   anywhere, anytime. */

void
show_research_dialog()
{
	int		height, mainheight;

	if (!active_display(dside))
	    return;
	/* Create it if necessary. */
	if (researchwin == nil) {
		researchwin = GetNewDialog(dSideResearch, NULL, NULL);
		SetWTitle(researchwin, "\pSide Research");
#if 0
		/* Put it at the bottom adjacent to noticewin. */
		get_main_screen_size(NULL, &mainheight);
		height = researchwin->portRect.bottom - researchwin->portRect.top;
		MoveWindow(researchwin, 411, mainheight - height - 5, false);
		MakeDialogFloat(researchwin);
#endif
	}
	SelectTheWindow(researchwin);
	ActivateWindow(researchwin, true);
	draw_research_dialog(true);
}

void
draw_research_dialog(int force)
{
	char			buf1[BUFSIZE], buf2[BUFSIZE], buf3[BUFSIZE];
	short 		i, m;
	Str255 		pname;
	Handle 		itemhandle;
	Rect			itemrect; 
	MenuHandle	advanceMenu;
	GrafPtr 		oldport;
	
	/* Skip if we lack display or research window. */
	if (!active_display(dside) || !researchwin)
		return;
	GetPort(&oldport);
	SetPort(researchwin);

	/* Set the autoresearch checkbox. */
	GetDItem(researchwin, diSideResearchCheck, NULL, &itemhandle, NULL);
	SetCtlValue((ControlHandle) itemhandle, dside->autoresearch);

	/* Get the current research topic, if any. */
	if (dside->research_topic == NOADVANCE) {
		strcpy(buf1, "Idle"); 
	} else if (dside->research_topic == NONATYPE) { 
		strcpy(buf1, "Stop Research"); 
	} else {
		strcpy(buf1, a_type_name(dside->research_topic));
	}
	/* Set the popup menu to the current research topic. */
	advanceMenu = build_research_menu(dside);
	m = CountMItems(advanceMenu);
	GetDItem(researchwin, diSideResearchPopup, NULL, &itemhandle, NULL);
	SetCtlMax((ControlHandle) itemhandle, m);	/* Important! */
	for (i = 1; i <= m; i++) {
		GetItem(advanceMenu, i, pname);
		p2c(pname, buf2);
		if (strcmp(buf1, buf2) != 0)
			continue;
		SetCtlValue((ControlHandle) itemhandle, i);
	}
	/* Build the three text fields. */
	if (dside->research_topic == NOADVANCE) {
		strcpy(buf1, "Your wise men are idle.");
		strcpy(buf2, "Pick a new research topic.");
	} else if (dside->research_topic == NONATYPE) {
		strcpy(buf1, "Your wise men are resting.");
		strcpy(buf2, "");
	} else {
		strcpy(buf1, "You are researching :"); 
		NumToString(dside->advance[dside->research_topic], pname);
		p2c(pname, buf3);
		strcpy(buf2, buf3);
		strcat(buf2, " of ");
		NumToString(a_rp(dside->research_topic), pname);
		p2c(pname, buf3);
		strcat(buf2, buf3);
		strcat(buf2, " points completed.");
	}
	/* Check if either text field needs an update. */
	GetDItem(researchwin, diSideResearchText1, NULL, &itemhandle, NULL);
	GetIText(itemhandle, pname);
	p2c(pname, buf3);
	if (strcmp(buf1, buf3) != 0) {
		c2p(buf1, pname);
		SetIText(itemhandle, pname);
		force = TRUE;
	}
	GetDItem(researchwin, diSideResearchText2, NULL, &itemhandle, NULL);
	GetIText(itemhandle, pname);
	p2c(pname, buf3);
	if (strcmp(buf2, buf3) != 0) { 
		c2p(buf2, pname);
		SetIText(itemhandle, pname);
		force = TRUE;
	}
	if (force) {
		DrawDialog(researchwin);
		draw_default_button(researchwin, OkButton);
	}
	SetPort(oldport);
}	

int 
do_key_down_research(char key)
{
	/* Use the Enter or Return key to exit. */
	if (key == 3 || key == 13) {
		hit_research_dialog(diSideResearchClose);
		return TRUE;
	/* Use the Escape key for the Help button. */
	} else if (key == 27) {
		hit_research_dialog(diSideResearchHelp);
		return TRUE;
	} else return FALSE;
}

void
do_mouse_down_research(Point mouse, int mods)
{
	short ditem;

	/* Check if a dialog item was hit and branch to dialog handling code in that case.
	This makes it possible to avoid using the Dialog Manager. */
	ditem = FindDItem(researchwin, mouse) + 1;	/* FindDItem returns (ditem -1)! */
	if (ditem > 0) {
		/* First undo previous conversion to locals by do_mouse_down. */
		LocalToGlobal(&mouse);
		hit_research_dialog(ditem);
		return;
	}
}


void
hit_research_dialog(int ditem)
{
	short			autoresearch, advance, mitem, done = FALSE, a, i, m;
	char			sname[32], cname[32];
	MenuHandle	advanceMenu;
	HelpNode 		*helpnode;
	Str255 		pname;
	Handle 		itemhandle;  
	GrafPtr 		oldport;
	Point			mouse = {0};
	
	GetPort(&oldport);	
	SetPort(researchwin);

	switch (ditem) {

		/* Toggle autoresearch. */
		case diSideResearchCheck: 		
			GetDItem(researchwin, ditem, NULL, &itemhandle, NULL);
			SetCtlValue((ControlHandle) itemhandle, !GetCtlValue((ControlHandle) itemhandle));
			autoresearch = GetCtlValue((ControlHandle) itemhandle);
			net_set_autoresearch(dside, autoresearch);
			/* Turn on research if it was stopped. */
			if (dside->research_topic == NONATYPE)
			    net_set_side_research(dside, NOADVANCE);
			done = TRUE;
		break;

		/* Pick a new research topic. */
		case diSideResearchPopup: 		
			advanceMenu = build_research_menu(dside);
			GetDItem(researchwin, ditem, NULL, &itemhandle, NULL);
			/* This is no longer automatically done by the dialog manager. */
			TrackControl((ControlHandle) itemhandle, mouse, (void *) -1);
			/* Make sure the new menu selection is drawn. */
			DrawControls(researchwin);
			mitem = GetCtlValue((ControlHandle) itemhandle);
			GetItem(advanceMenu, mitem, pname);
			p2c(pname, sname);
			/* Stop research if "Stop Research" was selected. */
			if (strcmp(sname, "Stop Research") == NULL) {
				net_set_side_research(dside, NONATYPE);
			} else {			
				advance = NOADVANCE;
				for_all_advance_types(a) {
					if (strcmp(a_type_name(a), sname) == NULL) {
						advance = a;
						break;
					}
				}
				net_set_side_research(dside, advance);
				/* Close the window if a real choice was made. */
				if (strcmp(sname, "Idle") != NULL)
				    done = TRUE;
			}
		break;

		case diSideResearchHelp:
			if (dside->research_topic != NOADVANCE) {	
				helpnode = find_help_node(first_help_node, a_type_name(dside->research_topic));
				if (helpnode)
					show_help_window(helpnode);
				else 	beep();
			}
		break;

		case 	diSideResearchClose:
			done = TRUE;
		break;
		
		default:
		break;
	}
	/* Force a redrawing of the dialog. */
	draw_research_dialog(true);
	if (done)
	    	close_window(researchwin);
	SetPort(oldport);
}

/*
	Research dialog for all advanced units belonging to a side. Unit is the city that just made a
	discovery and advance the discovery itself. Set to NULL and (-1) if no discovery was made. 
	This is the old global_advance_dialog which was renamed and moved here from macadv.c.
*/

void
side_research_dialog(Side *side, Unit *unit, int advance)
{
	short 		ditem, mitem, menu, num, text, status, check, done = FALSE, disabled, i, m, s;
	char			cname[32], sname[32], buf[BUFSIZE];
	int			oldprojects[MAXPROJMENUS + 1];
	int			participants[MAXATYPES];	
	Str255 		pname, pnumber;
	int			numprojects = 0;
	int			maxprojects;
	int			loafers = 0;	
	Handle 		itemhandle; 
	MenuHandle	doneMenu;
	MenuHandle	popMenu;
	GrafPtr 		oldport;
	Unit 			*unit2;
 	DialogPtr		win;
	
	/* Crash bug fix. */
	if (!side)
	  return;
	memset(oldprojects, 0, (MAXPROJMENUS + 1) * sizeof(int));
	memset(participants, 0, MAXATYPES * sizeof(int));

	/* Always give the own side, designers and debuggers full control. */
	if ((side && side == dside) || Debug || DebugG || DebugM || is_designer(dside)) {
		disabled = FALSE;
	/* Allow peeking at trusted side but with controls disabled. */
	} else if (trusted_side(dside, side)) {
		disabled = TRUE;
	/* Allow peeking at enemy if we can see everything. */ 
	} else if (dside->see_all) {
		disabled = TRUE;
	} else return;

	/* Open the dialog. */
	win = GetNewDialog(dGlobalAdvance, NULL, (DialogPtr) -1);
	GetPort(&oldport);	
	SetPort(win);

	/* Hide everything except main text. */
	HideDItem(win, diGlobalMadScientist);
	HideDItem(win, diGlobalBusyScientist);
	HideDItem(win, diGlobalIdleScientist);
	for (i = diGlobalIdleMenu; i < diGlobalIdleMenu + 4 * (MAXPROJMENUS + 1); i++)
		HideDItem(win, i);
	for (i = diGlobalIdleCheck; i <= diGlobalIdleCheck + MAXPROJMENUS; i++)
		HideDItem(win, i);
	HideDItem(win, diGlobalMoreNote);
	
	/* Count loafers and project participants. */
	for_all_side_units(side, unit2) {
		if (!u_can_research(unit2->type))
			continue;
		if (unit2->curadvance != NOADVANCE && unit2->curadvance != advance)
			participants[unit2->curadvance] += 1;				
		else loafers += 1;
	}
	/* Count the number of active projects. */
	for_all_advance_types(s) {
		if (participants[s] > 0) {
			numprojects += 1;
			oldprojects[numprojects] = s;
		}
	}
	maxprojects = min(numprojects, MAXPROJMENUS);

	/* Load achieved advances into done menu. */
	doneMenu = GetMenu(mAdvanceAchieved);
	for_all_advance_types(s) {
		if (side->advance[s] == DONE) {
			c2p(a_type_name(s), pname);
			AppendMenu(doneMenu, pname);
		}
	}
	/* Load available advances into popup menu. */
	popMenu = GetMenu(mResearchPopup);
	for_all_advance_types(s) {
		if (side_can_research(side, s)) {
			c2p(a_type_name(s), pname);
			AppendMenu(popMenu, pname);
		}
	}
	m = CountMItems(popMenu);

	/* Show mad scientist and relevant text if new discovery was signaled. */
	if (advance != NOADVANCE) {
		ShowDItem(win, diGlobalMadScientist);
		if (side == dside)
			strcpy(buf, "Your");
		else	strcpy(buf, side_adjective(side));
		strcat(buf, " scientists in ");
		strcat(buf, unit->name);
		strcat(buf, " discover ");
		strcat(buf,  a_type_name(advance));
		strcat(buf, "!\r\r");
		strcat(buf, "Pick new advance to research:");
	/* Else show busy scientist and report ongoing research. */
	} else {
		ShowDItem(win, diGlobalBusyScientist);
		if (side == dside)
			strcpy(buf, "Your");
		else	strcpy(buf, side_adjective(side));
			strcat(buf, " scientists are busy with the following projects:");
		if (loafers)
			strcat(buf, "\r\rNote : some of them are idle. You should put them to work!");
	}
	/* Show the main text. */
	c2p(buf, pname);
	GetDItem(win, diGlobalMainText, NULL, &itemhandle, NULL);
	SetIText(itemhandle, pname);

	/* Maybe show warning about more projects than shown. */
	if (numprojects > MAXPROJMENUS)
		ShowDItem(win, diGlobalMoreNote);

	if (loafers) {
		/* Show first menu (idle units) only if needed. */
		GetDItem(win, diGlobalIdleNum, NULL, &itemhandle, NULL);
		NumToString(loafers, pnumber);
		SetIText(itemhandle, pnumber);
		ShowDItem(win, diGlobalIdleNum);
		ShowDItem(win, diGlobalIdleText);
		GetDItem(win, diGlobalIdleMenu, NULL, &itemhandle, NULL);
		ShowDItem(win, diGlobalIdleMenu);
		/* Don't allow other sides to use the menu. */
		if (disabled)
			HiliteControl((ControlHandle) itemhandle, 255); 
		/* Also show first checkbox (idle units) if needed. */
		GetDItem(win, diGlobalIdleCheck, NULL, &itemhandle, NULL);
		SetCtlValue((ControlHandle) itemhandle, TRUE);
		for_all_side_units(side, unit2) {
			if (!u_can_research(unit2->type))
				continue;
			if (unit2->curadvance != NOADVANCE && unit2->curadvance != advance)
				continue;
			if (unit2->autoresearch != TRUE)
				SetCtlValue((ControlHandle) itemhandle, FALSE);
		}
		ShowDItem(win, diGlobalIdleCheck);
		/* Don't allow other sides to use the checkbox. */
		if (disabled)
			HiliteControl((ControlHandle) itemhandle, 255); 
	}
	/* Show project popup menus and associated text items. */
	num = diGlobalIdleNum;
	status = diGlobalIdleStatus;
	check = diGlobalIdleCheck;
	text = diGlobalIdleText;
	menu = diGlobalIdleMenu;
	for (s = 1; s <= maxprojects; s++) {
		/* Show the number of citites on each project. */
		GetDItem(win, ++num, NULL, &itemhandle, NULL);
		NumToString(participants[oldprojects[s]], pnumber);
		SetIText(itemhandle, pnumber);
		ShowDItem(win, num);
		/* Show the "are currently researching" text. */
		ShowDItem(win, ++text);
		/* Show the popup menu with the advance name. */
		GetDItem(win, ++menu, NULL, &itemhandle, NULL);
		SetCtlMax((ControlHandle) itemhandle, m);	/* Important! */
		for (i = 1; i <= m; i++) {
			GetItem(popMenu, i, pname);
			p2c(pname, cname);
			if (strcmp(a_type_name(oldprojects[s]), cname) != 0)
				continue;
			SetCtlValue((ControlHandle) itemhandle, i);
		}
		ShowDItem(win, menu);
		/* Don't allow other sides to use the menu. */
		if (disabled)
			HiliteControl((ControlHandle) itemhandle, 255); 
		/* Show the status info on the advence. */
		GetDItem(win, ++status, NULL, &itemhandle, NULL);
		NumToString(side->advance[oldprojects[s]], pnumber);
		p2c(pnumber, cname);
		strcat(cname, " / ");
		NumToString(a_rp(oldprojects[s]), pnumber);
		p2c(pnumber, sname);
		strcat(cname, sname);
		c2p(cname, pnumber);
		SetIText(itemhandle, pnumber);
		ShowDItem(win, status);
		/* Show the auto checkbox. */
		GetDItem(win, ++check, NULL, &itemhandle, NULL);
		SetCtlValue((ControlHandle) itemhandle, TRUE);
		for_all_side_units(side, unit2) {
			if (!u_can_research(unit2->type))
				continue;
			if (unit2->curadvance != oldprojects[s])
				continue;
			if (unit2->autoresearch != TRUE)
				SetCtlValue((ControlHandle) itemhandle, FALSE);
		}
		ShowDItem(win, check);
		/* Don't allow other sides to use the checkbox. */
		if (disabled)
			HiliteControl((ControlHandle) itemhandle, 255); 
	}

	/* Finally handle the All units checkbox. */
	GetDItem(win, diGlobalAllCheck, NULL, &itemhandle, NULL);
	/* First assume autoresearch is ON for all units. */
	SetCtlValue((ControlHandle) itemhandle, TRUE);
	for_all_side_units(side, unit2) {
		if (!u_can_research(unit2->type))
			continue;
		/*Uncheck the box if at least one unit is not on autoresearch. */ 
		if (unit2->autoresearch != TRUE) {		
			SetCtlValue((ControlHandle) itemhandle, FALSE);
			break;
		}
	}

	/* Don't allow other sides to use the checkbox. */
	if (disabled)
		HiliteControl((ControlHandle) itemhandle, 255); 

	ShowWindow(win);
	while (!done) {
		SetCursor(&QD(arrow));
		draw_default_button(win, OkButton);
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case 	OkButton:
				/* Don't allow others to change anything. */
				if (disabled) {
					done = TRUE;
					break;
				}					
				/* First deal with any idle units. */
				if (loafers) {
					GetDItem(win, diGlobalIdleMenu, NULL, &itemhandle, NULL);
					mitem = GetCtlValue((ControlHandle) itemhandle);
					GetItem(popMenu, mitem, pname);
					p2c(pname, sname);
					/* A new task was chosen for the idle units. */
					if (strcmp("Idle", sname) != 0) {
						for_all_side_units(side, unit2) {
							if (!u_can_research(unit2->type))
								continue;
							/* Set all Idle units and those who worked on the just acquired advance 
							to do selected research instead. */
							if (unit2->curadvance == NOADVANCE || unit2->curadvance == advance)
								unit2->curadvance = atype_from_name(sname);
						}
					} else {
						for_all_side_units(side, unit2) {
							if (!u_can_research(unit2->type))
								continue;
							/* Set all units who worked on the just acquired advance to Idle. */
							if (unit2->curadvance == advance)
								unit2->curadvance = NOADVANCE;
						}						
					}
					/* Then deal with the first auto checkbox. */
					GetDItem(win, diGlobalIdleCheck, NULL, &itemhandle, NULL);
					check = GetCtlValue((ControlHandle) itemhandle);
					for_all_side_units(side, unit2) {
						if (!u_can_research(unit2->type))
							continue;
						if (strcmp("Idle", sname) == 0) {
							if (unit2->curadvance == NOADVANCE || unit2->curadvance == advance) {
								unit2->autoresearch = check;
							} else continue;
						} else {
							if (unit2->curadvance == atype_from_name(sname)) {
								unit2->autoresearch = check;
							} else	 continue;
						}
					}
				}
				/* Then deal with ongoing research projects. */
				for (i = 1; i <= maxprojects; i++) {
					GetDItem(win, diGlobalIdleMenu + i, NULL, &itemhandle, NULL);
					mitem = GetCtlValue((ControlHandle) itemhandle);
					GetItem(popMenu, mitem, pname);
					p2c(pname, sname);
					/* Only proceed if we changed the project. */
					if (strcmp(a_type_name(oldprojects[i]), sname) != 0) {
						for_all_side_units(side, unit2) {
							if (!u_can_research(unit2->type))
								continue;
							/* Change units involved in the old project to new goal. */
							if (unit2->curadvance == oldprojects[i]) {
								if (strcmp("Idle", sname) == 0)
									unit2->curadvance = NOADVANCE;
								else 	unit2->curadvance = atype_from_name(sname);			
							}
						}
					}
					/* Finally deal with auto checkboxes. */
					GetDItem(win, diGlobalIdleCheck + i, NULL, &itemhandle, NULL);
					check = GetCtlValue((ControlHandle) itemhandle);
					for_all_side_units(side, unit2) {
						if (!u_can_research(unit2->type))
							continue;
						if (unit2->curadvance != oldprojects[i])
							continue;
						unit2->autoresearch = check;
					}
				}
				done = TRUE;
				break;

			case 	CancelButton:
				done = TRUE;
				break;

			default:
				/* Handle auto checkboxes. */
				for (i = 0; i <= maxprojects;  i++) {
					if (ditem == diGlobalIdleCheck + i) {
						GetDItem(win, ditem, NULL, &itemhandle, NULL);
						SetCtlValue((ControlHandle) itemhandle, 
							!GetCtlValue((ControlHandle) itemhandle));
					}
				}
				/* Handle All units checkbox. Toggle all other checkboxes. */
				if (ditem == diGlobalAllCheck) {
					GetDItem(win, ditem, NULL, &itemhandle, NULL);
					SetCtlValue((ControlHandle) itemhandle, 
						!GetCtlValue((ControlHandle) itemhandle));
					check = GetCtlValue((ControlHandle) itemhandle);
					for (i = 0; i <= maxprojects;  i++) {
						GetDItem(win, diGlobalIdleCheck + i, NULL, &itemhandle, NULL);
						SetCtlValue((ControlHandle) itemhandle, check);
					}
				}
				break;
		}
	}
	/* Close the dialog. */
	DisposeDialog(win);
	/* Restore old port. */
	SetPort(oldport);
}

/* History window. */

/* This is the top-level access to bring up the history window, can be called
   anywhere, anytime. */

void
show_history_window()
{
	if (historywin == nil) {
		create_history_window();
	}
	SelectTheWindow(historywin);
	ActivateWindow(historywin, true);
}

void
create_history_window()
{
	Rect vscrollrect;

	/* Create the window, color if possible, since images may be in color. */
	if (hasColorQD) {	
		historywin = GetNewCWindow(wHistory, NULL, NULL);
	} else {
		historywin = GetNewWindow(wHistory, NULL, NULL);
	}
	SetWTitle(historywin, "\pHistory");
	SizeWindow(historywin, 400, 122, true);
	SetPort(historywin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	/* (should calc max based on size of font and height of screen) */
	histcontents = (HistEvent **) xmalloc(maxvishistlines * sizeof(HistEvent *));
	vscrollrect = historywin->portRect;
	vscrollrect.top -= 1;
	vscrollrect.bottom -= floatsbarwid - 1;
	vscrollrect.left = vscrollrect.right - floatsbarwid;
	vscrollrect.right += 1;
	histvscrollbar = NewControl(historywin, &vscrollrect, "\p", TRUE,
			 					0, 0, 100, scrollBarProc, 0L);
	HiliteControl(histvscrollbar, 0);
	total_history_lines = update_total_hist_lines(dside);
	set_history_scrollbar();
	/* Finally make it floating. */
	MakeFloat(historywin);
}

static void
calc_history_layout()
{
	total_history_lines = update_total_hist_lines(dside);
	set_history_scrollbar();
	numhistcontents = build_hist_contents(dside, GetCtlValue(histvscrollbar), histcontents,
										  numvishistlines);
}

static void
set_history_scrollbar()
{
	int hgt, oldval, oldmax;
	HistEvent *nexthevt;

	/* Compute the number of lines available for displaying history. */
	hgt = historywin->portRect.bottom - historywin->portRect.top;
	history_line_spacing = small_line_spacing;
	numvishistlines = hgt / history_line_spacing;
	/* Tweak the scrollbar appropriately. */
	oldval = GetCtlValue(histvscrollbar);
	oldmax = GetCtlMax(histvscrollbar);
	SetCtlMax(histvscrollbar, max(0, total_history_lines - numvishistlines + 1));
	HiliteControl(histvscrollbar, (GetCtlMax(histvscrollbar) > 0 ? 0 : 255));
	SetCtlValue(histvscrollbar, GetCtlMax(histvscrollbar));
}

void
draw_history()
{
	int		i, n, headdate, numchars;
	char		*datestr, hdatebuf[100];
	Rect		contentrect;
	HistEvent	*hevt;

	if (!active_display(dside) || historywin == nil)
	  return;
	contentrect = historywin->portRect;
	contentrect.right -= floatsbarwid;
	EraseRect(&contentrect);
	/* Trick build_hist_contents into getting one extra histevent which we 
	may need to pad out the window. */
	++numvishistlines;
	numhistcontents = build_hist_contents(dside, GetCtlValue(histvscrollbar), 
				   histcontents, numvishistlines);
	--numvishistlines;
	/* Dont draw "Turn X" twice. */
	if (histcontents[0] != NULL) {
		/* Draw the header line. */
		MoveTo(4, small_line_spacing -2);
		headdate = (histcontents[0] ? histcontents[0] : histcontents[1])->startdate; 
		/* (should be relative) */
		datestr = absolute_date_string(headdate);
		sprintf(hdatebuf, "%s ...", datestr);
		/* (should clip to drawing only visible chars) */
		numchars = strlen(hdatebuf);
		TextFace(bold);
		ForeColor(redColor);
		DrawText(hdatebuf, 0, numchars);
		TextFace(normal);
		ForeColor(blackColor);
		--numhistcontents;
	}
	/* Now draw each event or date. */
	for (i = 0; i <= numhistcontents; ++i) {
		if (histcontents[i] != NULL) {
			draw_historical(histcontents[i], i, TRUE);
		} else {
			draw_historical(histcontents[i+1], i, FALSE);
		}
	}
}

void
draw_historical(HistEvent *hevt, int y, int drawevt)
{
	int pos, numchars;
	char *datestr, buf[500];

	if (hevt == NULL)
	  return;
	TextFont(small_font_id);
	TextSize(small_font_size);
	pos = history_line_spacing * y + small_line_spacing - 2;
	/* Make room for "Turn X ..." header. */
	if (histcontents[0] != NULL)
	    pos += small_line_spacing;
	MoveTo((drawevt ? 16 : 4), pos);
	if (drawevt) {
		historical_event_desc(dside, hevt, buf);
		/* Make the first letter a capital ... */
		buf[0] = uppercase(buf[0]);
		/* ... and add a period at the end. */
		sprintf(buf, "%s.", buf);
		numchars = strlen(buf);
		DrawText(buf, 0, numchars);
	} else {
		datestr = absolute_date_string(hevt->startdate);
		sprintf(buf, "%s:", datestr);
		numchars = strlen(buf);
		TextFace(bold);
		ForeColor(redColor);
		DrawText(buf, 0, numchars);
		TextFace(normal);
		ForeColor(blackColor);
	}
}

void
update_history_window(HistEvent *hevt)
{
	HistEvent *prevfirst, *prevsecond;

	prevfirst = histcontents[0];
	prevsecond = histcontents[1];
	SetPort(historywin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	calc_history_layout();
	if (histcontents[0] != prevfirst
	    || histcontents[1] != prevsecond
	    || numvishistlines > total_history_lines) {
		force_update(historywin);
	}
}

static pascal void
history_scroll_fn(ControlHandle control, short code)
{
	int curvalue, minvalue, maxvalue, pagesize, jump;

	curvalue = GetCtlValue(control);
	minvalue = GetCtlMin(control);
	maxvalue = GetCtlMax(control);
	pagesize = numvishistlines;
	if (pagesize > 1)
	  pagesize -= 1;
	switch (code) {
		case inPageDown:
			jump = pagesize;
			break;
		case inDownButton:
			jump = 1;
			break;
		case inPageUp:
			jump = - pagesize;
			break;
		case inUpButton:
			jump = -1;
			break;
		default:
			jump = 0;
			break;
	}
	curvalue = max(min(curvalue + jump, maxvalue), minvalue);
	SetCtlValue(control, curvalue);
}

void
do_mouse_down_history(Point mouse, int mods)
{
	ControlHandle control;
	short oldval, part, value;

	if (history_scroll_proc == NULL)
	  history_scroll_proc = NewControlActionProc(history_scroll_fn);

	oldval = GetCtlValue(histvscrollbar);

	part = FindControl(mouse, historywin, &control);
	if (control == histvscrollbar) {
		switch (part) {
			case inThumb:
				part = TrackControl(control, mouse, NULL);
				break;
			default:
				part = TrackControl(control, mouse, history_scroll_proc);
				break;
		}
		value = GetCtlValue(control);
		if (value != oldval) {
			Dprintf("New scroll is %d, old was %d\n", value, oldval);
			numhistcontents = build_hist_contents(dside, value, histcontents, numvishistlines);
			force_update(historywin);
		}
	} else {
		/* anything to do here? */
	}
}

/* Grow/shrink the history window to the given size. */

void
grow_history(int h, int v)
{
	EraseRect(&historywin->portRect);
	SizeWindow(historywin, h, v, 1);
	move_history_scrollbar(h, v);
	/* This will force a full redraw at the next update. */
	InvalRect(&historywin->portRect);
}					

/* Zoom the history window to its best maximal size. */

void
zoom_history(int part)
{
	int titleh, vislinesavail;
	Rect zoomrect;
	GDHandle zoomgd;

	EraseRect(&historywin->portRect);
	if (part == inZoomOut) {
		if (hasColorQD) {
			zoomgd = best_zoom_screen(&historywin->portRect);
			zoomrect = (*zoomgd)->gdRect;
			if (zoomgd == GetMainDevice()) {
				zoomrect.top += GetMBarHeight();
			}
		} else {
			/* If no Color QD, then there is only one screen. */
			zoomrect = QD(screenBits).bounds;
			zoomrect.top += GetMBarHeight();
		}
		titleh = 20; /* (should calc) */
		zoomrect.top += titleh;
		InsetRect(&zoomrect, 4, 4);
		/* If not much history, shrink the zoomed window to fit. */
		vislinesavail = (zoomrect.bottom - zoomrect.top - sbarwid) / history_line_spacing;
		total_history_lines = update_total_hist_lines(dside);
		if (vislinesavail > total_history_lines) {
			zoomrect.bottom = zoomrect.top + total_history_lines * history_line_spacing + sbarwid;
		}
		(*((WStateDataHandle) ((WindowPeek) historywin)->dataHandle))->stdState = zoomrect;
	}
	ZoomWindow(historywin, part, false);
	move_history_scrollbar(window_width(historywin), window_height(historywin));
	/* This will force a full redraw at the next update. */
	InvalRect(&historywin->portRect);
}

void
move_history_scrollbar(int h, int v)
{
	MoveControl(histvscrollbar, h - floatsbarwid, -1);
	SizeControl(histvscrollbar, floatsbarwid + 1, v + 1 - floatsbarwid + 1);
	set_history_scrollbar();
}

/* notice window. */

/* This is the top-level access to bring up the notice window, can be called
   anywhere, anytime. */

void
show_notice_window()
{
	if (noticewin == nil) {
		create_notice_window();
	}
	SelectTheWindow(noticewin);
	ActivateWindow(noticewin, true);
}

void
create_notice_window()
{
	int h, v, mainheight;
	Rect destrect, viewrect, vscrollrect, tmprect;

	/* Create the window, color if possible, since images may be in color. */
	if (hasColorQD) {	
		noticewin = GetNewCWindow(wFloatResize, NULL, NULL);
	} else {
		noticewin = GetNewWindow(wFloatResize, NULL, NULL);
	}
	SetWTitle(noticewin, "\pNotices");
	SizeWindow(noticewin, 400, 122, true);
	SetPort(noticewin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	h = window_width(noticewin);  v = window_height(noticewin);
	SetRect(&viewrect, 5, 0, h - floatsbarwid, v); 
	destrect = viewrect;
	/* Use TEStyleNew instead of TENew to enable text styles. */
	notice_text = TEStyleNew(&destrect, &destrect);
	/* Set up a vertical scrollbar. */
	vscrollrect = noticewin->portRect;
	vscrollrect.top -= 1;
	vscrollrect.bottom -= floatsbarwid - 1;
	vscrollrect.left = vscrollrect.right - floatsbarwid;
	vscrollrect.right += 1;
	notice_v_scrollbar =
		NewControl(noticewin, &vscrollrect, "\p", TRUE, 0, 0, 0, scrollBarProc, 0L);
	HiliteControl(notice_v_scrollbar, 0);
	if (1 /* position notices at bottom of main screen */) {
		get_main_screen_size(NULL, &mainheight);
		tmprect = noticewin->portRect;
		MoveWindow(noticewin,
				   3,
				   mainheight - (tmprect.bottom - tmprect.top) - 5,
				   FALSE);
	}
	/* Finally make it floating. */
	MakeFloat(noticewin);
}

void
append_notice(char *str)
{
	/* Delete old notices. */
	if (((*notice_text)->teLength) > 30000) {
		TESetSelect(0, (*notice_text)->teLength - 30000, notice_text);
		TEDelete(notice_text);
	}
#if defined(THINK_C) || defined(__MWERKS__)
	/* Hack up newlines so that TextEdit recognizes them. */
	{
		int i;
	
		for (i = 0; i < strlen(str); ++i) {
			if (str[i] == '\n')
			  str[i] = '\r';
		}
	}
#endif
	/* TEStyleNew sets lineHeight to -1 to enable variable heights. However, 
	since adjust_notice_scrollbar uses lineHeight we must set it manually to
	a suitable fixed value. */
	(*notice_text)->lineHeight = small_font_size + 3;
	/* Also update size and font from defaults. */
	noticeStyle->tsSize = small_font_size;
	noticeStyle->tsFont = small_font_id;
	/* New turn notices end with a colon. Use blue bold face text. */
	if (str[strlen(str) - 1] == ':') {
		noticeStyle->tsColor = (RGBColor) {0, 0, 0xFFFF};
		noticeStyle->tsFace = 1;
	} else {
		noticeStyle->tsColor = (RGBColor) {0, 0, 0};
		noticeStyle->tsFace = 0;
	}
	/* Set insertion point and style. */
	TESetSelect(32767, 32767, notice_text);
	TESetStyle(doFace + doFont + doSize + doColor, noticeStyle, false, notice_text);
	/* Add a linefeed before a new turn notice. */
	if (str[strlen(str) - 1] == ':') {
//		TEStyleInsert("\r", strlen("\r"), 0, notice_text);
	/* Indent all other notices by two tabs. */
	} else {
		TEStyleInsert("\t\t", strlen("\t\t"), 0, notice_text);
	}
	/* Insert the notice itself. */
	TEStyleInsert(str, strlen(str), 0, notice_text);
	/* Add a linefeed at the end of each notice. */
	TEStyleInsert("\r", strlen("\r"), 0, notice_text);
	/* Draw it. */
	draw_notice();
}

void
draw_notice()
{
	GrafPtr oldport;

	GetPort(&oldport);
	SetPort(noticewin);
	adjust_notice_scrollbar();
	TEUpdate(&(noticewin->portRect), notice_text);
	SetPort(oldport);
}

void
adjust_notice_scrollbar()
{
	int	oldmax, newmax, oldvalue, newvalue;

	oldvalue = GetCtlValue(notice_v_scrollbar);
	oldmax = GetCtlMax(notice_v_scrollbar);

	/* Compute and set the new scrollbar max value. */
	newmax = (*notice_text)->nLines 
		 - (((*notice_text)->viewRect.bottom 
		     - (*notice_text)->viewRect.top)
		     / (*notice_text)->lineHeight);
	if (newmax < 0)
	      newmax = 0;
	SetCtlMax(notice_v_scrollbar, newmax);

	/* If the thumb was at max, move it to the new max. */
	if (oldvalue == oldmax) {
		newvalue = newmax;
	/* Else leave it alone. */
	} else {
		newvalue = oldvalue;
	}
	SetCtlValue(notice_v_scrollbar, newvalue);

	/* Finally do the text scrolling. */
	TEScroll(0, (*notice_text)->viewRect.top 
		        - (*notice_text)->destRect.top
		        - (*notice_text)->lineHeight * newvalue, 
		                notice_text);
}

void
activate_notice(int activate)
{
	HiliteControl(notice_v_scrollbar, (activate ? 0 : 255));
	if (activate)
	  TEActivate(notice_text);
	else
	  TEDeactivate(notice_text);
}

static pascal void
notice_vscroll_fn(ControlHandle control, short code)
{
	int oldvalue, curvalue, minvalue, maxvalue, pagesize, jump;

	curvalue = GetCtlValue(control);
	minvalue = GetCtlMin(control);
	maxvalue = GetCtlMax(control);
	pagesize = ((*notice_text)->viewRect.bottom - (*notice_text)->viewRect.top) /
				(*notice_text)->lineHeight;
	if (pagesize > 1)
	  pagesize -= 1;
	switch (code) {
		case inPageDown:
			jump = pagesize;
			break;
		case inDownButton:
			jump = 1;
			break;
		case inPageUp:
			jump = - pagesize;
			break;
		case inUpButton:
			jump = -1;
			break;
		default:
			jump = 0;
			break;
	}
	oldvalue = curvalue;
	curvalue = max(min(curvalue + jump, maxvalue), minvalue);
	SetCtlValue(control, curvalue);
	/* Calculate the actual jump and use it to adjust the text. */
	jump = curvalue - oldvalue;
	if (jump != 0)
	  TEScroll(0, - jump * (*notice_text)->lineHeight, notice_text);
}

/* Respond to an event occurring in the notice window. */

void
do_mouse_down_notice(Point mouse, int mods)
{
	ControlHandle control;
	short part, value;

	if (notice_vscroll_proc == NULL)
	  notice_vscroll_proc = NewControlActionProc(notice_vscroll_fn);

	part = FindControl(mouse, noticewin, &control);
	if (control == notice_v_scrollbar) {
		if (part != 0) {
			switch (part) {
				case inPageDown:
				case inDownButton:
				case inPageUp:
				case inUpButton:
					value = TrackControl(control, mouse, notice_vscroll_proc);
					break;
				case inThumb:
					value = GetCtlValue(control);
					if ((part = TrackControl(control, mouse, nil)) != 0) {
						value -= GetCtlValue(control);
						if (value != 0) {
							TEScroll(0, value * (*notice_text)->lineHeight, notice_text);
						}
					}
					break;
			}
		}
	} else if (PtInRect(mouse, &((*notice_text)->viewRect))) {
		TEClick(mouse, 0, notice_text);
	}
}

void
grow_notice(int h, int v)
{
	EraseRect(&noticewin->portRect);
	SizeWindow(noticewin, h, v, 1);
	MoveControl(notice_v_scrollbar, h - floatsbarwid, -1);
	SizeControl(notice_v_scrollbar, floatsbarwid + 1, v + 1 - floatsbarwid + 1);
	(*notice_text)->viewRect.right = h - floatsbarwid;
	(*notice_text)->viewRect.bottom = v;
	(*notice_text)->destRect.right = h - floatsbarwid;
	TECalText(notice_text);
	InvalRect(&noticewin->portRect);
}					

void
zoom_notice(int part)
{
	int titleh, h, v;
	Rect zoomrect;
	GDHandle gd, zoomgd;

	EraseRect(&noticewin->portRect);
	if (part == inZoomOut) {
		if (hasColorQD) {
			zoomgd = best_zoom_screen(&noticewin->portRect);
			zoomrect = (*zoomgd)->gdRect;
			if (zoomgd == GetMainDevice()) {
				zoomrect.top += GetMBarHeight();
			}
			InsetRect(&zoomrect, 3, 3);
		} else {
			/* If no Color QD, then there is only the one screen. */
			zoomrect = QD(screenBits).bounds;
			zoomrect.top += GetMBarHeight();
			InsetRect(&zoomrect, 4, 4);
		}
		titleh = 20; /* (should calc) */
		zoomrect.top += titleh;
		(*((WStateDataHandle) ((WindowPeek) noticewin)->dataHandle))->stdState = zoomrect;
	}
	ZoomWindow(noticewin, part, false);
	h = window_width(noticewin);  v = window_height(noticewin);
	MoveControl(notice_v_scrollbar, h - floatsbarwid, -1);
	SizeControl(notice_v_scrollbar, floatsbarwid + 1, v + 1 - floatsbarwid + 1);
	adjust_notice_scrollbar();
	(*notice_text)->viewRect.right = h - floatsbarwid;
	(*notice_text)->viewRect.bottom = v;
	(*notice_text)->destRect.right = h - floatsbarwid;
	TECalText(notice_text);
	/* This will force a full redraw at the next update. */
	InvalRect(&noticewin->portRect);
}

/* scores window. */

/* This is the top-level access to bring up the scores window, can be called
   anywhere, anytime. */

void
show_scores_window()
{
	if (scoreswin == nil) {
		create_scores_window();
		append_scores(get_scores(dside));
	}
	SelectTheWindow(scoreswin);
	ActivateWindow(scoreswin, true);
}

void
create_scores_window()
{
	int h, v;
	Rect destrect, viewrect, vscrollrect;

	/* Create the window, color if possible, since images may be in color. */
	if (hasColorQD) {	
		scoreswin = GetNewCWindow(wScores, NULL, NULL);
	} else {
		scoreswin = GetNewWindow(wScores, NULL, NULL);
	}
	SetPort(scoreswin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	h = window_width(scoreswin);  v = window_height(scoreswin);
	SetRect(&viewrect, 5, 0, h - floatsbarwid, v); 
	destrect = viewrect;
	scores_text = TENew(&destrect, &destrect);
	/* Set up a vertical scrollbar. */
	vscrollrect = scoreswin->portRect;
	vscrollrect.top -= 1;
	vscrollrect.bottom -= floatsbarwid - 1;
	vscrollrect.left = vscrollrect.right - floatsbarwid;
	vscrollrect.right += 1;
	scores_v_scrollbar =
		NewControl(scoreswin, &vscrollrect, "\p", TRUE, 0, 0, 0, scrollBarProc, 0L);
	HiliteControl(scores_v_scrollbar, 0);
	add_window_menu_item("Scores", scoreswin);
	/* Finally make it floating. */
	MakeFloat(scoreswin);
}

void
append_scores(char *str)
{
	/* Delete old scoress. */
	if (((*scores_text)->teLength) > 30000) {
		TESetSelect(0, (*scores_text)->teLength - 30000, scores_text);
		TEDelete(scores_text);
	}
#if defined(THINK_C) || defined(__MWERKS__)
	/* Hack up newlines so that TextEdit recognizes them. */
	{
		int i;
	
		for (i = 0; i < strlen(str); ++i) {
			if (str[i] == '\n')
			  str[i] = '\r';
		}
	}
#endif
	TESetSelect(32767, 32767, scores_text);
	TEInsert(str, strlen(str), scores_text);
	TEInsert("\r", strlen("\r"), scores_text);
	TESetSelect(32767, 32767, scores_text);
	(*scores_text)->destRect = (*scores_text)->viewRect;
	/* Update on the screen. */
	adjust_scores_scrollbar();
	draw_scores();
}

void
draw_scores()
{
	Rect tmprect;
	GrafPtr oldport;

	GetPort(&oldport);
	SetPort(scoreswin);
	TextFont(small_font_id);
	TextSize(small_font_size);
	SetRect(&tmprect, 5, 40, 5 + 32, 40 + 32);
	EraseRect(&tmprect);
	TEUpdate(&(scoreswin->portRect), scores_text);
	SetPort(oldport);
	adjust_scores_scrollbar();
}

void
adjust_scores_scrollbar()
{
	int lines, oldmax, newmax, oldvalue, newvalue;

	oldvalue = GetCtlValue(scores_v_scrollbar);
	oldmax = GetCtlMax(scores_v_scrollbar);
	lines = (*scores_text)->nLines;
	/* Account for a return at the end of the text. */
	if (*(*(*scores_text)->hText + (*scores_text)->teLength - 1) == 0x0d)
	  ++lines;
	newmax = lines - (((*scores_text)->viewRect.bottom - (*scores_text)->viewRect.top)
					 / (*scores_text)->lineHeight);
	if (newmax < 0)
	  newmax = 0;
	SetCtlMax(scores_v_scrollbar, newmax);
	if (oldvalue == oldmax) {
		/* If the thumb was at max, move it to the new max. */
		newvalue = newmax;
	} else {
		/* Otherwise adjust it proportionally. */
		newvalue = ((*scores_text)->viewRect.top - (*scores_text)->destRect.top)
					/ (*scores_text)->lineHeight;
		if (newvalue < 0)
		  newvalue = 0;
		if (newvalue > newmax)
		  newvalue = newmax;
	}
	SetCtlValue(scores_v_scrollbar, newvalue);
	TEScroll(0, ((*scores_text)->viewRect.top - (*scores_text)->destRect.top)
				 - (GetCtlValue(scores_v_scrollbar) * (*scores_text)->lineHeight),
			 scores_text);
}

void
activate_scores(int activate)
{
	HiliteControl(scores_v_scrollbar, (activate ? 0 : 255));
	if (activate)
	  TEActivate(scores_text);
	else
	  TEDeactivate(scores_text);
}

static pascal void
scores_vscroll_fn(ControlHandle control, short code)
{
	int oldvalue, curvalue, minvalue, maxvalue, pagesize, jump;

	curvalue = GetCtlValue(control);
	minvalue = GetCtlMin(control);
	maxvalue = GetCtlMax(control);
	pagesize = ((*scores_text)->viewRect.bottom - (*scores_text)->viewRect.top) /
				(*scores_text)->lineHeight;
	if (pagesize > 1)
	  pagesize -= 1;
	switch (code) {
		case inPageDown:
			jump = pagesize;
			break;
		case inDownButton:
			jump = 1;
			break;
		case inPageUp:
			jump = - pagesize;
			break;
		case inUpButton:
			jump = -1;
			break;
		default:
			jump = 0;
			break;
	}
	oldvalue = curvalue;
	curvalue = max(min(curvalue + jump, maxvalue), minvalue);
	SetCtlValue(control, curvalue);
	/* Calculate the actual jump and use it to adjust the text. */
	jump = curvalue - oldvalue;
	if (jump != 0)
	  TEScroll(0, - jump * (*scores_text)->lineHeight, scores_text);
}

/* Respond to an event occurring in the scores window. */

void
do_mouse_down_scores(Point mouse, int mods)
{
	ControlHandle control;
	short part, value;

	if (scores_vscroll_proc == NULL)
	  scores_vscroll_proc = NewControlActionProc(scores_vscroll_fn);

	part = FindControl(mouse, scoreswin, &control);
	if (control == scores_v_scrollbar) {
		if (part != 0) {
			switch (part) {
				case inPageDown:
				case inDownButton:
				case inPageUp:
				case inUpButton:
					value = TrackControl(control, mouse, scores_vscroll_proc);
					break;
				case inThumb:
					value = GetCtlValue(control);
					if ((part = TrackControl(control, mouse, nil)) != 0) {
						value -= GetCtlValue(control);
						if (value != 0) {
							TEScroll(0, value * (*scores_text)->lineHeight, scores_text);
						}
					}
					break;
			}
		}
	} else if (PtInRect(mouse, &((*scores_text)->viewRect))) {
		TEClick(mouse, 0, scores_text);
	}
}

void
grow_scores(int h, int v)
{
	EraseRect(&scoreswin->portRect);
	SizeWindow(scoreswin, h, v, 1);
	MoveControl(scores_v_scrollbar, h - floatsbarwid, -1);
	SizeControl(scores_v_scrollbar, floatsbarwid + 1, v + 1 - floatsbarwid + 1);
	(*scores_text)->viewRect.right = h - floatsbarwid;
	(*scores_text)->viewRect.bottom = v;
	(*scores_text)->destRect.right = h - floatsbarwid;
	TECalText(scores_text);
	InvalRect(&scoreswin->portRect);
}					

void
zoom_scores(int part)
{
	int titleh, h, v;
	Rect zoomrect;
	GDHandle gd, zoomgd;

	EraseRect(&scoreswin->portRect);
	if (part == inZoomOut) {
		if (hasColorQD) {
			zoomgd = best_zoom_screen(&scoreswin->portRect);
			zoomrect = (*zoomgd)->gdRect;
			if (zoomgd == GetMainDevice()) {
				zoomrect.top += GetMBarHeight();
			}
			InsetRect(&zoomrect, 3, 3);
		} else {
			/* If no Color QD, then there is only the one screen. */
			zoomrect = QD(screenBits).bounds;
			zoomrect.top += GetMBarHeight();
			InsetRect(&zoomrect, 4, 4);
		}
		titleh = 20; /* (should calc) */
		zoomrect.top += titleh;
		(*((WStateDataHandle) ((WindowPeek) scoreswin)->dataHandle))->stdState = zoomrect;
	}
	ZoomWindow(scoreswin, part, false);
	h = window_width(scoreswin);  v = window_height(scoreswin);
	MoveControl(scores_v_scrollbar, h - floatsbarwid, -1);
	SizeControl(scores_v_scrollbar, floatsbarwid + 1, v + 1 - floatsbarwid + 1);
	adjust_scores_scrollbar();
	(*scores_text)->viewRect.right = h - floatsbarwid;
	(*scores_text)->viewRect.bottom = v;
	(*scores_text)->destRect.right = h - floatsbarwid;
	TECalText(scores_text);
	/* This will force a full redraw at the next update. */
	InvalRect(&scoreswin->portRect);
}
