/*
 *       Copyright 1988 by Evans & Sutherland Computer Corporation,
 *                          Salt Lake City, Utah
 *  Portions Copyright 1989 by the Massachusetts Institute of Technology
 *                        Cambridge, Massachusetts
 *
 * Copyright 1992 Claude Lecommandeur.
 */

/***********************************************************************
 *
 * $XConsortium: resize.c,v 1.80 91/05/11 17:35:42 dave Exp $
 *
 * window resizing borrowed from the "wm" window manager
 *
 * 11-Dec-87 Thomas E. LaStrange                File created
 *
 * Do the necessary modification to be integrated in ctwm.
 * Can no longer be used for the standard twm.
 *
 * 22-April-92 Claude Lecommandeur.
 *
 *
 ***********************************************************************/

#include "ctwm.h"

#include <stdio.h>
#include <stdlib.h>

#include "events.h"
#include "util.h"
#include "otp.h"
#include "functions_defs.h"
#include "add_window.h"
#include "colormaps.h"
#include "screen.h"
#include "drawing.h"
#include "r_area.h"
#include "r_area_list.h"
#include "r_layout.h"
#include "win_decorations.h"
#include "win_ops.h"
#include "win_resize.h"
#include "win_utils.h"
#include "workspace_manager.h"
#include "iconmgr.h"

#define MINHEIGHT 0     /* had been 32 */
#define MINWIDTH 0      /* had been 60 */

static int dragx;       /* all these variables are used */
static int dragy;       /* in resize operations */
static unsigned int dragWidth;
static unsigned int dragHeight;

static int origx;
static int origy;
static int origWidth;
static int origHeight;

static int clampTop;
static int clampBottom;
static int clampLeft;
static int clampRight;
static int clampDX;
static int clampDY;

static int last_width;
static int last_height;

static unsigned int resizeGrabMask;

static void DisplaySize(TwmWindow *tmp_win, int width, int height);

static void do_auto_clamp(TwmWindow *tmp_win, XEvent *evp)
{
	Window junkRoot;
	int x, y, h, v, junkbw;
	unsigned int junkMask;

	switch(evp->type) {
		case ButtonPress:
			x = evp->xbutton.x_root;
			y = evp->xbutton.y_root;
			break;
		case KeyPress:
			x = evp->xkey.x_root;
			y = evp->xkey.y_root;
			break;
		default:
			if(!XQueryPointer(dpy, Scr->Root, &junkRoot, &junkRoot,
			                  &x, &y, &junkbw, &junkbw, &junkMask)) {
				return;
			}
	}

	/*
	 * Determine in which of the 9 "quadrants" of the window we are.
	 * Cast the values to signed int: if the numerator is negative
	 * we don't want them converted to unsigned due to the default
	 * promotion rules: that would produce a very large quotient.
	 */
	h = (int)(x - dragx) / (int)(dragWidth < 3 ? 1 : (dragWidth / 3));
	v = (int)(y - dragy - tmp_win->title_height) /
	    (int)(dragHeight < 3 ? 1 : (dragHeight / 3));

	if(h <= 0) {
		clampLeft = 1;
		clampDX = (x - dragx);
	}
	else if(h >= 2) {
		clampRight = 1;
		clampDX = (x - dragx - dragWidth);
	}

	if(v <= 0) {
		clampTop = 1;
		clampDY = (y - dragy);
	}
	else if(v >= 2) {
		clampBottom = 1;
		clampDY = (y - dragy - dragHeight);
	}
}

/***********************************************************************
 *
 *  Procedure:
 *      OpaqueResizeSize - determine if window should be resized opaquely.
 *
 *  Inputs:
 *      tmp_win - the TwmWindow pointer
 *
 ***********************************************************************
 */

void OpaqueResizeSize(TwmWindow *tmp_win)
{
	if(tmp_win->OpaqueResize) {
		/*
		 * OpaqueResize defaults to a thousand.  Assume that any number
		 * >= 1000 is "infinity" and don't bother calculating.
		 */
		if(Scr->OpaqueResizeThreshold >= 1000) {
			Scr->OpaqueResize = true;
		}
		else {
			/*
			 * scrsz will hold the number of pixels in your resolution,
			 * which can get big.  [signed] int may not cut it.
			 */
			const unsigned long winsz = tmp_win->frame_width
			                            * tmp_win->frame_height;
			const unsigned long scrsz = Scr->rootw  * Scr->rooth;
			if(winsz > (scrsz * (Scr->OpaqueResizeThreshold / 100.0))) {
				Scr->OpaqueResize = false;
			}
			else {
				Scr->OpaqueResize = true;
			}
		}
	}
	else {
		Scr->OpaqueResize = false;
	}
}


/***********************************************************************
 *
 *  Procedure:
 *      StartResize - begin a window resize operation
 *
 *  Inputs:
 *      ev      - the event structure (button press)
 *      tmp_win - the TwmWindow pointer
 *      fromtitlebar - action invoked from titlebar button
 *
 ***********************************************************************
 */

