/* Offscreen graphics support for the Mac interface to Xconq.
   Copyright (C) 1997, 1998 Hans Ronne.

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"

/* Needed for changing screen depths and handling the control strip */

#include <Palettes.h>

#ifdef CONTROL_STRIP

/*	Apple control strip routine descriptors follow below. */
/*	Must be included in PPC projects that use the control strip. *
/*	See http://devworld.apple.com/dev/qa/ops/ops15.html. */

#if GENERATINGCFM

/* If we're not generating CFM, then assume the
   68K inlines in the headers apply instead. */

#include <MixedMode.h>
#include <OSUtils.h>

pascal Boolean SBIsControlStripVisible ( void );
pascal void SBShowHideControlStrip(Boolean showIt);

/*  SBIsControlStripVisible is a Pascal routine, */
/*  dispatched from the selector in D0, returning */
/*  a Boolean result */

pascal Boolean
SBIsControlStripVisible(void)
{
    enum
        {
        uppSBIsControlStripVisibleInfo = kD0DispatchedPascalStackBased
            | RESULT_SIZE (SIZE_CODE (sizeof(Boolean)))
            | DISPATCHED_STACK_ROUTINE_SELECTOR_SIZE (kFourByteCode)
            };

            return CallUniversalProc (
            GetToolTrapAddress (_ControlStripDispatch),
            uppSBIsControlStripVisibleInfo, 0x00);
}

pascal void
SBShowHideControlStrip(Boolean showIt)
{
    enum
        {
        uppSBShowHideControlStripInfo =
            kD0DispatchedPascalStackBased
            | DISPATCHED_STACK_ROUTINE_SELECTOR_SIZE (kFourByteCode)
            | DISPATCHED_STACK_ROUTINE_PARAMETER
                (1, SIZE_CODE (sizeof (showIt)))
            };

            CallUniversalProc (
            GetToolTrapAddress (_ControlStripDispatch),
            uppSBShowHideControlStripInfo, 0x01, showIt);
}

#else   /*  not GENERATINGCFM */
#include <ControlStrip.h>
#endif /*  GENERATINGCFM */

#endif /* CONTROL_STRIP */

static Rect debugRect = {0};

static int scrolling = FALSE;

static void reduce_screen_depth(GDHandle gdev);
static void copy_clipped_to_map(Map *map, Rect sourcerect, Rect destrect);
static void auto_scroll_left(Map *map);
static void auto_scroll_right(Map *map);
static void auto_scroll_up(Map *map);
static void auto_scroll_down(Map *map);
static void auto_scroll_down_left(Map *map);
static void auto_scroll_down_right(Map *map);
static void auto_scroll_up_left(Map *map);
static void auto_scroll_up_right(Map *map);
static void auto_scroll_left_arrow(Map *map);
static void auto_scroll_right_arrow(Map *map);
static void auto_scroll_up_arrow(Map *map);
static void auto_scroll_down_arrow(Map *map);
static void rotate_angle_up(Map *map);
static void rotate_angle_down(Map *map);

/* Check if any device has more than 256 colors */

void
check_screen_depths(void)
{
	int 		depth;
	GDHandle 	gdev;
	Boolean	ControlStripShown;

	if (!hasColorQD)
	  return;

#ifdef CONTROL_STRIP
/*	
	Make sure all control strips or extensions strips are shown before proceeding since
	changing the screen depth while a strip is hidden will move it 20 pixels downwards.
	This is a pain if you like to keep your strips hidden.
*/
	/* First save the users control strip visibility setting */
	ControlStripShown = SBIsControlStripVisible();
	
	/* Unhide the strip */
	SBShowHideControlStrip(TRUE);
#endif

	/* Now check the Device List and change screen depths if necessary */
	gdev = GetDeviceList();
	depth = (*((*gdev)->gdPMap))->pixelSize;
	if (depth > 8 && HasDepth(gdev, 8, 0, 0)) {
		reduce_screen_depth(gdev);
	}
	while ((gdev = GetNextDevice(gdev)) != nil) {
		depth = (*((*gdev)->gdPMap))->pixelSize;
		if (depth > 8 && HasDepth(gdev, 8, 0, 0)) {
			reduce_screen_depth(gdev);
		}
	}

#ifdef CONTROL_STRIP
	/* Restore the users control strip visibility setting */
	SBShowHideControlStrip(ControlStripShown);
#endif
}

/* Ask for permission to change depth of gdev to 8 bits (256 colors) */

