/* 	Mouse-driven user interaction for the Mac interface to Xconq.
	Copyright (C) 1992-1998 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.  */

/* This file now ONLY contains user interaction code for the mouse
   such as clicking, dragging, selecting, scrolling and toggling items. */
     
#include "conq.h"
#include "kpublic.h"
#include "macconq.h"

extern int side_owns_occupant PARAMS ((Side *side, Unit *unit));

extern void set_position_modally(void);
extern void city_dialog(Map *map, Unit *unit);
extern void do_closeup_mi(void);

static int selrect;
static int downx, downy, downdir;
static void drag_for_distance(Map *map, int h0, int v0);

int topunithgt;

Map *curmap;

ControlActionUPP map_scroll_proc;

/* This scroll proc is shared by both the horizontal and vertical scrollbars. */

static pascal void
map_scroll_fn(ControlHandle control, short code)
{
	int pagesize, jump;

	/* The page jump should be most but not all of a screenful. */
	if (control == curmap->hscrollbar)
	  pagesize = (3 * curmap->vp->pxw) / 4;
	else
	  pagesize = (3 * curmap->vp->pxh) / 4;
	/* Adjust the pagesize to always be a multiple of 4. */
	pagesize = ((pagesize + 3) / 4) * 4;

	switch (code) {
		case inPageDown:
			jump = pagesize;
			break;
		case inDownButton:
			jump = 4;
			break;
		case inPageUp:
			jump = 0 - pagesize;
			break;
		case inUpButton:
			jump = -4;
			break;
		default:
			jump = 0;
			break;
	}
	if (control == curmap->hscrollbar) {
		scroll_map_window(curmap, jump, 0);
	} else if (control == curmap->vscrollbar){
		scroll_map_window(curmap, 0, jump);
	}
}

void
scroll_best_map_to_unit(Unit *unit, int bringtofront)
{
	Map *map, *bestmap;
	GrafPtr	oldport;
	
	/* Find the "best" (highest power, unit already visible) map to scroll over
	   to the unit. */
	bestmap = maplist;
	for_all_maps(map) {
		if (map->vp->power > bestmap->vp->power
		    || (map->vp->power == bestmap->vp->power
		        && in_middle(map, unit->x, unit->y)
			 	&& !in_middle(bestmap, unit->x, unit->y))) {
			bestmap = map;
		}
	}
	/* We have a map, now make it show the unit. */
	if (!in_middle(bestmap, unit->x, unit->y)) {
		/* Save the current port. */
		GetPort(&oldport);
		/* Important or junk may appear in the list window. */
		SetPort(bestmap->window);
		scroll_to_unit(bestmap, unit);
		/* Force a gworld update of the whole map content to
		   make sure the scrolled region is updated properly. */
		copy_from_gworld(bestmap, bestmap->contentrect);
		SetPort(oldport);
	}
	if (bringtofront) {
		SelectTheWindow(bestmap->window);
		update_window(bestmap->window);
		adjust_menus();
	}
}

/* Scroll the given map over to display the given unit. */

void
scroll_to_unit(Map *map, Unit *unit)
{
	int oldsx = map->vp->sx, oldsy = map->vp->sy;
	int	newsx, newsy;
	
	/* Return if unit is not at all on the map */
	if (!inside_area(unit->x, unit->y))
	  return;

	/* Dont scroll if the unit already is in the window */
	if (in_middle(map, unit->x, unit->y))
	  return;

	/* Find new vp */
	set_view_focus(map->vp, unit->x, unit->y);
	m_center_on_focus(map);
	newsx = map->vp->sx;
	newsy = map->vp->sy;

	/* Return if position is unchanged */
	if (oldsx == newsx && oldsy == newsy)
	  return;
	
	/* Restore old vp and let scroll_map_window do the job */
	map->vp->sx = oldsx;
	map->vp->sy = oldsy;
	
	scroll_map_window(map, newsx - oldsx, newsy - oldsy);
}

/* Handle a mouse down in the map window. */