void StartResize(XEvent *evp, TwmWindow *tmp_win,
                 bool fromtitlebar, bool from3dborder)
{
	Window      junkRoot, grabwin;
	unsigned int junkbw, junkDepth;
	Cursor      cursor;

	cursor = (Scr->BorderCursors
	          && tmp_win->curcurs) ? tmp_win->curcurs : Scr->ResizeCursor;
	ResizeWindow = tmp_win->frame;
	if(! Scr->OpaqueResize || resizeWhenAdd) {
		XGrabServer(dpy);
	}
	resizeGrabMask = ButtonPressMask | ButtonReleaseMask |
	                 ButtonMotionMask | PointerMotionHintMask;

	grabwin = Scr->Root;
#ifdef WINBOX
	if(tmp_win->winbox) {
		grabwin = tmp_win->winbox->window;
	}
#endif
	XGrabPointer(dpy, grabwin, True, resizeGrabMask,
	             GrabModeAsync, GrabModeAsync, grabwin, cursor, CurrentTime);

	XGetGeometry(dpy, (Drawable) tmp_win->frame, &junkRoot,
	             &dragx, &dragy, &dragWidth, &dragHeight, &junkbw,
	             &junkDepth);
	dragx += tmp_win->frame_bw;
	dragy += tmp_win->frame_bw;
	origx = dragx;
	origy = dragy;
	origWidth = dragWidth;
	origHeight = dragHeight;
	clampTop = clampBottom = clampLeft = clampRight = clampDX = clampDY = 0;

	if(Scr->AutoRelativeResize && (from3dborder || !fromtitlebar)) {
		do_auto_clamp(tmp_win, evp);
	}

	Scr->SizeStringOffset = SIZE_HINDENT;
	MoveResizeSizeWindow(evp->xbutton.x_root, evp->xbutton.y_root,
	                     Scr->SizeStringWidth + SIZE_HINDENT * 2,
	                     Scr->SizeFont.height + SIZE_VINDENT * 2);
	XMapRaised(dpy, Scr->SizeWindow);
	InstallRootColormap();
	last_width = 0;
	last_height = 0;
	DisplaySize(tmp_win, origWidth, origHeight);

	if(! Scr->OpaqueResize || resizeWhenAdd)
		MoveOutline(Scr->Root, dragx - tmp_win->frame_bw,
		            dragy - tmp_win->frame_bw, dragWidth + 2 * tmp_win->frame_bw,
		            dragHeight + 2 * tmp_win->frame_bw,
		            tmp_win->frame_bw, tmp_win->title_height + tmp_win->frame_bw3D);
}


void MenuStartResize(TwmWindow *tmp_win, int x, int y, int w, int h)
{
	if(! Scr->OpaqueResize) {
		XGrabServer(dpy);
	}
	resizeGrabMask = ButtonPressMask | ButtonMotionMask | PointerMotionMask;
	XGrabPointer(dpy, Scr->Root, True, resizeGrabMask,
	             GrabModeAsync, GrabModeAsync,
	             Scr->Root, Scr->ResizeCursor, CurrentTime);
	dragx = x + tmp_win->frame_bw;
	dragy = y + tmp_win->frame_bw;
	origx = dragx;
	origy = dragy;
	dragWidth = origWidth = w;
	dragHeight = origHeight = h;
	clampTop = clampBottom = clampLeft = clampRight = clampDX = clampDY = 0;
	last_width = 0;
	last_height = 0;
	Scr->SizeStringOffset = SIZE_HINDENT;
	MoveResizeSizeWindow(dragx, dragy,
	                     Scr->SizeStringWidth + SIZE_HINDENT * 2,
	                     Scr->SizeFont.height + SIZE_VINDENT * 2);
	XMapRaised(dpy, Scr->SizeWindow);
	DisplaySize(tmp_win, origWidth, origHeight);
	if(! Scr->OpaqueResize)
		MoveOutline(Scr->Root, dragx - tmp_win->frame_bw,
		            dragy - tmp_win->frame_bw,
		            dragWidth + 2 * tmp_win->frame_bw,
		            dragHeight + 2 * tmp_win->frame_bw,
		            tmp_win->frame_bw, tmp_win->title_height + tmp_win->frame_bw3D);
}

/***********************************************************************
 *
 *  Procedure:
 *      AddStartResize - begin a windorew resize operation from AddWindow
 *
 *  Inputs:
 *      tmp_win - the TwmWindow pointer
 *
 ***********************************************************************
 */

void AddStartResize(TwmWindow *tmp_win, int x, int y, int w, int h)
{
	XGrabServer(dpy);
	resizeGrabMask = ButtonReleaseMask | ButtonMotionMask | PointerMotionHintMask;
	XGrabPointer(dpy, Scr->Root, True, resizeGrabMask,
	             GrabModeAsync, GrabModeAsync,
	             Scr->Root, Scr->ResizeCursor, CurrentTime);

	dragx = x + tmp_win->frame_bw;
	dragy = y + tmp_win->frame_bw;
	origx = dragx;
	origy = dragy;
	dragWidth = origWidth = w - 2 * tmp_win->frame_bw;
	dragHeight = origHeight = h - 2 * tmp_win->frame_bw;
	clampTop = clampBottom = clampLeft = clampRight = clampDX = clampDY = 0;
	last_width = 0;
	last_height = 0;
	DisplaySize(tmp_win, origWidth, origHeight);
}