void
reduce_screen_depth(GDHandle gdev)
{
	DialogPtr	 win;
	short 	done = FALSE;
	short 	ditem;

	win = GetNewDialog(dScreenDepth, NULL, (DialogPtr) -1);
	ShowWindow(win);
	while (!done) {

		SetCursor(&QD(arrow));
		draw_default_button(win, diScreenDepthOK);
		ModalDialog(NULL, &ditem);
		switch (ditem) {

			case diScreenDepthOK:
				SetDepth(gdev, 8, 0, 0);
				done = TRUE;
				break;

			case diScreenDepthCancel:
				done = TRUE;
				break;

			default:
				break;
		}
	}
	DisposeDialog(win);
}

/* Scrolls the map window dx, dy steps within its offscreen gworld. This function should always 
   be called whenever the viewport is moved. If necessary, get the new focus point, reset to the old
   focus, and then let scroll_map_window do the job. This ensures that the gworld always is used. */

void 
scroll_map_window(Map *map, int dx, int dy)
{
	int 		mainw, mainh, newsx, newsy, scaled_sx, scaled_sy, dummy, redraw = FALSE;
	Rect		destRect, mapRect;
	Point		mapUL, mapDR;
	GrafPtr	oldport;
	WindowPtr	win;

	GetPort(&oldport);
	/* Set current port to the map window. */
	SetPort((GrafPtr)map->windowPortPtr);

  	if (DebugG) {
		/* Erase the old debugRect. */
		ForeColor(redColor);
		PenMode(patXor);
		FrameRect(&debugRect);
		ForeColor(blackColor);
		PenMode(normal);
		scrolling = TRUE;	/* Turn off debugRect while scrolling. */
	}

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

	/* Find the new position. */
	newsx = map->vp->sx + dx;
	newsy = map->vp->sy + dy;
	
	if (area.xwrap) {	
		/* Handle xwrapped world. */
		while (newsx >= map->vp->sxmax)
		  newsx -= map->vp->sxmax;
		while (newsx <= 0)
		  newsx += map->vp->sxmax;
	} else {		
		/* Handle non-xwrapped world. Restrict newsx to stay between the map edges. */
		newsx = limitn(map->vp->sxmin, newsx, map->vp->sxmax);
		/* Adjust dx to reflect this restriction. */
		dx = newsx - map->vp->sx;
	}

	/* Scale the scrollbar to circumvent the 32K limit (see 
	set_map_scrollbars for details). */
	dummy = map->vp->sxmax;
	scaled_sx = newsx;
	while (dummy > 32000) {
		dummy /= 2;
		scaled_sx /= 2;
	}		
	/* Use the scaled value to set the new thumb position. */
	SetCtlValue(map->hscrollbar, scaled_sx);

	/* Restrict newsy to stay between the map edges. */
	newsy = limitn(map->vp->symin, newsy, map->vp->symax);
	/* Adjust dy to reflect this restriction. */
	dy = newsy - map->vp->sy;

	/* Scale the scrollbar to circumvent the 32K limit (see 
	set_map_scrollbars for details). */
	dummy = map->vp->symax;
	scaled_sy = newsy;
	while (dummy > 32000) {
		dummy /= 2;
		scaled_sy /= 2;
	}		
	/* Use the scaled value to set the new thumb position. */
	SetCtlValue(map->vscrollbar, scaled_sy);

	/* Move the viewport. */
	map->vp->sx = newsx;
	map->vp->sy = newsy;
	set_content_rect(map);
	
	/* Handle dx. Don't update if the map is wrapped. */
	if (map->xwrap) {
		redraw = FALSE; 
	} else if ((dx > 0 && map->offsetx + dx > map->bufx)
			   || (dx < 0 && map->offsetx + dx < -map->bufx)) {
		/* Handles dx in other cases.  Update if stepping outside. */
		redraw = TRUE;
	}		
	/* Always update if dy take us outside the gworld. */
	if ((dy > 0 && map->offsety + dy > map->bufy)
		|| (dy < 0 && map->offsety + dy < -map->bufy)) {
		redraw = TRUE;
	}		

	if (redraw) {
		/* Redraw gworld and copy contentrect to the window. */
		update_gworld(map);
		/* Redraw other-map boxes and restore port before exit. */
		draw_related_maps(map);
		SetPort(oldport);
		if (DebugG) {
			SetRect(&debugRect, 0, 0, 0, 0);		/* Prevents drawing of junk next time. */
			scrolling = FALSE;				/* Turn on debugRect after scrolling. */
		}
		return;
	}	   
	  
	/* Scroll the already drawn region. */
	ScrollRect(&map->contentrect, -dx, -dy, NULL);
	/* Update offset trackers. */
	map->offsetx += dx;
	map->offsety += dy;
	/* Wrap offsetx if necessary. */
	map->offsetx %= map->vp->sxmax;

	/* Find the corners of the map contentrect in global coordinates. */
	mapUL.h = map->contentrect.left;
	mapUL.v = map->contentrect.top;
	mapDR.h = map->contentrect.right;
	mapDR.v = map->contentrect.bottom;
	LocalToGlobal(&mapUL);			
	LocalToGlobal(&mapDR);			

	/* Clip it to main screen minus menubar to fix graphics drag at edges. */
	get_main_screen_size(&mainw, &mainh);
	mapUL.h = max(0, mapUL.h);
	mapUL.v = max(GetMBarHeight(), mapUL.v);
	mapDR.h = min(mapDR.h, mainw);
	mapDR.v = min(mapDR.v, mainh);
		
	/* Convert clipped contentrect back to local coordinates. */
	GlobalToLocal(&mapUL);
	GlobalToLocal(&mapDR);
	SetRect(&mapRect, mapUL.h, mapUL.v, mapDR.h, mapDR.v);

	/* Do horizontal scrolling. */
	destRect = mapRect;

	if (dx > 0) {							/* Going Right. */
		destRect.left = destRect.right - dx; 
		copy_from_gworld(map, destRect);
	} else if (dx < 0) {						/* Going Left. */
		destRect.right = destRect.left - dx; 
		copy_from_gworld(map, destRect);
	}

	/* Do vertical scrolling. */
	destRect = mapRect;

	/* Skip overlap already updated by Going Right or Going Left. */
	destRect.left = max(destRect.left, destRect.left - dx); 
	destRect.right = min(destRect.right, destRect.right - dx);		

	if (dy > 0) {							/* Going Down. */
		destRect.top = destRect.bottom - dy;
		copy_from_gworld(map, destRect);
	} else if (dy < 0) {						/* Going Up. */
		destRect.bottom = destRect.top - dy;
		copy_from_gworld(map, destRect);
	}
	
	/* This handles situations where we have hidden the scrollbars and the grow box forms
	a small protruding corner that causes a graphics drag. */
	
	if (!map->sbarwid) {

		Rect		growRect;

		/* Calculate the grow box rect. */
		growRect = map->contentrect;
		growRect.left = growRect.right - (map != worldmap ? sbarwid : floatsbarwid);
		growRect.top = growRect.bottom - (map != worldmap ? sbarwid : floatsbarwid);

		/* Clip to (map and) main screen content. Proceed if an overlap was found. */
		if (SectRect(&growRect, &mapRect, &growRect) == TRUE) {
		
			/* Do horizontal scrolling. */
			if (dx > 0) {						/* Going Right. */
				destRect = growRect;
				destRect.right = destRect.left;
				destRect.left -= dx;
				destRect.top -= dy;
				copy_from_gworld(map, destRect);
			}

			/* Do vertical scrolling. */
			if (dy > 0) {						/* Going Down. */
				destRect = growRect;
				destRect.bottom = destRect.top;
				destRect.top -= dy;
				/* Add small square missed by Going Right. */	
				destRect.left -= dx;							
				copy_from_gworld(map, destRect);
			}
		}
	}
	
	/* This updates areas that scroll into view from behind floating windows. Note that 
	because of the inverted geometry, going-down and going-up ADDS an extra square at
	the end of destRect instead of skipping one (min and max are reversed). */

	/* Note: this code also handles non-floating windows in front of the scrolled window
	correctly. Can happen if the map is scrolled in the background. */

	for_all_windows(win) {

		WindowPeek 	winPeek;
		Rect			winRect;		
		Point			winUL;
		Point			winDR;
					
		/* Stop when we reach the map window. */
		if (win == map->window)
			break;

		/* Find the corners of the window in global coordinates. */
		winPeek = (WindowPeek) win;
		winRect = (*(winPeek->strucRgn))->rgnBBox;			
		
		/* Transform the result into local coordinates. */
		winUL.h = winRect.left;
		winUL.v = winRect.top;
		winDR.h = winRect.right;
		winDR.v = winRect.bottom;
		GlobalToLocal(&winUL);			
		GlobalToLocal(&winDR);			
		SetRect(&winRect, winUL.h, winUL.v, winDR.h, winDR.v);

		/* Clip window rect to the map (and main screen) content. Skip 
		to the next window if no overlap was found. */
		if (SectRect(&winRect, &mapRect, &winRect) != TRUE)
		    continue;

		/* Do horizontal scrolling. */
		destRect = winRect;

		if (dx > 0) {						/* Going Right. */
			destRect.right = destRect.left;
			destRect.left -= dx;
			copy_from_gworld(map, destRect);
		} else if (dx < 0) {					/* Going Left. */
			destRect.left = destRect.right;
			destRect.right -= dx;
			copy_from_gworld(map, destRect);
		}

		/* Do vertical scrolling. */
		destRect = winRect;

		/* Add small square missed by Going Right or Going Left. */
		destRect.left = min(destRect.left, destRect.left - dx); 
		destRect.right = max(destRect.right, destRect.right - dx);		

		if (dy > 0) {						/* Going Down. */
			destRect.bottom = destRect.top;
			destRect.top -= dy;
			copy_from_gworld(map, destRect);
		} else if (dy < 0) {					/* Going Up. */
			destRect.top = destRect.bottom;
			destRect.bottom -= dy;
			copy_from_gworld(map, destRect);
		}
	}

	/* Redraw other-map boxes and restore the old port. */
	draw_related_maps(map);
	SetPort(oldport);

	if (DebugG) {
		SetRect(&debugRect, 0, 0, 0, 0);		/* Prevents drawing of junk next time. */
		scrolling = FALSE;				/* Turn on debugRect after scrolling. */
				
	}
}