void
do_mouse_down_map(Map *map, Point mouse, int mods)
{
	short part;
	int newsx, newsy, dummy;
	ControlHandle control;

	if (map_scroll_proc == NULL) 
	  map_scroll_proc = NewControlActionProc(map_scroll_fn);
	part = FindControl(mouse, map->window, &control);

	/* Horizontal scrollbar */
	if (control == map->hscrollbar) {
		switch (part) {
			case inThumb:
				part = TrackControl(control, mouse, NULL);
				if (part == inThumb) {
					/* Get apparent newsx from the new thumb position */
					newsx = GetCtlValue(control);
					/* Correct newsx for scrollbar scaling used to fix the 32K limit bug */				
					dummy = map->vp->sxmax;
					while (dummy > 32000) {	
						dummy /= 2;
						newsx *= 2;	/* Simulates the scaling but in reverse */
					}		
					/* Scroll the map window within its gworld */
					scroll_map_window(map, newsx - map->vp->sx, 0);
				}
				break;
			default:
				curmap = map;
				part = TrackControl(control, mouse, map_scroll_proc);
				break;
		}

	/* Vertical scrollbar */
	} else if (control == map->vscrollbar) {
		switch (part) {
			case inThumb:
				part = TrackControl(control, mouse, NULL);
				if (part == inThumb) {
					/* Get apparent newsy from the new thumb position */
					newsy = GetCtlValue(control);
					/* Correct newsy for scrollbar scaling used to fix the 32K limit bug */				
					dummy = map->vp->symax;
					while (dummy > 32000) {	
						dummy /= 2;
						newsy *= 2;	/* Simulates the scaling but in reverse */
					}		
					/* Scroll the map window within its gworld */
					scroll_map_window(map, 0, newsy - map->vp->sy);
				}
				break;
			default:
				curmap = map;
				part = TrackControl(control, mouse, map_scroll_proc);
				break;
		}
	} else if (mouse.h <= map->conw) {
		/* Interpret as a control panel hit. */
		do_mouse_down_map_control_panel(map, mouse.h, mouse.v, mods);
	} else {
		do_mouse_down_map_content(map, mouse.h, mouse.v, mods);
	}
}

void
do_mouse_down_map_control_panel(Map *map, int h, int v, int mods)
{
	int winh = map->window->portRect.bottom - map->window->portRect.top;

	/* (should better organize tests here) */

	/* Use 15 instead of map->sbarwid here, since the latter may be zero. */
	if (between(winh - 2 * 15, v, winh)) {
		switch ((winh - v) / 15) {
			case 0:
				magnify_map(map, ((h < map->conw / 2) ? -1 : 1));
				break;
#if 0
			/* This was too confusing here, so it's now flushed.  However, the
			   capability still seems worthwhile, so it should reappear elsewhere. */
			case 1:
				map_modal = ZOOM_MODAL;
				break;
#endif
		}
	} else if (v < 32) {
		toggle_survey(map);
	} else if ((v - 32) < 5 * 15) {
		switch ((v - 32) / 15) {
			case 0:
				if (h < map->conw / 2) {
					select_previous_awake_mover(map);
				} else {
					select_next_awake_mover(map);
				}
				break;
			case 1:
				if (h < map->conw / 2) {
					select_previous_mover(map);
				} else {
					select_next_mover(map);
				}
				break;
			case 2:
				if (h < map->conw / 2) {
					select_previous_actor(map);
				} else {
					select_next_actor(map);
				}
				break;
			case 3:
				if (h < map->conw / 2) {
					select_previous_unit(map);
				} else {
					select_next_unit(map);
				}
				break;
			case 4:
				beep();
				break;
		}

	} else if (v - 32 - 5*15 - 2 - 7/*why?*/ < 9 * 11) {
		switch ((v - 32 - 5*15 - 2 - 7/*why?*/) / 11) {

			case 0:
				toggle_map_grid(map);
				break;
			case 1:
				map->vp->draw_names = !map->vp->draw_names;
				/* Also toggle featurenames if it had the same old setting as drawnames */
				/* but let it alone if the setting already was different */
				if (map->featurenames != map->vp->draw_names)
			 	      map->featurenames = !map->featurenames;
				force_map_update(map);
				break;
			case 2:
				toggle_map_people(map);
				break;
			case 3:
				toggle_map_control(map);
				break;
			case 4:
				toggle_map_plans(map);
				break;
			case 5:
				toggle_map_ai(map);
				break;
			case 6:
				if (dside->may_set_see_all) {
					map->see_all = !map->see_all;
					force_map_update(map);
				}
				break;
			case 7:
				toggle_map_sidecolors(map);
				break;
			case 8:
				map->iconmasks = !map->iconmasks;
				/* Also toggle boxmasks if it had the same old setting as iconmasks */
				/* but let it alone if the setting already was different */
				if (map->boxmasks != map->iconmasks)
				      map->boxmasks = !map->boxmasks;
				/* Also toggle textmasks if it had the same old setting as iconmasks */
				/* but let it alone if the setting already was different */
				if (map->textmasks != map->iconmasks)
				      map->textmasks = !map->textmasks;
				force_map_update(map);
				break;
		}
	} else {
		/* Unused area, ignore */
	}
}