void MenuDoResize(int x_root, int y_root, TwmWindow *tmp_win)
{
	int action;
	Cursor cursor = 0;

	action = 0;

	x_root -= clampDX;
	y_root -= clampDY;

	if(clampTop) {
		int         delta = y_root - dragy;
		if((int)(dragHeight - delta) < MINHEIGHT) {
			delta = dragHeight - MINHEIGHT;
			clampTop = 0;
		}
		dragy += delta;
		dragHeight -= delta;
		action = 1;
		cursor = TopCursor;
	}
	else if(y_root <= dragy) {
		dragy = y_root;
		dragHeight = origy + origHeight -
		             y_root;
		clampBottom = 0;
		clampTop = 1;
		clampDY = 0;
		action = 1;
		cursor = TopCursor;
	}
	if(clampLeft) {
		int         delta = x_root - dragx;
		if((int)(dragWidth - delta) < MINWIDTH) {
			delta = dragWidth - MINWIDTH;
			clampLeft = 0;
		}
		dragx += delta;
		dragWidth -= delta;
		action = 1;
		cursor = clampTop ? TopLeftCursor : LeftCursor;
	}
	else if(x_root <= dragx) {
		dragx = x_root;
		dragWidth = origx + origWidth -
		            x_root;
		clampRight = 0;
		clampLeft = 1;
		clampDX = 0;
		action = 1;
		cursor = clampTop ? TopLeftCursor : LeftCursor;
	}
	if(clampBottom) {
		int         delta = y_root - dragy - dragHeight;
		if((int)(dragHeight + delta) < MINHEIGHT) {
			delta = MINHEIGHT - dragHeight;
			clampBottom = 0;
		}
		dragHeight += delta;
		action = 1;
		cursor = clampLeft ? BottomLeftCursor : BottomCursor;
	}
	else if(y_root >= dragy + dragHeight) {
		dragy = origy;
		dragHeight = 1 + y_root - dragy;
		clampTop = 0;
		clampBottom = 1;
		clampDY = 0;
		action = 1;
		cursor = clampLeft ? BottomLeftCursor : BottomCursor;
	}
	if(clampRight) {
		int         delta = x_root - dragx - dragWidth;
		if((int)(dragWidth + delta) < MINWIDTH) {
			delta = MINWIDTH - dragWidth;
			clampRight = 0;
		}
		dragWidth += delta;
		action = 1;
		cursor = clampBottom ? BottomRightCursor : RightCursor;
		cursor = clampTop ? TopRightCursor : cursor;
	}
	else if(x_root >= dragx + dragWidth) {
		dragx = origx;
		dragWidth = 1 + x_root - origx;
		clampLeft = 0;
		clampRight = 1;
		clampDX = 0;
		action = 1;
		cursor = clampBottom ? BottomRightCursor : RightCursor;
		cursor = clampTop ? TopRightCursor : cursor;
	}

	if(action) {
		ConstrainSize(tmp_win, &dragWidth, &dragHeight);
		if(clampLeft) {
			dragx = origx + origWidth - dragWidth;
		}
		if(clampTop) {
			dragy = origy + origHeight - dragHeight;
		}
		if(Scr->OpaqueResize)
			SetupWindow(tmp_win, dragx - tmp_win->frame_bw, dragy - tmp_win->frame_bw,
			            dragWidth, dragHeight, -1);
		else
			MoveOutline(Scr->Root,
			            dragx - tmp_win->frame_bw,
			            dragy - tmp_win->frame_bw,
			            dragWidth + 2 * tmp_win->frame_bw,
			            dragHeight + 2 * tmp_win->frame_bw,
			            tmp_win->frame_bw, tmp_win->title_height + tmp_win->frame_bw3D);
		if(Scr->BorderCursors && (cursor != tmp_win->curcurs)) {
			tmp_win->curcurs = cursor;
			XChangeActivePointerGrab(dpy, resizeGrabMask, cursor, CurrentTime);
		}
	}

	DisplaySize(tmp_win, dragWidth, dragHeight);
}

/***********************************************************************
 *
 *  Procedure:
 *      DoResize - move the rubberband around.  This is called for
 *                 each motion event when we are resizing
 *
 *  Inputs:
 *      x_root  - the X corrdinate in the root window
 *      y_root  - the Y corrdinate in the root window
 *      tmp_win - the current twm window
 *
 ***********************************************************************
 */

void DoResize(int x_root, int y_root, TwmWindow *tmp_win)
{
	int action;
	Cursor cursor = 0;

	action = 0;

	x_root -= clampDX;
	y_root -= clampDY;

	if(clampTop) {
		int         delta = y_root - dragy;
		if((int)(dragHeight - delta) < MINHEIGHT) {
			delta = dragHeight - MINHEIGHT;
			clampTop = 0;
		}
		dragy += delta;
		dragHeight -= delta;
		action = 1;
		cursor = TopCursor;
	}
	else if(y_root <= dragy) {
		dragy = y_root;
		dragHeight = origy + origHeight -
		             y_root;
		clampBottom = 0;
		clampTop = 1;
		clampDY = 0;
		action = 1;
		cursor = TopCursor;
	}
	if(clampLeft) {
		int         delta = x_root - dragx;
		if((int)(dragWidth - delta) < MINWIDTH) {
			delta = dragWidth - MINWIDTH;
			clampLeft = 0;
		}
		dragx += delta;
		dragWidth -= delta;
		action = 1;
		cursor = clampTop ? TopLeftCursor : LeftCursor;
	}
	else if(x_root <= dragx) {
		dragx = x_root;
		dragWidth = origx + origWidth -
		            x_root;
		clampRight = 0;
		clampLeft = 1;
		clampDX = 0;
		action = 1;
		cursor = clampTop ? TopLeftCursor : LeftCursor;
	}
	if(clampBottom) {
		int         delta = y_root - dragy - dragHeight;
		if((int)(dragHeight + delta) < MINHEIGHT) {
			delta = MINHEIGHT - dragHeight;
			clampBottom = 0;
		}
		dragHeight += delta;
		action = 1;
		cursor = clampLeft ? BottomLeftCursor : BottomCursor;
	}
	else if(y_root >= dragy + dragHeight - 1) {
		dragy = origy;
		dragHeight = 1 + y_root - dragy;
		clampTop = 0;
		clampBottom = 1;
		clampDY = 0;
		action = 1;
		cursor = clampLeft ? BottomLeftCursor : BottomCursor;
	}
	if(clampRight) {
		int         delta = x_root - dragx - dragWidth;
		if((int)(dragWidth + delta) < MINWIDTH) {
			delta = MINWIDTH - dragWidth;
			clampRight = 0;
		}
		dragWidth += delta;
		action = 1;
		cursor = clampBottom ? BottomRightCursor : RightCursor;
		cursor = clampTop ? TopRightCursor : cursor;
	}
	else if(x_root >= dragx + dragWidth - 1) {
		dragx = origx;
		dragWidth = 1 + x_root - origx;
		clampLeft = 0;
		clampRight = 1;
		clampDX = 0;
		action = 1;
		cursor = clampBottom ? BottomRightCursor : RightCursor;
		cursor = clampTop ? TopRightCursor : cursor;
	}

	if(action) {
		ConstrainSize(tmp_win, &dragWidth, &dragHeight);
		if(clampLeft) {
			dragx = origx + origWidth - dragWidth;
		}
		if(clampTop) {
			dragy = origy + origHeight - dragHeight;
		}
		if(Scr->OpaqueResize && ! resizeWhenAdd) {
			SetupWindow(tmp_win, dragx - tmp_win->frame_bw, dragy - tmp_win->frame_bw,
			            dragWidth, dragHeight, -1);
		}
		else {
			MoveOutline(Scr->Root,
			            dragx - tmp_win->frame_bw,
			            dragy - tmp_win->frame_bw,
			            dragWidth + 2 * tmp_win->frame_bw,
			            dragHeight + 2 * tmp_win->frame_bw,
			            tmp_win->frame_bw, tmp_win->title_height + tmp_win->frame_bw3D);
		}
		if(Scr->BorderCursors && (cursor != tmp_win->curcurs)) {
			tmp_win->curcurs = cursor;
			XChangeActivePointerGrab(dpy, resizeGrabMask, cursor, CurrentTime);
		}
	}

	DisplaySize(tmp_win, dragWidth, dragHeight);
}