/* Copies destrect from its source in the offscreen gworld. 
   Special code for handling wrapped cylindrical gworlds. */

void 
copy_from_gworld(Map *map, Rect destrect)
{
	Rect 	portrect= map->gworldPortPtr->portRect;
	Rect	sourcerect = destrect;
	int 	sxmax = map->vp->sxmax;
		
	/* Erase the old debugRect. */
	if (DebugG &! scrolling) {
		ForeColor(redColor);
		PenMode(patXor);
		FrameRect(&debugRect);
		ForeColor(blackColor);
		PenMode(normal);
	}

	/* Adjust source position for offset, scrolling buffers and decor. */
	OffsetRect(&sourcerect, map->offsetx + map->bufx - map->conw, 
			   		   map->offsety + map->bufy - map->toph);

	/* Make sure sourcerect is at least partially within the gworld. */
	while (area.xwrap && sourcerect.right < 0)
		OffsetRect(&sourcerect, sxmax, 0);
	while (area.xwrap && sourcerect.left > sxmax)
		OffsetRect(&sourcerect, -sxmax, 0);

	/* Handle wrapped map (complicated). */
	if (map->xwrap) {
		Rect westsource = sourcerect;
		Rect eastsource = sourcerect;
		Rect midsource = sourcerect;
		Rect westdest = destrect;
		Rect eastdest = destrect;
		Rect middest = destrect;
				
		/* Handle inside area (unnecessary if scrolling).*/
		if (!scrolling) {
			SectRect(&sourcerect, &portrect, &midsource);
			middest.left += midsource.left - sourcerect.left;				
			middest.right += midsource.right - sourcerect.right;				
			/* Make several tandem copies if necessary. */
			while (map->vp->pxw + sxmax > middest.left) {
				copy_clipped_to_map(map, midsource, middest);
				OffsetRect(&middest, sxmax, 0);
			} 
		}
				
		/* Handle area beyond western edge. */
		if (sourcerect.left < 0) {
			westsource.right = 0;
			westdest.right -= sourcerect.right;
			OffsetRect(&westsource, sxmax, 0);
			/* Make only westmost copy if scrolling. */
			if (scrolling)
				copy_clipped_to_map(map, westsource, westdest);
			/* Else make several tandem copies if necessary. */
			else while (map->vp->pxw + sxmax > westdest.left) {
				copy_clipped_to_map(map, westsource, westdest);
				OffsetRect(&westdest, sxmax, 0);
			}
		}

		/* Handle area beyond eastern edge. */
		if (sourcerect.right > portrect.right) {
			eastsource.left = portrect.right;
			eastdest.left += portrect.right - sourcerect.left;
			OffsetRect(&eastsource, -sxmax, 0);
			/* Make only eastmost copy if scrolling. */
			if (scrolling)
				copy_clipped_to_map(map, eastsource, eastdest);
			/* Else make several tandem copies if necessary. */
			else while (map->vp->pxw + sxmax > eastdest.left) {
				copy_clipped_to_map(map, eastsource, eastdest);
				OffsetRect(&eastdest, sxmax, 0);
			}
		}

	/* Handle all other cases (simple). */
	} else copy_clipped_to_map(map, sourcerect, destrect);
	
	/* Draw the new debugRect. */
	if (DebugG &! scrolling) {
		debugRect = destrect;					
		ForeColor(redColor);
		PenMode(patXor);
		FrameRect(&debugRect);
		ForeColor(blackColor);
		PenMode(normal);
	}
}