void
toggle_survey(Map *map)
{
	int i;
	Unit *unit;

	map->moveonclick = !map->moveonclick;
	map->autoselect = !map->autoselect;
	draw_control_panel(map);
	if (map->autoselect) {
		if (map->numselections > 0) {
			for (i = 0; i < map->numselections; ++i) {
				unit = map->selections[i];
				if (unit != NULL) {
					map->curunit = autonext_unit(dside, unit);
					select_exactly_one_unit(map, map->curunit);
				}
			}
		}
	} else {
		/* Update appearance of selections. */
		draw_selections(map);
	}
}

void
magnify_map(Map *map, int inout)
{
	set_map_mag(map, map->vp->power + inout);
}

/* This sets the map's magnification directly and updates it. */

void
set_map_mag(Map *map, int newpower)
{
	newpower = clip_to_limits(0, newpower, NUMPOWERS-1);
	if (map->vp->power != newpower) {
	
		/* Erase other-map boxes in other windows */
		draw_related_maps(map);
		
		set_map_power(map, newpower);
		m_center_on_focus(map);
		set_map_scrollbars(map);

		/* Update the entire map */
		force_map_update(map);

		/* Redraw other-map boxes in other windows */
		draw_related_maps(map);
	}
}

void
toggle_map_grid(Map *map)
{
	map->vp->draw_grid = !map->vp->draw_grid;
	/* (should not do a total redraw?) */
	force_map_update(map);
}

void
toggle_map_topline(Map *map)
{
	int oldtoph = map->toph;
	GrafPtr oldport;

	/* Setting the port fixed a display bug. */
	GetPort(&oldport);
	SetPort(map->window);
	map->draw_topline = !map->draw_topline;
	map->toplineh = (map->draw_topline ? tophgt : 0);
	/* Erase other-map boxes in other windows. */
	draw_related_maps(map);
	map->toph = map->toplineh + map->topunith;

#if 1

	/* This is a temp fix. It should be possible to get the code below to work! */
	force_map_update(map);

#else

	/* Update without scrolling if we are expanding and the buffer is too small. */ 
	if (map->bufy - map->offsety < oldtoph - map->toph) {
		set_content_rect(map);
		update_gworld(map);
		/* This is not done by update_gworld (unlike update_resized_gworld). */
		if (map->toplineh > 0)
		  draw_top_line(map);
		if (map->topunith > 0)
		  draw_unit_info(map);
	/* Always scroll the map if we are shrinking, or if the buffer is big enough. */
	} else update_resized_map(map);

#endif

	/* Redraw other-map boxes in other windows. */
	draw_related_maps(map);
	SetPort(oldport);
}

void
toggle_map_topunit(Map *map)
{
	GrafPtr oldport;
	int oldtoph;

	/* Setting the port fixed a display bug. */
	GetPort(&oldport);
	SetPort(map->window);
	oldtoph = map->toph;
	map->draw_topunit = !map->draw_topunit;
	map->topunith = (map->draw_topunit ? topunithgt : 0);
	/* Erase other-map boxes in other windows. */
	draw_related_maps(map);
	map->toph = map->toplineh + map->topunith;
	/* Update without scrolling if we are expanding and the buffer is too small. */ 
	if (map->bufy - map->offsety < oldtoph - map->toph) {
		set_content_rect(map);
		update_gworld(map);
		/* This is not done by update_gworld (unlike update_resized_gworld). */
		if (map->toplineh > 0)
		  draw_top_line(map);
		if (map->topunith > 0)
		  draw_unit_info(map);
	/* Always scroll the map if we are shrinking, or if the buffer is big enough. */
	} else update_resized_map(map);
	/* Redraw other-map boxes in other windows. */
	draw_related_maps(map);
	SetPort(oldport);
}

void
toggle_map_scrollbars(Map *map)
{
	/* Erase other-map boxes in other windows. */
	draw_related_maps(map);

	if (map->sbarwid)
		map->sbarwid = 0;
	else if (map == worldmap)
		map->sbarwid = floatsbarwid;
	else	map->sbarwid = sbarwid;
	
	adjust_map_decor(map);
	force_map_update(map);

	/* Redraw other-map boxes in other windows. */
	draw_related_maps(map);
}

void
toggle_map_cpanel(Map *map)
{
	/* Erase other-map boxes in other windows. */
	draw_related_maps(map);

	/* Toggle the panel. */
	map->conw = (map->conw > 0 ? 0 : conwid);
	adjust_map_decor(map);
	force_map_update(map);

	/* Redraw other-map boxes in other windows. */
	draw_related_maps(map);
}