/***********************************************************************
 *
 *  Procedure:
 *      DisplaySize - display the size in the dimensions window
 *
 *  Inputs:
 *      tmp_win - the current twm window
 *      width   - the width of the rubber band
 *      height  - the height of the rubber band
 *
 ***********************************************************************
 */

static void DisplaySize(TwmWindow *tmp_win, int width, int height)
{
	char str[100];
	int dwidth;
	int dheight;

	if(last_width == width && last_height == height) {
		return;
	}

	last_width = width;
	last_height = height;

	dheight = height - tmp_win->title_height - 2 * tmp_win->frame_bw3D;
	dwidth = width - 2 * tmp_win->frame_bw3D;

	/*
	 * ICCCM says that PMinSize is the default is no PBaseSize is given,
	 * and vice-versa.
	 */
	if(tmp_win->hints.flags & (PMinSize | PBaseSize)
	                && tmp_win->hints.flags & PResizeInc) {
		if(tmp_win->hints.flags & PBaseSize) {
			dwidth -= tmp_win->hints.base_width;
			dheight -= tmp_win->hints.base_height;
		}
		else {
			dwidth -= tmp_win->hints.min_width;
			dheight -= tmp_win->hints.min_height;
		}
	}

	if(tmp_win->hints.flags & PResizeInc) {
		dwidth /= tmp_win->hints.width_inc;
		dheight /= tmp_win->hints.height_inc;
	}

	sprintf(str, " %4d x %-4d ", dwidth, dheight);
	XRaiseWindow(dpy, Scr->SizeWindow);

	Draw3DBorder(Scr->SizeWindow, 0, 0,
	             Scr->SizeStringOffset + Scr->SizeStringWidth + SIZE_HINDENT,
	             Scr->SizeFont.height + SIZE_VINDENT * 2,
	             2, Scr->DefaultC, off, false, false);

	FB(Scr->DefaultC.fore, Scr->DefaultC.back);
	XmbDrawImageString(dpy, Scr->SizeWindow, Scr->SizeFont.font_set,
	                   Scr->NormalGC, Scr->SizeStringOffset,
	                   Scr->SizeFont.ascent + SIZE_VINDENT, str, 13);
}

/***********************************************************************
 *
 *  Procedure:
 *      EndResize - finish the resize operation
 *
 ***********************************************************************
 */

void EndResize(void)
{
	TwmWindow *tmp_win;

#ifdef DEBUG
	fprintf(stderr, "EndResize\n");
#endif

	MoveOutline(Scr->Root, 0, 0, 0, 0, 0, 0);
	XUnmapWindow(dpy, Scr->SizeWindow);

	tmp_win = GetTwmWindow(ResizeWindow);
	if(!tmp_win) {
		return;
	}

	ConstrainSize(tmp_win, &dragWidth, &dragHeight);

	if(dragWidth != tmp_win->frame_width ||
	                dragHeight != tmp_win->frame_height) {
		unzoom(tmp_win);
	}

	SetupWindow(tmp_win, dragx - tmp_win->frame_bw, dragy - tmp_win->frame_bw,
	            dragWidth, dragHeight, -1);

	if(tmp_win->isiconmgr) {
		int ncols = tmp_win->iconmgrp->cur_columns;
		if(ncols == 0) {
			ncols = 1;
		}

		tmp_win->iconmgrp->width = (int)(((dragWidth - 2 * tmp_win->frame_bw3D) *
		                                  (long) tmp_win->iconmgrp->columns)
		                                 / ncols);
		PackIconManager(tmp_win->iconmgrp);
	}

	if(!Scr->NoRaiseResize) {
		OtpRaise(tmp_win, WinWin);
		WMapRaise(tmp_win);
	}

	UninstallRootColormap();

	ResizeWindow = None;
}

void MenuEndResize(TwmWindow *tmp_win)
{
	MoveOutline(Scr->Root, 0, 0, 0, 0, 0, 0);
	XUnmapWindow(dpy, Scr->SizeWindow);
	ConstrainSize(tmp_win, &dragWidth, &dragHeight);
	AddingX = dragx - tmp_win->frame_bw;
	AddingY = dragy - tmp_win->frame_bw;
	AddingW = dragWidth;
	AddingH = dragHeight;
	SetupWindow(tmp_win, AddingX, AddingY, AddingW, AddingH, -1);
}


/***********************************************************************
 *
 *  Procedure:
 *      AddEndResize - finish the resize operation for AddWindo<w
 *
 ***********************************************************************
 */