/* Core function in offscreen graphics that does the actual copying to the screen. Both source
   and destination rects are first adjusted so that copying is clipped to the map contentrect. */ 

void
copy_clipped_to_map(Map *map, Rect sourcerect, Rect destrect)
{
	/* Much of this clipping could be eliminated when certain bugs are gone. */

	/* Clip left edge to gworld. */
	if (sourcerect.left < 0) {
		destrect.left  -= sourcerect.left;
		sourcerect.left = 0;
	}	
	/* Clip right edge to gworld. */
	if (sourcerect.right > map->gworldPortPtr->portRect.right) {
		destrect.right -= sourcerect.right - map->gworldPortPtr->portRect.right;
		sourcerect.right = map->gworldPortPtr->portRect.right;
	}
	/* Clip left edge to contentrect. */
	if (destrect.right > map->contentrect.right) {
		sourcerect.right -= destrect.right - map->contentrect.right;
		destrect.right = map->contentrect.right;
	}
	/* Clip right edge to contentrect. */
	if (destrect.left < map->contentrect.left) {
		sourcerect.left += map->contentrect.left - destrect.left;
		destrect.left = map->contentrect.left;
	}
	/* Clip top edge to contentrect. */
	if (destrect.top < map->contentrect.top) {
		sourcerect.top += map->contentrect.top - destrect.top;
		destrect.top = map->contentrect.top;
	}
	/* Clip bottom edge to contentrect. */
	if (destrect.bottom > map->contentrect.bottom) {
		sourcerect.bottom -= destrect.bottom - map->contentrect.bottom;
		destrect.bottom = map->contentrect.bottom;
	}

	LockPixels(GetGWorldPixMap(map->gworldPortPtr));
	CopyBits(&((GrafPtr) map->gworldPortPtr)->portBits,
		       &((GrafPtr) map->windowPortPtr)->portBits,
		       &sourcerect, &destrect, srcCopy, NULL  );
	UnlockPixels(GetGWorldPixMap(map->gworldPortPtr));
}