void
toggle_map_other_maps(Map *map)
{
	map->vp->draw_other_maps = !map->vp->draw_other_maps;
	/* (should not do a total redraw) */
	force_map_update(map);
}

void
toggle_map_lighting(Map *map)
{
	map->vp->draw_lighting = !map->vp->draw_lighting;
	/* We have to do a total redraw. */
	force_map_update(map);
}

void
toggle_map_coverage(Map *map)
{
	map->vp->draw_cover = !map->vp->draw_cover;
	/* (should only change newly dimmed/lightened cells) */
	force_map_update(map);
}

void
toggle_map_names(Map *map)
{
	map->vp->draw_names = !map->vp->draw_names;
	/* (if now on, should draw names on top of everything, don't redraw everything) */
	if (map->vp->hh > 5) {
		force_map_update(map);
	} else {
		/* (should be a force update on control panel alone) */
		draw_control_panel(map);
	}
}

void
toggle_map_people(Map *map)
{
    if (!people_sides_defined())
      return;
	map->vp->draw_people = !map->vp->draw_people;
	if (bwid2[map->vp->power] > 0) {
		force_map_update(map);
	} else {
		/* (should be a force update on control panel alone) */
		draw_control_panel(map);
	}
}

void
toggle_map_control(Map *map)
{
    if (!control_sides_defined())
      return;
	map->vp->draw_control = !map->vp->draw_control;
	if (bwid2[map->vp->power] > 0) {
		force_map_update(map);
	} else {
		/* (should be a force update on control panel alone) */
		draw_control_panel(map);
	}
}

void
toggle_map_elevations(Map *map)
{
	map->vp->draw_elevations = !map->vp->draw_elevations;
	force_map_update(map);
}

void
toggle_map_materials(Map *map, int m)
{
	map->vp->draw_materials[m] = !map->vp->draw_materials[m];
	map->vp->num_materials_to_draw += (map->vp->draw_materials[m] ? 1 : -1);
	force_map_update(map);
}

void
toggle_map_aux_terrain(Map *map, int t)
{
	map->vp->draw_aux_terrain[t] = !map->vp->draw_aux_terrain[t];
	force_map_update(map);
}

void
toggle_map_temperature(Map *map)
{
	map->vp->draw_temperature = !map->vp->draw_temperature;
	force_map_update(map);
}

void
toggle_map_winds(Map *map)
{
	map->vp->draw_winds = !map->vp->draw_winds;
	force_map_update(map);
}

void
toggle_map_clouds(Map *map)
{
	map->vp->draw_clouds = !map->vp->draw_clouds;
	force_map_update(map);
}

void
toggle_map_storms(Map *map)
{
	map->vp->draw_storms = !map->vp->draw_storms;
	force_map_update(map);
}

void
toggle_map_plans(Map *map)
{
	map->vp->draw_plans = !map->vp->draw_plans;

	force_map_update(map);
}

void
toggle_map_ai(Map *map)
{
	if (!side_has_ai(dside))
	  return;
	map->vp->draw_ai = !map->vp->draw_ai;
	force_map_update(map);
}

void
toggle_map_sidecolors(Map *map)
{
	map->sidecolors = !map->sidecolors;
	force_map_update(map);
}

void
toggle_map_draw_emblems(Map *map)
{
	map->draw_emblems = !map->draw_emblems;
	force_map_update(map);
}

void
toggle_iconmasks(Map *map)
{
	map->iconmasks = !map->iconmasks;
	force_map_update(map);
}

void
toggle_boxmasks(Map *map)
{
	map->boxmasks = !map->boxmasks;
	force_map_update(map);
}

void
toggle_textmasks(Map *map)
{
	map->textmasks = !map->textmasks;
	force_map_update(map);
}

void
toggle_featureborders(Map *map)
{
	map->featureborders = !map->featureborders;
	force_map_update(map);
}

void
toggle_featurenames(Map *map)
{
	map->featurenames = !map->featurenames;
	force_map_update(map);
}

void
toggle_shorelines(Map *map)
{
	map->shorelines = !map->shorelines;
	force_map_update(map);
}

void
toggle_simple_borders(Map *map)
{
	map->simple_borders = !map->simple_borders;
	force_map_update(map);
}

void
toggle_optimize_fonts(Map *map)
{
	map->optimize_fonts = !map->optimize_fonts;
	force_map_update(map);
}

void
toggle_draw_latlong(Map *map)
{
	map->vp->draw_latlong = !map->vp->draw_latlong;
	force_map_update(map);
}

void
toggle_solid_color_terrain(Map *map)
{
	map->solid_color_terrain = !map->solid_color_terrain;
	force_map_update(map);
}