void AddEndResize(TwmWindow *tmp_win)
{

#ifdef DEBUG
	fprintf(stderr, "AddEndResize\n");
#endif

	ConstrainSize(tmp_win, &dragWidth, &dragHeight);
	AddingX = dragx;
	AddingY = dragy;
	AddingW = dragWidth + (2 * tmp_win->frame_bw);
	AddingH = dragHeight + (2 * tmp_win->frame_bw);
}

/***********************************************************************
 *
 *  Procedure:
 *      ConstrainSize - adjust the given width and height to account for the
 *              constraints imposed by size hints
 *
 *      The general algorithm, especially the aspect ratio stuff, is
 *      borrowed from uwm's CheckConsistency routine.
 *
 ***********************************************************************/

void ConstrainSize(TwmWindow *tmp_win,
                   unsigned int *widthp, unsigned int *heightp)
{
#define makemult(a,b) ((b==1) ? (a) : (((int)((a)/(b))) * (b)) )

	int minWidth, minHeight, maxWidth, maxHeight, xinc, yinc, delta;
	int baseWidth, baseHeight;
	int dwidth = *widthp, dheight = *heightp;


	dwidth  -= 2 * tmp_win->frame_bw3D;
	dheight -= (tmp_win->title_height + 2 * tmp_win->frame_bw3D);

	if(tmp_win->hints.flags & PMinSize) {
		minWidth = tmp_win->hints.min_width;
		minHeight = tmp_win->hints.min_height;
	}
	else if(tmp_win->hints.flags & PBaseSize) {
		minWidth = tmp_win->hints.base_width;
		minHeight = tmp_win->hints.base_height;
	}
	else {
		minWidth = minHeight = 1;
	}

	if(tmp_win->hints.flags & PBaseSize) {
		baseWidth = tmp_win->hints.base_width;
		baseHeight = tmp_win->hints.base_height;
	}
	else if(tmp_win->hints.flags & PMinSize) {
		baseWidth = tmp_win->hints.min_width;
		baseHeight = tmp_win->hints.min_height;
	}
	else {
		baseWidth = baseHeight = 0;
	}


	if(tmp_win->hints.flags & PMaxSize) {
		maxWidth = min(Scr->MaxWindowWidth, tmp_win->hints.max_width);
		maxHeight = min(Scr->MaxWindowHeight, tmp_win->hints.max_height);
	}
	else {
		maxWidth = Scr->MaxWindowWidth;
		maxHeight = Scr->MaxWindowHeight;
	}

	if(tmp_win->hints.flags & PResizeInc) {
		xinc = tmp_win->hints.width_inc;
		yinc = tmp_win->hints.height_inc;
		if(xinc == 0) {
			xinc = 1;
		}
		if(yinc == 0) {
			yinc = 1;
		}
	}
	else {
		xinc = yinc = 1;
	}

	/*
	 * First, clamp to min and max values
	 */
	if(dwidth < minWidth) {
		dwidth = minWidth;
	}
	if(dheight < minHeight) {
		dheight = minHeight;
	}

	if(dwidth > maxWidth) {
		dwidth = maxWidth;
	}
	if(dheight > maxHeight) {
		dheight = maxHeight;
	}


	/*
	 * Second, fit to base + N * inc
	 */
	dwidth = ((dwidth - baseWidth) / xinc * xinc) + baseWidth;
	dheight = ((dheight - baseHeight) / yinc * yinc) + baseHeight;


	/*
	 * Third, adjust for aspect ratio
	 */
	/*
	 * The math looks like this:
	 *
	 * minAspectX    dwidth     maxAspectX
	 * ---------- <= ------- <= ----------
	 * minAspectY    dheight    maxAspectY
	 *
	 * If that is multiplied out, then the width and height are
	 * invalid in the following situations:
	 *
	 * minAspectX * dheight > minAspectY * dwidth
	 * maxAspectX * dheight < maxAspectY * dwidth
	 *
	 */

	if(tmp_win->hints.flags & PAspect) {
		int minAspectX = tmp_win->hints.min_aspect.x;
		int minAspectY = tmp_win->hints.min_aspect.y;
		int maxAspectX = tmp_win->hints.max_aspect.x;
		int maxAspectY = tmp_win->hints.max_aspect.y;

		if(minAspectX && minAspectY && maxAspectX && maxAspectY) {
			if(minAspectX * dheight > minAspectY * dwidth) {
				delta = makemult(minAspectX * dheight / minAspectY - dwidth,
				                 xinc);
				if(dwidth + delta <= maxWidth) {
					dwidth += delta;
				}
				else {
					delta = makemult(dheight - dwidth * minAspectY / minAspectX,
					                 yinc);
					if(dheight - delta >= minHeight) {
						dheight -= delta;
					}
				}
			}

			if(maxAspectX * dheight < maxAspectY * dwidth) {
				delta = makemult(dwidth * maxAspectY / maxAspectX - dheight,
				                 yinc);
				if(dheight + delta <= maxHeight) {
					dheight += delta;
				}
				else {
					delta = makemult(dwidth - maxAspectX * dheight / maxAspectY,
					                 xinc);
					if(dwidth - delta >= minWidth) {
						dwidth -= delta;
					}
				}
			}
		}
	}


	/*
	 * Fourth, account for border width and title height
	 */
	*widthp = dwidth + 2 * tmp_win->frame_bw3D;
	*heightp = dheight + tmp_win->title_height + 2 * tmp_win->frame_bw3D;
}




/**********************************************************************
 *  Rutgers mod #1   - rocky.
 *  Procedure:
 *         fullzoom - zooms window to full height of screen or
 *                    to full height and width of screen. (Toggles
 *                    so that it can undo the zoom - even when switching
 *                    between fullzoom and vertical zoom.)
 *
 *  Inputs:
 *         tmp_win - the TwmWindow pointer
 *
 *
 **********************************************************************
 */