/* Updates the resized map using as much as possible of its offscreen gworld. Then also updates the 
   latter and redraws the map in the background. This function is called by grow_map and zoom_map 
   after window resizing but before contentrect has been resized. */

void
update_resized_map(Map *map)
{
	int right, left, down, up;
	Rect old_contentrect;

	/* Save and then resize contentrect */
	old_contentrect = map->contentrect;
	set_content_rect(map);						

	/* Calculate diffs between old & resized contentrects */
	right = map->contentrect.right 	- old_contentrect.right;
	left  = map->contentrect.left 	- old_contentrect.left;
	down  = map->contentrect.bottom - old_contentrect.bottom;
	up    = map->contentrect.top 	- old_contentrect.top;

	/* Draw the map decor */
	set_map_scrollbars(map);						
	if (map->conw > 0)
	  draw_control_panel(map);
	if (map->toplineh > 0)
	  draw_top_line(map);
	if (map->topunith > 0)
	  draw_unit_info(map);
	
	/* Check if the new contentrect extends outside the old gworld */
	if (right - left > map->bufx - map->offsetx 
	    || down - up > map->bufy - map->offsety) {

		/* Add buffers and substract offsets to get the old gworld portrect */
		InsetRect(&old_contentrect, - map->bufx, - map->bufy);
		OffsetRect(&old_contentrect, - map->offsetx, - map->offsety);

		/* First use as much as possible of the old gworld */
		copy_from_gworld(map, old_contentrect);	   

		/* Then exclude this region from the update */ 
		InvalRect(&map->window->portRect);
		ValidRect(&old_contentrect);
		BeginUpdate(map->window);

		/* Paint the update region blue (looks nice) */
		BackPat(QDPat(black));

		if (hasColorQD)
		  RGBForeColor(&backcolor);

		EraseRect(&map->contentrect);
		ForeColor(blackColor);
		EndUpdate(map->window);

		/* Redraw the gworld (and the map) */ 
		update_gworld(map);

	} else {
		/* If not, first copy map content from the old gworld */
		copy_from_gworld(map, map->contentrect);	   

		/* Then redraw the gworld */
		update_gworld(map);
	}
}

/* Update the offscreen gworld. This function should always be called when the window size, magnification 
   or view angle is changed. It redraws the whole map within the updated gworld. It also optimizes the size 
   and position of the gworld. If the whole map fits within the gworld for either the x or y coordinate, the 
   viewport is shifted to the middle of the map for that coordinate, the gworld is updated, and the viewport is 
   then returned to the old position. If the map is larger than the gworld, the position of the latter is always 
   adjusted so that it does not protrude beyond the map edges. The purpose of these adjustements is to ensure 
   that as much scrolling as possible can be done without updating the gworld and redrawing the map. */

/* Note: update_gworld should NOT be called from within a BeginUpdate segment. */