void
toggle_erase_names(Map *map)
{
	map->erase_names = !map->erase_names;
	force_map_update(map);
}

void
do_mouse_down_map_content(Map *map, int h, int v, int mods)
{
	int i, rslt, anysuccess;
	Unit *unit;
	
	/* Remember this cell. */
	m_nearest_cell(map, h, v, &downx, &downy);
	/* Assume that last place clicked is a reasonable focus. */
	if (inside_area(downx, downy)) {
		map->vp->vcx = downx;  map->vp->vcy = downy;
	}
	/* First handle refocus clicks in world map. */
	if (map == worldmap) {
		WindowPtr	 win;
		Map *nextmap;

		/* Find the next frontmost map. */
		for_all_windows(win) {
			if (map_from_window(win) != NULL
			     && win != FrontWindow()) {
			       	nextmap = map_from_window(win);
				/* Scroll next map to the new focus. */
				set_map_focus(nextmap, downx, downy);		
				break;
			}
		}
		return;
	}		
	if (map_modal != NO_MODAL) {
		switch (map_modal) {
			case ATTACK_MODAL:
				set_position_modally();
				do_attack(dside);
				break;
			case FIRE_MODAL:
				set_position_modally();
				do_fire(dside);
				break;
			case FIRE_INTO_MODAL:
				set_position_modally();
				do_fire_into(dside);
				break;
			case SET_FORMATION_MODAL:
				set_position_modally();
				do_set_formation(dside);
				break;
			case MOVE_TO_MODAL:
				set_position_modally();
				do_move_to_command();
				break;
			case ADD_TERRAIN_MODAL:
				set_position_modally();
				do_add_terrain(dside);
				break;
			case REMOVE_TERRAIN_MODAL:
				set_position_modally();
				do_remove_terrain(dside);
				break;
			case DISTANCE_MODAL:
				drag_for_distance(map, h, v);
				break;
			case ZOOM_MODAL:
				select_area_and_zoom(map, h, v, mods);
				break;
			case GENERIC_MODAL:
				set_position_modally();
				break;
			default:
				run_error("unknown modal tool %d", map_modal);
				break;
		}
		/* Reset modality whether or not the command succeeded, otherwise
		   the player can get caught here if no commands can succeed. */
		map_modal = NO_MODAL;
	} else if (mods & cmdKey) {
		if (map->moveonclick && map->autoselect) {
			unselect_all(map);
			m_nearest_unit(map, h, v, &unit);
			/* Always select the unit if it exists and we may examine it. */
			if (side_sees_unit(dside, unit)) {
				map->curunit = unit;
				select_unit_on_map(map, unit);
				draw_selections(map);
				/* But only move on drag if we control it. */
				if (side_controls_unit(dside, unit))
					move_on_drag(map, unit, mods);
			} else {
				select_all_dragged_over(map, h, v, mods);
				/* Pick the first of the multiple selection as the "current unit". */
				if (map->numselections > 0) {
					map->curunit = map->selections[0];
				}
			}
		} else {
			anysuccess = FALSE;
			for (i = 0; i < map->numselections; ++i) {
				/* Only move those selected units that we control. */ 
				unit = map->selections[i];
				if (side_controls_unit(dside, unit)) {
					rslt = move_the_selected_unit(map, unit, h, v);
					if (rslt)
					  anysuccess = TRUE;
				}
			}
			if (!anysuccess)
			  beep();
		}
	} else if (mods & optionKey) {
		for (i = 0; i < map->numselections; ++i) {
			/* Only fire those selected units that we control. */ 
			unit = map->selections[i];
			if (side_controls_unit(dside, unit)) {
				fire_the_selected_unit(map, unit, h, v);
			}
		}
	} else if (mods & shiftKey) {
		m_nearest_unit(map, h, v, &unit);
		if (unit && side_sees_unit(dside, unit)) {
			/* Invert the selection status of the unit. */
			if (unit_is_selected(map, unit)) {
				unselect_unit_on_map(map, unit);
				update_cell(map, unit->x, unit->y);
			} else {
				select_unit_on_map(map, unit);
				draw_selections_at(map, unit->x, unit->y);
			}
			draw_unit_info(map);
		} else {
			select_all_dragged_over(map, h, v, mods);
		}
	} else if (mods & controlKey) {
		m_nearest_unit(map, h, v, &unit);
		/* Open city dialog or closeup if clicking on a unit that we may examine. */
		if (side_sees_unit(dside, unit)) {

			UnitCloseup *unitcloseup;

			if (u_advanced(unit->type)) {
				city_dialog(map, unit);
			} else { 	
				if ((unitcloseup = find_unit_closeup(unit)) != NULL) {
					SelectTheWindow(unitcloseup->window);
				} else {
					create_unit_closeup(unit);
				}
			}
		/* Else focus on clicked spot and select closest own unit. */
		} else {
			long	unitdist, mindist = 32000;

			/* Focus on the clicked point. */
			set_map_focus(map, downx, downy);
			for_all_side_units(dside, unit) {
   				if (side_controls_unit(dside, unit)) {
					/* Use least square method. */
					unitdist = (downx - unit->x) * (downx - unit->x) + 
					        	        (downy - unit->y) * (downy - unit->y);
					if (unitdist < mindist) {
						mindist = unitdist;
						map->curunit = unit;
					}
		    		}
		    	}
			if (map->curunit) {
				unselect_all(map);
				select_unit_on_map(map, map->curunit);
				draw_selections(map);
			}
		}
	} else {
		/* Interpret an unmodified mouse down. */
#ifdef DESIGNERS
		if (is_designer(dside) && tooltype != notool) {
			apply_designer_tool(map, h, v, mods);
		} else
#endif /* DESIGNERS */
		if (map->moveonclick && !is_designer(dside)) {
			/* Usually will only be one to move, but be general anyway. */
			anysuccess = FALSE;
			for (i = 0; i < map->numselections; ++i) {
				/* Only move those selected units that we control. */ 
				unit = map->selections[i];
				if (side_controls_unit(dside, unit)) {
					rslt = move_the_selected_unit(map, unit, h, v);
					if (rslt)
					  anysuccess = TRUE;
				}
			}
			if (!anysuccess)
			  beep();
			map->scrolltocurunit = TRUE;
		} else {
			unselect_all(map);
			m_nearest_unit(map, h, v, &unit);
			/* Always select the unit if it exists and we may examine it. */
			if (side_sees_unit(dside, unit)) {
				map->curunit = unit;
				select_unit_on_map(map, unit);
				draw_selections(map);
				/* But only move on drag if we control it. */
				if (side_controls_unit(dside, unit))
					move_on_drag(map, unit, mods);
			} else {
				select_all_dragged_over(map, h, v, mods);
				/* Pick the first of the selection as the current unit. */
				if (map->numselections > 0)
					map->curunit = map->selections[0];
			}
		}
	}
}