void fullzoom(TwmWindow *tmp_win, int func)
{
	Window      junkRoot;
	unsigned int junkbw, junkDepth;
	int tmpX, tmpY, tmpW, tmpH;

	/*
	 * All our callers [need to] do this, so moving it here saves a few
	 * lines in some places around the calling, and when redundant it
	 * just wastes a comparison, so it's cheap.
	 */
	if(tmp_win->squeezed) {
		XBell(dpy, 0);
		return;
	}


	XGetGeometry(dpy, (Drawable) tmp_win->frame, &junkRoot,
	             &dragx, &dragy, (unsigned int *)&dragWidth, (unsigned int *)&dragHeight,
	             &junkbw,
	             &junkDepth);

	/*
	 * Guard; if it was already not zoomed, and we're asking to unzoom
	 * it, just finish right away.  This saves us work, but also avoids
	 * really bad side effects in some cases.  e.g., if we try to
	 * ZOOM_NONE a window that's never been ZOOM'd, tmp_win->save_* will
	 * all be 0, so we'd wind up resizing it to a point.  It's possible
	 * for that to happen via e.g. an EWMH message removing a _FULLSCREEN
	 * or similar attribute; that can then call into us telling us not to
	 * zoom, on a window that's never been zoomed.
	 *
	 * This wouldn't protect us if somehow it was zoomed but hadn't set
	 * that data, but I don't see how that can happen.  Worry about that
	 * when it does.
	 */
	if(func == ZOOM_NONE && tmp_win->zoomed == ZOOM_NONE) {
		return;
	}

	if(tmp_win->zoomed == func) {
		/* It was already zoomed this way, unzoom it */
		dragHeight = tmp_win->save_frame_height;
		dragWidth = tmp_win->save_frame_width;
		dragx = tmp_win->save_frame_x;
		dragy = tmp_win->save_frame_y;

		unzoom(tmp_win);

		/* XXX _should_ it be falling through here? */
	}
	else {
		RLayout *borderedLayout = NULL;
		RArea area, finalArea = RAreaInvalid();
		int frame_bw_times_2;

#ifdef WINBOX
		if(tmp_win->winbox) {
			XWindowAttributes winattrs;
			if(XGetWindowAttributes(dpy, tmp_win->winbox->window, &winattrs)) {
				borderedLayout = RLayoutNew(
				                         RAreaListNew(1,
				                                      RAreaNew(winattrs.x,
				                                                      winattrs.y,
				                                                      winattrs.width,
				                                                      winattrs.height),
				                                      NULL));
			}
		}
#endif
		if(borderedLayout == NULL) {
			borderedLayout = Scr->BorderedLayout;
		}

		if(tmp_win->zoomed == ZOOM_NONE) {
			tmp_win->save_frame_x = dragx;
			tmp_win->save_frame_y = dragy;
			tmp_win->save_frame_width = dragWidth;
			tmp_win->save_frame_height = dragHeight;
		}
		tmp_win->zoomed = func;

		frame_bw_times_2 = 2 * tmp_win->frame_bw;

		area = RAreaNew(dragx, dragy, dragWidth, dragHeight);

		switch(func) {
			case ZOOM_NONE:
				break;
			case F_XZOOM:
				finalArea = RLayoutFullVert(borderedLayout, &area);
			/* fall through */
			case F_ZOOM:
				if(!RAreaIsValid(&finalArea)) {
					finalArea = RLayoutFullVert1(borderedLayout, &area);
				}
				dragy = finalArea.y;
				dragHeight = finalArea.height - frame_bw_times_2;
				break;
			case F_XHORIZOOM:
				finalArea = RLayoutFullHoriz(borderedLayout, &area);
			/* fall through */
			case F_HORIZOOM:
				if(!RAreaIsValid(&finalArea)) {
					finalArea = RLayoutFullHoriz1(borderedLayout, &area);
				}
				dragx = finalArea.x;
				dragWidth = finalArea.width - frame_bw_times_2;
				break;
			case F_XFULLZOOM:
				finalArea = RLayoutFull(borderedLayout, &area);
			/* fall through */
			case F_FULLZOOM:
				if(!RAreaIsValid(&finalArea)) {
					finalArea = RLayoutFull1(borderedLayout, &area);
				}
				dragx = finalArea.x;
				dragy = finalArea.y;
				dragWidth = finalArea.width - frame_bw_times_2;
				dragHeight = finalArea.height - frame_bw_times_2;
				break;
			case F_XLEFTZOOM:
				dragx = RLayoutFindLeftEdge(borderedLayout, &area);
				dragWidth += area.x - dragx;
				// TODO make it visible if hidden
				break;
			case F_LEFTZOOM:
				dragx = RLayoutFindMonitorLeftEdge(borderedLayout, &area);
				dragWidth += area.x - dragx;
				// TODO make it visible if hidden
				break;
			case F_XRIGHTZOOM: {
				int limit = RLayoutFindRightEdge(borderedLayout, &area);
				dragWidth = limit - area.x + 1 - frame_bw_times_2;
				// TODO make it visible if hidden
			}
			break;
			case F_RIGHTZOOM: {
				int limit = RLayoutFindMonitorRightEdge(borderedLayout, &area);
				dragWidth = limit - area.x + 1 - frame_bw_times_2;
				// TODO make it visible if hidden
			}
			break;
			case F_XTOPZOOM:
				dragy = RLayoutFindTopEdge(borderedLayout, &area);
				dragHeight += area.y - dragy;
				// TODO make it visible if hidden
				break;
			case F_TOPZOOM:
				dragy = RLayoutFindMonitorTopEdge(borderedLayout, &area);
				dragHeight += area.y - dragy;
				// TODO make it visible if hidden
				break;
			case F_XBOTTOMZOOM: {
				int limit = RLayoutFindBottomEdge(borderedLayout, &area);
				dragHeight = limit - area.y + 1 - frame_bw_times_2;
				// TODO make it visible if hidden
			}
			break;
			case F_BOTTOMZOOM: {
				int limit = RLayoutFindMonitorBottomEdge(borderedLayout, &area);
				dragHeight = limit - area.y + 1 - frame_bw_times_2;
				// TODO make it visible if hidden
			}
			break;
			case F_FULLSCREENZOOM:
			case F_XFULLSCREENZOOM: {
				int bw3D = tmp_win->frame_bw3D;
				int bw3D_times_2 = 2 * bw3D;
				int bw = tmp_win->frame_bw + bw3D;

				finalArea = func == F_XFULLSCREENZOOM
				            ? RLayoutFull(borderedLayout, &area)
				            : RLayoutFull1(borderedLayout, &area);
				dragx = finalArea.x - bw;
				dragy = finalArea.y - tmp_win->title_height - bw;
				dragWidth = finalArea.width + bw3D_times_2;
				dragHeight = finalArea.height + tmp_win->title_height + bw3D_times_2;

				/* and should ignore aspect ratio and size increments... */
#ifdef EWMH
				/* x-ref HandleFocusIn() comments for why we need this */
				OtpSetAflag(tmp_win, OTP_AFLAG_FULLSCREEN);
				OtpRestackWindow(tmp_win);
				/* the OtpRaise below is effectively already done here... */
#endif
			}
		}

		/* Temporary built layout? */
		if(borderedLayout != Scr->BorderedLayout) {
			RLayoutFree(borderedLayout);
		}
	}

	if(!Scr->NoRaiseResize && func != F_FULLSCREENZOOM) {
		OtpRaise(tmp_win, WinWin);
	}

	if(func != F_FULLSCREENZOOM) {
		ConstrainSize(tmp_win, &dragWidth, &dragHeight);
	}
#ifdef BETTERZOOM
	if(func == F_ZOOM) {
		if(dragy + dragHeight < tmp_win->save_frame_y + tmp_win->save_frame_height) {
			dragy = tmp_win->save_frame_y + tmp_win->save_frame_height - dragHeight;
		}
	}
#endif
	SetupWindow(tmp_win, dragx, dragy, dragWidth, dragHeight, -1);
	/* I don't understand the reason of this. Claude.
	    XUngrabPointer (dpy, CurrentTime);
	*/
	XUngrabServer(dpy);

	XQueryPointer(dpy,
	              tmp_win->w,
	              &junkRoot, &junkRoot,
	              &tmpX, &tmpY, &tmpW, &tmpH, &junkDepth);
	if(tmp_win->frame_x > tmpX ||
	                tmp_win->frame_x + tmp_win->frame_width < tmpX ||
	                tmp_win->frame_y > tmpY ||
	                tmp_win->frame_y + tmp_win->frame_height < tmpY) {
		XWarpPointer(dpy, Scr->Root, tmp_win->w, 0, 0, 0, 0, 0, 0);
	}

#ifdef EWMH
	/*
	 * Reset _NET_WM_STATE prop on the window.  It sets whichever state
	 * applies, not always the _MAXIMIZED_VERT we specify here.
	 */
	EwmhSet_NET_WM_STATE(tmp_win, EWMH_STATE_MAXIMIZED_VERT);
#endif
}