void
update_gworld(Map *map)
{
	int margx, margy, newsx, newsy, oldsx, oldsy;			
	Rect GWportRect;	
	
	/* Compute sizes of map margins outside the viewport */
	margx = (map->vp->sxmax - map->vp->sxmin) / 2;
	margy = (map->vp->symax - map->vp->symin) / 2;

	/* Save the current viewport position */
	newsx = oldsx = map->vp->sx;
	newsy = oldsy = map->vp->sy;
	
	/* Optimize x position and size of the gworld */
	if (margx <= 0)									/* The viewport is wider than the map */
	  map->bufx = 0;								/* Scrolling buffers are unnecessary */								

	else if (area.xwrap 								
		&& map->vp->sxmax <= map->vp->pxw)			/* Handle wrapped worlds where the */
	  	map->bufx = 0;								/* window is wider than the area */

	else if (area.xwrap) {								/* Handle other wrapped worlds */
		map->bufx = min(map->max_bufx,				/* Make gworld same size as the area if */ 
		(area.width * map->vp->hw - map->vp->pxw)/2);	/* max_bufx is big enough for wrapping */
												/* 1 pixel may be lost here (see below) */
			
	} else if (margx < map->max_bufx - map->vp->sxmin- 4) {	/* The map will fit within the gworld */
		map->bufx = map->vp->sxmin + margx + 4; 		/* Minimize offscreen x buffer */
		newsx = limitn(map->vp->sxmin, margx, 			/* Center gworld around center of map */
				        map->vp->sxmax);  
	} else {										/* The map is wider than the gworld */
		map->bufx = map->max_bufx;					/* Set x buffer to max allowed value */
		newsx = limitn(map->vp->sxmin + map->bufx, newsx,	/* Adjust the gworld so that its edges */
				        map->vp->sxmax - map->bufx);	 	/* do not extend outside the map edges */
	}

	/* Optimize y position and size of the gworld */
	if (margy <= 0)
	  map->bufy = 0;								/* The viewport is higher than the map */
	else if (margy < map->max_bufy - map->vp->symin- 4) {	/* The map will fit within the gworld */
		map->bufy = map->vp->symin + margy + 4; 		/* Minimize offscreen y buffer */					
		newsy = limitn(map->vp->symin, margy, 			/* Center gworld around center of map */
				        map->vp->symax); 
	} else {										/* The map is higher than the gworld */								
		map->bufy = map->max_bufy;					/* Set y buffer to max allowed value */		
		newsy = limitn(map->vp->symin + map->bufy, newsy,	/* Adjust the gworld so that its edges */ 
				        map->vp->symax - map->bufy);		/* do not extend outside the map edges */
	}
		
	/* Shift the viewport to optimized gworld position */
	map->vp->sx += newsx - oldsx;
	map->vp->sy += newsy - oldsy; 

	/* Set gworld port rect to temp contentrect */
	set_content_rect(map);
	GWportRect = map->contentrect;
	
	/* Add offscreen scrolling buffers on each side */
	InsetRect(&GWportRect, - map->bufx, - map->bufy);

	/* Add 1 pixel to get correct wrapped gworld width if necessary */
	if (area.xwrap && map->vp->sxmax - map->vp->pxw != 2 * map->bufx)
		GWportRect.right += 1;

	/* Set gworld to area width if window is wider than area */
	if (area.xwrap && map->vp->sxmax <= map->vp->pxw) {
		SetRect(&GWportRect, 0, 0, 
				map->vp->sxmax, 
				map->vp->pxh + 2 * map->bufy);
	}

	/* Update gworld centered around new optimized positon and check for errors */
	map->qdErr = UpdateGWorld(&map->gworldPortPtr, 0,  &GWportRect, 0, 0, 0);

	/* Alert and close the map window if we ran out of memory */
	if (map->gworldPortPtr == NULL || map->qdErr != noErr ) {
		close_window(map->window);
		StopAlert(aNoMem, NULL);
		return;
	}

	/* Set window displacement trackers to zero */
	map->offsetx = map->offsety = 0;

	/* Cache the wrapping status. */
	if (area.xwrap && map->vp->sxmax <= GWportRect.right - GWportRect.left)
	  map->xwrap = TRUE;
	else
	  map->xwrap = FALSE;

	/* Do all drawing in the background */
	ValidRect(&map->contentrect);
	BeginUpdate(map->window);

	/* Redraw the map (in new position) */
	draw_map(map);

	/* Return viewport to the old position */
	map->vp->sx -= newsx - oldsx;
	map->vp->sy -= newsy - oldsy; 

	/* Adjust window displacement trackers */
	map->offsetx -= newsx - oldsx;
	map->offsety -= newsy - oldsy; 

	/* Restore old contentrect and draw scrollbars */
	set_content_rect(map);
	set_map_scrollbars(map);

	/* Show the result */
	EndUpdate(map->window);

	/* Finally update the screen using the new gworld */
	copy_from_gworld(map, map->contentrect);
}

/* Check if cell (x, y) is within the offscreen gworld of the current map */