void
select_all_dragged_over(Map *map, int h0, int v0, int mods)
{
	Point pt0, pt1, newmouse;
	int drawn = FALSE;
	Rect tmprect;

	SetPt(&pt0, h0, v0);
	SetPt(&pt1, h0, v0);
	SetRect(&tmprect, h0, v0, h0, v0);
	/* (should be a generic subr?) */
	PenMode(patXor);
	PenPat(QDPat(gray));
	while (WaitMouseUp()) {
		GetMouse(&newmouse);
		if (!EqualPt(pt1, newmouse) /* && PtInRect(newmouse, &(map->window->portRect)) */) {
			if (drawn) {
				tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
				tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
				FrameRect(&tmprect);
			}
			pt1 = newmouse;
			tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
			tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
			FrameRect(&tmprect);
			drawn = TRUE;
		}
	}
	if (drawn) {
		tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
		tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
		FrameRect(&tmprect);
	}
	PenNormal();
	select_all_units_in_rect(map, &tmprect);
}

void
select_area_and_zoom(Map *map, int h0, int v0, int mods)
{
	Point pt0, pt1, newmouse;
	int drawn = FALSE, x, y;
	Rect tmprect;

	SetPt(&pt0, h0, v0);
	SetPt(&pt1, h0, v0);
	/* (should be a generic subr) */
	PenMode(patXor);
	PenPat(QDPat(gray));
	while (WaitMouseUp()) {
		GetMouse(&newmouse);
		if (!EqualPt(pt1, newmouse) /* && PtInRect(newmouse, &(map->window->portRect)) */) {
			if (drawn) {
				tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
				tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
				FrameRect(&tmprect);
			}
			pt1 = newmouse;
			tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
			tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
			FrameRect(&tmprect);
			drawn = TRUE;
		}
	}
	if (drawn) {
		tmprect.left = min(pt0.h, pt1.h);  tmprect.top = min(pt0.v, pt1.v);
		tmprect.right = max(pt0.h, pt1.h);  tmprect.bottom = max(pt0.v, pt1.v);
		FrameRect(&tmprect);
	}
	PenNormal();
	m_nearest_cell(map, pt1.h, pt1.v, &x, &y);
	if (x != downx && y != downy) {
		magnify_to_fit(map, downx, downy, x, y);
	}
}