/*
 * Forget about a window being zoomed.
 * This also needs to undo the special effects of F_FULLSCREENZOOM.
 */
void unzoom(TwmWindow *tmp_win)
{
	if(tmp_win->zoomed != ZOOM_NONE) {
#ifdef EWMH
		if(tmp_win->zoomed == F_FULLSCREENZOOM) {
			OtpClearAflag(tmp_win, OTP_AFLAG_FULLSCREEN);
			OtpRestackWindow(tmp_win);
		}
#endif

		tmp_win->zoomed = ZOOM_NONE;
	}
}

void savegeometry(TwmWindow *tmp_win)
{
	if(!tmp_win) {
		return;
	}
	tmp_win->savegeometry.x      = tmp_win->frame_x;
	tmp_win->savegeometry.y      = tmp_win->frame_y;
	tmp_win->savegeometry.width  = tmp_win->frame_width;
	tmp_win->savegeometry.height = tmp_win->frame_height;
}

void restoregeometry(TwmWindow *tmp_win)
{
	int x, y;
	unsigned int w, h;

	if(!tmp_win) {
		return;
	}
	if(tmp_win->savegeometry.width == (unsigned int) - 1) {
		return;
	}
	x = tmp_win->savegeometry.x;
	y = tmp_win->savegeometry.y;
	w = tmp_win->savegeometry.width;
	h = tmp_win->savegeometry.height;
	SetupWindow(tmp_win, x, y, w, h, -1);
}