int
cell_is_in_gworld(Map *map, int x, int y)
{
	Rect 	portrect = map->gworldPortPtr->portRect;
	int 	sx, sy;

	if (!in_area(x, y)) 
		return FALSE;   
	xform(map, x, y, &sx, &sy);
	if (sy + map->vp->hh + map->bufy < 0) 
		return FALSE;
	if (sy >  map->vp->pxh + 2 * map->bufy) 
		return FALSE;

	if (map->xwrap)
	  return TRUE;
	else if (sx + map->vp->hw + map->bufx < 0) 
	  return FALSE;
	else if (sx >  map->vp->pxw + 2 * map->bufx) 
	  return FALSE;
	return TRUE;
}

/* This function is called from handle_events. It enables a special use of the numeric keypad keys for 
   scrolling the map within its offscreen gworld. Returns TRUE if a numeric keypad key really was hit.
   Note: since numeric + and * double as arrow keys on antique Macs, the latter cannot zoom in or set
   the angle using these shortcuts. All other keypad shortcuts work as expected also on antique Macs. */

int
handle_numeric_keypad(EventRecord *event)
{
	int	pxh, pxw;
	Map *map;
	char	key;

	/* Get the worldmap if alt key was down, else get the front map. */
	if (event->modifiers & optionKey)
		map = worldmap;
	else	map = frontmap;

	/* Use keyCode instead of charCode to distinguish numeric keypad keys. */
	key = (event->message & keyCodeMask) >> 8;

	/* Return FALSE if this is not a numeric keypad key (or if it is an arrow 
	key on an antique Mac) */
	if (key < 0x41
		|| (key > 0x5C && key < 0x7B)
		|| key == 0x42
		|| key == 0x46
		|| key == 0x47
		|| key == 0x48
		|| key == 0x4C
		|| key == 0x4D)

	  return FALSE;

	/* Return TRUE and BEEP if the front window is not a map window */
	if (!map) {
		beep(); 
		return TRUE;
	}

	/* Moved here to stop crashes. */
	pxh = map->vp->pxh;
	pxw = map->vp->pxw;
	
	/* Make jumps reasonable size for small maps. */
	pxw = min(pxw, (area.width * map->vp->hw)/10);

	/* Scroll one full window if command key also was down */
	if (event->modifiers & cmdKey) {
		if (key == 0x56) { scroll_map_window(map, -pxw, 0); return TRUE; }		/* Keypad 4 */
		if (key == 0x58) { scroll_map_window(map, pxw, 0); return TRUE; }		/* Keypad 6 */
		if (key == 0x5B) { scroll_map_window(map, 0, -pxh); return TRUE; }		/* Keypad 8 */
		if (key == 0x54) { scroll_map_window(map, 0, pxh); return TRUE; }		/* Keypad 2 */

		if (key == 0x59) { scroll_map_window(map, -pxw, -pxh); return TRUE; }	/* Keypad 7 */
		if (key == 0x5C) { scroll_map_window(map, pxw, -pxh); return TRUE; }	/* Keypad 9 */
		if (key == 0x53) { scroll_map_window(map, -pxw, pxh); return TRUE; }	/* Keypad 1 */
		if (key == 0x55) { scroll_map_window(map, pxw, pxh); return TRUE; }		/* Keypad 3 */					

		if (key == 0x7B) { scroll_map_window(map, -pxw, 0); return TRUE; }		/* Left Arrow*/
		if (key == 0x7C) { scroll_map_window(map, pxw, 0); return TRUE; }		/* Right Arrow */
		if (key == 0x7E) { scroll_map_window(map, 0, -pxh); return TRUE; }		/* Up Arrow */
		if (key == 0x7D) {  scroll_map_window(map, 0, pxh); return TRUE; }		/* Down Arrow */

	} else {
		/* Else either auto-scroll as long as the key is down, or change the center,
		   scale or view angle.  */
		if (key == 0x56) { auto_scroll_left(map); return TRUE; }		/* Keypad 4 */
		if (key == 0x58) { auto_scroll_right(map); return TRUE; }		/* Keypad 6 */
		if (key == 0x5B) { auto_scroll_up(map); return TRUE; }		/* Keypad 8 */
		if (key == 0x54) { auto_scroll_down(map); return TRUE; }		/* Keypad 2 */

		if (key == 0x59) { auto_scroll_up_left(map); return TRUE; }	/* Keypad 7 */
		if (key == 0x5C) { auto_scroll_up_right(map); return TRUE; }	/* Keypad 9 */
		if (key == 0x53) { auto_scroll_down_left(map); return TRUE; }	/* Keypad 1 */
		if (key == 0x55) { auto_scroll_down_right(map); return TRUE; }	/* Keypad 3 */

		if (key == 0x7B) { auto_scroll_left_arrow(map); return TRUE; }		/* Left Arrow*/
		if (key == 0x7C) { auto_scroll_right_arrow(map); return TRUE; }		/* Right Arrow */
		if (key == 0x7E) { auto_scroll_up_arrow(map); return TRUE; }		/* Up Arrow */
		if (key == 0x7D) { auto_scroll_down_arrow(map); return TRUE; }		/* Down Arrow */

		if (key == 0x57) { do_recenter(dside); return TRUE; }			/* Keypad 5 */
		if (key == 0x45) { magnify_map(map, 1); return TRUE; }		/* Keypad + */
		if (key == 0x4E) { magnify_map(map, -1); return TRUE; }		/* Keypad - */

		if (key == 0x43) { rotate_angle_up(map); return TRUE; }		/* Keypad * */
		if (key == 0x4B) { rotate_angle_down(map); return TRUE; }		/* Keypad / */
	}
	return FALSE;	/* Default FALSE permits normal use of unassigned numeric keypad keys */
}