/* This routine changes a map's viewport and magnification to fit the given rectangle. */

void
magnify_to_fit(Map *map, int x1, int y1, int x2, int y2)
{
	int wid, hgt, wanted, power;

	DGprintf("Magnifying map to fit in area %d,%d - %d,%d\n", x1, y1, x2, y2);
	/* (still need to do y/2 correction) */
	wid = abs(x2 - x1) + 1;  hgt = abs(y2 - y1) + 1;
	map->vp->vcx = min(x1, x2) + wid / 2;  map->vp->vcy = min(y1, y2) + hgt / 2;
	/* Compute the "ideal" size of a displayed cell. */
	wanted = min(map->vp->pxw / wid, map->vp->pxh / hgt);
	/* Search for the best approximation. */
	for (power = NUMPOWERS-1; power > 0; --power) {
		if (hws[power] < wanted) break;
	}
	set_map_mag(map, power);
}

void
move_on_drag(Map *map, Unit *unit, int mods)
{
	int sx, sy, sw, sh, h0, v0, drawn = FALSE, x, y;
	Point pt0, pt1, newmouse;

	m_xform_unit_self(map, unit, &sx, &sy, &sw, &sh);
	h0 = sx + sw / 2;  v0 = sy + sh / 2;
	SetPt(&pt0, h0, v0);
	SetPt(&pt1, h0, v0);
	/* (should be a generic subr?) */
	PenMode(patXor);
	while (WaitMouseUp()) {
		GetMouse(&newmouse);
		/* should scroll, then abort if we drag outside the window */
		if (0 /* PtInRect(newmouse, &(map->window->portRect)) */) {
		}
		if (!EqualPt(pt1, newmouse)) {
			if (drawn) {
				MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
			}
			pt1 = newmouse;
			MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
			drawn = TRUE;
		}
	}
	/* Erase the last drawn line. */
	if (drawn) {
		MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
	}
	PenNormal();
	m_nearest_cell(map, pt1.h, pt1.v, &x, &y);
	if (x != downx || y != downy) {
		if (!move_the_selected_unit(map, unit, pt1.h, pt1.v))
		  beep();
	} else {
		/* (should try to enter another unit in this cell) */
	}
}

void
drag_for_distance(Map *map, int h0, int v0)
{
	Point pt0, pt1, newmouse;
	int drawn = FALSE, x, y;

	SetPt(&pt0, h0, v0);
	SetPt(&pt1, h0, v0);
	/* (should be a generic subr) */
	PenMode(patXor);
	PenPat(QDPat(gray));
	while (WaitMouseUp()) {
		GetMouse(&newmouse);
		/* should scroll, then abort if we drag outside the window */
		if (0 /* PtInRect(newmouse, &(map->window->portRect)) */) {
		}
		if (!EqualPt(pt1, newmouse)) {
			if (drawn) {
				MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
			}
			pt1 = newmouse;
			MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
			drawn = TRUE;
		}
	}
	/* Erase the last drawn line. */
	if (drawn) {
		MoveTo(h0, v0);  LineTo(pt1.h, pt1.v);
	}
	PenNormal();
	m_nearest_cell(map, pt1.h, pt1.v, &x, &y);
	notify(dside, "Distance from %d,%d to %d,%d is %d",
		   downx, downy, x, y, distance(downx, downy, x, y));
}

void
unselect_all(Map *map)
{
	Unit *unit;

	while (map->numselections > 0) {
		/* Start from top of selection list. */
		unit = map->selections[map->numselections - 1]; 	
		/* Remove unit from selection list before updating its cell. */
		--map->numselections;
		update_cell(map, unit->x, unit->y);
	}
	draw_unit_info(map);
}

/* Add the given unit to the array of units selected in the given map.  If we need
   more space, then grow the array by 50%. */

void
select_unit_on_map(Map *map, Unit *unit)
{
	if (map->numselections >= map->maxselections) {
		int newsize = map->maxselections + map->maxselections / 2;
		Unit **newarray = (Unit **) realloc((char *) map->selections, newsize * sizeof(Unit *));

		if (newarray == NULL) {
			run_warning("couldn't realloc map selection array");
			return;
		}
		map->maxselections = newsize;
		map->selections = newarray;
	}
	map->selections[map->numselections++] = unit;
}

int
unit_is_selected(Map *map, Unit *unit)
{
	int i;

	for (i = 0; i < map->numselections; ++i) {
		if (map->selections[i] == unit)
		  return TRUE;
	}
	return FALSE;
}