void ChangeSize(char *in_string, TwmWindow *tmp_win)
{
	int change = 0, size = 0;
	char *endptr;
	int rx, ry, wx, wy, mr;
	Window  rr, cr;

	if(Isdigit(in_string[0])) {
		/* Handle the case f.changesize "640x480" */
		wx = strtol(in_string, &endptr, 10);
		if(*endptr++ != 'x') {
			fprintf(stderr,
			        "%s: Bad argument to f.changesize: \"%s\" (pattern \"640x480\")\n",
			        ProgramName, in_string);
			return;
		}
		wy = strtol(endptr, &endptr, 10);

		if(wy < tmp_win->title_height + 1) {
			wy = tmp_win->title_height + 1;
		}

		SetupWindow(tmp_win, tmp_win->frame_x, tmp_win->frame_y,
		            wx, wy + tmp_win->title_height, -1);
	}
	else {
		/* Handle the cases like f.changesize "right +10" */
		int cmdlen = 0;

		while(in_string[cmdlen] != ' ' && in_string[cmdlen] != '\0') {
			cmdlen++;
		}

		if(in_string[cmdlen] != ' ') {
			fprintf(stderr,
			        "%s: Bad argument to f.changesize: \"%s\" (sizechange missing)\n",
			        ProgramName, in_string);
			return;
		}

		change = strtol(in_string + cmdlen + 1, &endptr, 10);
		if(*endptr != 0) {
			fprintf(stderr,
			        "%s: Bad argument to f.changesize: \"%s\" (sizechange not a number)\n",
			        ProgramName, in_string);
			return;
		}

		if(strncmp("bottom", in_string, cmdlen) == 0) {
			size = tmp_win->frame_height + change;

			if(size < (tmp_win->title_height + 1)) {
				size = tmp_win->title_height + 1;
			}

			SetupWindow(tmp_win, tmp_win->frame_x, tmp_win->frame_y,
			            tmp_win->frame_width, size,
			            -1);

			XQueryPointer(dpy, tmp_win->w, &rr, &cr, &rx, &ry, &wx, &wy,
			              (unsigned int *)&mr);

			if((wy + tmp_win->title_height) > size) {
				XWarpPointer(dpy, None, tmp_win->w, 0, 0, 0, 0, 0, 0);
			}
		}
		else if(strncmp("top", in_string, cmdlen) == 0) {
			size = tmp_win->frame_height + change;

			if(size < (tmp_win->title_height + 1)) {
				size = tmp_win->title_height + 1;
			}

			SetupWindow(tmp_win, tmp_win->frame_x, (tmp_win->frame_y - change),
			            tmp_win->frame_width, size,
			            -1);

			XQueryPointer(dpy, tmp_win->w, &rr, &cr, &rx, &ry, &wx, &wy,
			              (unsigned int *)&mr);

			if((wy + tmp_win->title_height) > size) {
				XWarpPointer(dpy, None, tmp_win->w, 0, 0, 0, 0, 0, 0);
			}


		}
		else if(strncmp("left", in_string, cmdlen) == 0) {
			size = tmp_win->frame_width + change;

			if(size < 1) {
				size = 1;
			}

			SetupWindow(tmp_win, (tmp_win->frame_x - change), tmp_win->frame_y,
			            size, tmp_win->frame_height,
			            -1);

			XQueryPointer(dpy, tmp_win->w, &rr, &cr, &rx, &ry, &wx, &wy,
			              (unsigned int *)&mr);

			if(wx > size) {
				XWarpPointer(dpy, None, tmp_win->w, 0, 0, 0, 0, 0, 0);
			}


		}
		else if(strncmp("right", in_string, cmdlen) == 0) {
			size = tmp_win->frame_width + change;

			if(size < 1) {
				size = 1;
			}

			SetupWindow(tmp_win, tmp_win->frame_x, tmp_win->frame_y,
			            size, tmp_win->frame_height,
			            -1);

			XQueryPointer(dpy, tmp_win->w, &rr, &cr, &rx, &ry, &wx, &wy,
			              (unsigned int *)&mr);

			if(wx > size) {
				XWarpPointer(dpy, None, tmp_win->w, 0, 0, 0, 0, 0, 0);
			}

		}
		else {
			/* error */
			fprintf(stderr, "%s: Bad argument to f.changesize: \"%s\"\n (unknown border)",
			        ProgramName, in_string);
			return;
		}
	}
}


/***********************************************************************
 *
 *  Procedure:
 *      resizeFromCenter -
 *
 ***********************************************************************
 */
void
resizeFromCenter(Window w, TwmWindow *tmp_win)
{
	int lastx, lasty, bw2;

	bw2 = tmp_win->frame_bw * 2;
	AddingW = tmp_win->attr.width + bw2 + 2 * tmp_win->frame_bw3D;
	AddingH = tmp_win->attr.height + tmp_win->title_height + bw2 + 2 *
	          tmp_win->frame_bw3D;

	XGetGeometry(dpy, w, &JunkRoot, &origDragX, &origDragY,
	             &DragWidth, &DragHeight,
	             &JunkBW, &JunkDepth);

	XWarpPointer(dpy, None, w,
	             0, 0, 0, 0, DragWidth / 2, DragHeight / 2);
	XQueryPointer(dpy, Scr->Root, &JunkRoot,
	              &JunkChild, &JunkX, &JunkY,
	              &AddingX, &AddingY, &JunkMask);

	lastx = -10000;
	lasty = -10000;

	MenuStartResize(tmp_win, origDragX, origDragY, DragWidth, DragHeight);
	while(1) {
		XMaskEvent(dpy,
		           ButtonPressMask | PointerMotionMask | ExposureMask, &Event);

		if(Event.type == MotionNotify) {
			/* discard any extra motion events before a release */
			while(XCheckMaskEvent(dpy,
			                      ButtonMotionMask | ButtonPressMask, &Event))
				if(Event.type == ButtonPress) {
					break;
				}
		}

		if(Event.type == ButtonPress) {
			MenuEndResize(tmp_win);
			// Next line should be unneeded, done by MenuEndResize() ?
			XMoveResizeWindow(dpy, w, AddingX, AddingY, AddingW, AddingH);
			break;
		}

		if(Event.type != MotionNotify) {
			DispatchEvent2();
			if(Cancel) {
				// ...
				MenuEndResize(tmp_win);
				return;
			}
			continue;
		}

		/*
		 * XXX - if we are going to do a loop, we ought to consider
		 * using multiple GXxor lines so that we don't need to
		 * grab the server.
		 */
		XQueryPointer(dpy, Scr->Root, &JunkRoot, &JunkChild,
		              &JunkX, &JunkY, &AddingX, &AddingY, &JunkMask);

		if(lastx != AddingX || lasty != AddingY) {
			MenuDoResize(AddingX, AddingY, tmp_win);

			lastx = AddingX;
			lasty = AddingY;
		}

	}
}