/* The following auto_scroll functions bypass the main event loop for increased scrolling speeed. */

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

void
auto_scroll_left(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, -4, 0);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x56 >> 3] >> (0x56 & 7) & 1;	/* keypad 4 */
	}
}

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

void
auto_scroll_right(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 4, 0);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x58 >> 3] >> (0x58 & 7) & 1;	/* keypad 6 */
	}
}

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

void
auto_scroll_up(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 0, -4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x5B >> 3] >> (0x5B & 7) & 1;	/* keypad 8 */
	}
}

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

void
auto_scroll_down(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 0, 4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x54 >> 3] >> (0x54 & 7) & 1;	/* keypad 2 */
	}
}

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

void
auto_scroll_down_left(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, -4, 4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x53 >> 3] >> (0x53 & 7) & 1;	/* keypad 1 */
	}
}

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

void
auto_scroll_down_right(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 4, 4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x55 >> 3] >> (0x55 & 7) & 1;	/* keypad 3 */
	}
}

/* Scroll up and left as long as key is pressed. */

void
auto_scroll_up_left(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, -4, -4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x59 >> 3] >> (0x59 & 7) & 1;	/* keypad 7 */
	}
}

/* Scroll up and right as long as key is pressed. */

void
auto_scroll_up_right(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 4, -4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x5C >> 3] >> (0x5C & 7) & 1;	/* keypad 9 */
	}
}

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

void
auto_scroll_left_arrow(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, -4, 0);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7B >> 3] >> (0x7B & 7) & 1;	/* left arrow */
	}
}

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

void
auto_scroll_right_arrow(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 4, 0);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7C >> 3] >> (0x7C & 7) & 1;	/* right arrow */
	}
}

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

void
auto_scroll_up_arrow(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 0, -4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7E >> 3] >> (0x7E & 7) & 1;	/* keypad 8 */
	}
}

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

void
auto_scroll_down_arrow(Map *map)
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		scroll_map_window(map, 0, 4);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7D >> 3] >> (0x7D & 7) & 1;	/* keypad 2 */
	}
}

void
rotate_angle_up(Map *map)
{
	if (map->vp->angle == 90)
	  set_map_angle(map, 15);
	else if (map->vp->angle == 15)
	  set_map_angle(map, 30);
	else
	  set_map_angle(map, 90);
}

void
rotate_angle_down(Map *map)
{
	if (map->vp->angle == 90)
	  set_map_angle(map, 30);
	else if (map->vp->angle == 30)
	  set_map_angle(map, 15);
	else
	  set_map_angle(map, 90);
}

void
update_all_windows(void)
{
	WindowPtr win;
	
	for_all_windows(win) { 
		update_window(win);
	}
}

void
update_all_map_windows(void)
{
	Map *map;
	
	for_all_maps(map) {
		update_window(map->window);
	}
}

void
update_all_visible_windows(void)
{
	WindowPtr win;
	
	for_all_windows(win) {
		if (((WindowPeek) win)->visible)
			update_window(win);
	}
}

void
update_all_visible_map_windows(void)
{
	Map *map;
	
	for_all_maps(map) {
		if (((WindowPeek)map->window)->visible)
			update_window(map->window);
	}
}

void
update_all_dialogs(void)
{
	WindowPtr win;
	
	for_all_windows(win) {
		if (((WindowPeek) win)->windowKind == dialogKind)
			update_window(win);
	}
}

void
update_all_visible_dialogs(void)
{
	WindowPtr win;
	
	for_all_windows(win) {
		if (((WindowPeek) win)->windowKind == dialogKind
		    && ((WindowPeek) win)->visible)
			update_window(win);
	}
}