void
unselect_unit_on_map(Map *map, Unit *unit)
{
	int i, j;

	for (i = 0; i < map->numselections; ++i) {
		if (map->selections[i] == unit) {
			/* Keep selection list contiguous, move other units down. */
			for (j = i + 1; j < map->numselections; ++j) {
				map->selections[j - 1] = map->selections[j];
			}
			--map->numselections;
			draw_unit_info(map);
			return;
		}
	}
}

/* Given a map and a rectangle in it, select all the units whose images touch on
   that rectangle. */

void
select_all_units_in_rect(Map *map, Rect *rectptr)
{
	int rectissmall = FALSE;
	int sx, sy, sw, sh;
	Unit *unit;
	Rect unitrect, tmprect;
	
	/* First see if we're selecting over a large area or within a single cell. */
	if (rectptr->right - rectptr->left < map->vp->hw
		&& rectptr->bottom - rectptr->top < map->vp->hh) rectissmall = TRUE;
	/* Now look at all the plausible units and see if any's image intersects the rect. */
	for_all_units(unit) {
		if (in_play(unit)
		     && side_sees_unit(dside, unit)
		     && (rectissmall || unit->transport == NULL)) {
			m_xform_unit_self(map, unit, &sx, &sy, &sw, &sh);
			SetRect(&unitrect, sx, sy, sx + sw, sy + sh);
			if (SectRect(&unitrect, rectptr, &tmprect)) {
				select_unit_on_map(map, unit);
			}
		}
	}
	draw_selections(map);
}

/* This translates the user's "go to here" into appropriate tasks and/or actions. */

int
move_the_selected_unit(Map *map, Unit *unit, int h, int v)
{
	int x, y, rslt;
	Unit *other = NULL;

	m_nearest_cell(map, h, v, &x, &y);
	if (unit_at(x, y) != NULL) {
		m_nearest_unit(map, h, v, &other);
	}
	rslt = advance_into_cell(dside, unit, x, y, other);
	return rslt;
}

void
fire_the_selected_unit(Map *map, Unit *unit, int h, int v)
{
	int x, y;
	Unit *other;

	m_nearest_cell(map, h, v, &x, &y);
	if (x != unit->x || y != unit->y) {
		if (unit->act && unit->plan) { /* (should be more sophisticated test?) */
			if ((other = unit_at(x, y)) != NULL) {
				/* There's a unit to fire at. */
				if (other->side == unit->side) {
					beep();
				} else {
					net_prep_fire_at_action(unit, unit, other, -1);
				}
			} else {
				beep();
			}
		}
	}
}

void
select_exactly_one_unit(Map *map, Unit *unit)
{
    Unit *thisunit;

	if (map->numselections > 0) {		
		thisunit = map->selections[0];
		if (thisunit == unit) return;
	}
	unselect_all(map);
	select_unit_on_map(map, unit);
	scroll_to_unit(map, unit);
	draw_selections(map);
}

void
select_next_unit(Map *map)
{
	select_another(map, find_next_unit);
}

void
select_previous_unit(Map *map)
{
	select_another(map, find_prev_unit);
}

void
select_next_actor(Map *map)
{
	select_another(map, find_next_actor);
}

void
select_previous_actor(Map *map)
{
	select_another(map, find_prev_actor);
}

void
select_next_mover(Map *map)
{
	select_another(map, find_next_mover);
}

void
select_previous_mover(Map *map)
{
	select_another(map, find_prev_mover);
}

void
select_next_awake_mover(Map *map)
{
	select_another(map, find_next_awake_mover);
}

void
select_previous_awake_mover(Map *map)
{
	select_another(map, find_prev_awake_mover);
}

/* Given a map and a searching function, go find the "next" matching unit and select it. */

void
select_another(Map *map, Unit *(*fn)(Side *side, Unit *unit))
{
    Unit *thisunit, *nextunit;

	if (fn == NULL) {
		beep();
		return;
	}
	if (map->numselections > 0) {
		thisunit = map->selections[0];
	} else {
		thisunit = NULL;
	}
	nextunit = (*fn)(dside, thisunit);
	if (nextunit != NULL) {
		unselect_all(map);
		select_unit_on_map(map, nextunit);
		scroll_to_unit(map, nextunit);
		draw_selections(map);
		if (map->autoselect) {
			map->curunit = nextunit;
		}
	} else if (thisunit != NULL) {
		scroll_to_unit(map, thisunit);
		/* (should not be done this way, but how else?) */
		if (map->autoselect
			&& (thisunit->act && thisunit->act->acp > 0)
			&& (thisunit->plan && !thisunit->plan->asleep && !thisunit->plan->reserve && !thisunit->plan->delayed)) {
			map->curunit = thisunit;
		}
	}
}

