/****************************************************************************
 * This module is based on Twm, but has been siginificantly modified 
 * by Rob Nation 
 ****************************************************************************/
/*****************************************************************************/
/**       Copyright 1988 by Evans & Sutherland Computer Corporation,        **/
/**                          Salt Lake City, Utah                           **/
/**  Portions Copyright 1989 by the Massachusetts Institute of Technology   **/
/**                        Cambridge, Massachusetts                         **/
/**                                                                         **/
/**                           All Rights Reserved                           **/
/**                                                                         **/
/**    Permission to use, copy, modify, and distribute this software and    **/
/**    its documentation  for  any  purpose  and  without  fee is hereby    **/
/**    granted, provided that the above copyright notice appear  in  all    **/
/**    copies and that both  that  copyright  notice  and  this  permis-    **/
/**    sion  notice appear in supporting  documentation,  and  that  the    **/
/**    names of Evans & Sutherland and M.I.T. not be used in advertising    **/
/**    in publicity pertaining to distribution of the  software  without    **/
/**    specific, written prior permission.                                  **/
/**                                                                         **/
/**    EVANS & SUTHERLAND AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD    **/
/**    TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES  OF  MERCHANT-    **/
/**    ABILITY  AND  FITNESS,  IN  NO  EVENT SHALL EVANS & SUTHERLAND OR    **/
/**    M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAM-    **/
/**    AGES OR  ANY DAMAGES WHATSOEVER  RESULTING FROM LOSS OF USE, DATA    **/
/**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
/**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
/**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
/*****************************************************************************/


/***********************************************************************
 *
 * window resizing borrowed from the "wm" window manager
 *
 ***********************************************************************/
#include "../configure.h"

#include <stdio.h>
#include "fvwm.h"
#include "misc.h"
#include "screen.h"
#include "parse.h"

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

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

static int ymotion, xmotion;
static int last_width,last_height;
extern int menuFromFrameOrWindowOrTitlebar;
extern Window PressedW;

/****************************************************************************
 *
 * Starts a window resize operation
 *
 ****************************************************************************/
void resize_window(Window w,FvwmWindow *tmp_win, int val1, int val2, int val1_unit, int val2_unit)
{
  Bool finished = FALSE, done = FALSE;
  int x,y,delta_x,delta_y;
  Window ResizeWindow;
  extern int Stashed_X, Stashed_Y;
  Bool flags;
#ifndef NO_PAGER
  extern Bool pagerOn;
#endif

  if((w == None)||(tmp_win == NULL))
    return;

  /* Already checked this in functions.c, but its here too incase
   * there's a resize on initial placement. */
  if(check_allowed_function2(F_RESIZE,tmp_win) == 0)
    {
      XBell(dpy, Scr.screen);
      return;
    }
  /* can't resize icons */
  if(tmp_win->flags & ICONIFIED)
    return;

  ResizeWindow = tmp_win->frame;
  
  if((val1 != 0)&&(val2 != 0))
    {
      int width,height;

      dragWidth = val1*val1_unit/100;
      dragHeight = val2*val2_unit/100;

      ConstrainSize (tmp_win, &dragWidth, &dragHeight);
      SetupFrame (tmp_win, tmp_win->frame_x, 
		  tmp_win->frame_y ,dragWidth, dragHeight,FALSE);
      
      ResizeWindow = None; 
#ifndef NO_PAGER
      RedrawPager();
#endif
      return;
    }

  InstallRootColormap();
  if (menuFromFrameOrWindowOrTitlebar) 
    {
      /* warp the pointer to the cursor position from before menu appeared*/
      XWarpPointer(dpy, None, Scr.Root, 0, 0, 0, 0, Stashed_X,Stashed_Y);
      XFlush(dpy);
    }

  if(!GrabEm(MOVE))
    {
      XBell(dpy,Scr.screen);
      return;
    }

  if((!(Scr.flags & OpaqueResize))||
     ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
    XGrabServer(dpy);


  /* handle problems with edge-wrapping while resizing */
  flags = Scr.flags;
  Scr.flags &= ~(EdgeWrapX|EdgeWrapY);

#ifndef NO_PAGER
  pagerOn = False;
#endif
  XGetGeometry(dpy, (Drawable) ResizeWindow, &JunkRoot,
	       &dragx, &dragy, (unsigned int *)&dragWidth, 
	       (unsigned int *)&dragHeight, &JunkBW,&JunkDepth);

  dragx += tmp_win->bw;
  dragy += tmp_win->bw;
  origx = dragx;
  origy = dragy;
  origWidth = dragWidth;
  origHeight = dragHeight;
  ymotion=xmotion=0;

  /* pop up a resize dimensions window */
  XMapRaised(dpy, Scr.SizeWindow);
  last_width = 0;
  last_height = 0;
  DisplaySize(tmp_win, origWidth, origHeight,True);

  /* Get the current position to determine which border to resize */
  if((PressedW != Scr.Root)&&(PressedW != None))
    {
      if(PressedW == tmp_win->sides[0])   /* top */
	ymotion = 1;
      if(PressedW == tmp_win->sides[1])  /* right */
	xmotion = -1;
      if(PressedW == tmp_win->sides[2])  /* bottom */
	ymotion = -1;
      if(PressedW == tmp_win->sides[3])  /* left */
	xmotion = 1;
      if(PressedW == tmp_win->corners[0])  /* upper-left */
	{
	  ymotion = 1;
	  xmotion = 1;
	}
      if(PressedW == tmp_win->corners[1])  /* upper-right */
	{
	  xmotion = -1;
	  ymotion = 1;
	}
      if(PressedW == tmp_win->corners[2]) /* lower right */
	{
	  ymotion = -1;
	  xmotion = 1;
	}
      if(PressedW == tmp_win->corners[3])  /* lower left */
	{
	  ymotion = -1;
	  xmotion = -1;
	}
    }
  /* draw the rubber-band window */
  if((!(Scr.flags & OpaqueResize))||
     ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
    MoveOutline (Scr.Root, dragx - tmp_win->bw, dragy - tmp_win->bw, dragWidth + 2 * tmp_win->bw,
		 dragHeight + 2 * tmp_win->bw);

  /* loop to resize */
  while(!finished)
    {
      XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | KeyPressMask |
		 ButtonMotionMask | PointerMotionMask | ExposureMask,  &Event);
      StashEventTime(&Event);      

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

      done = FALSE;
      /* Handle a limited number of key press events to allow mouseless
       * operation */
      if(Event.type == KeyPress)
	Keyboard_shortcuts(&Event,ButtonRelease);
      switch(Event.type)
	{
	case ButtonPress:
	  XAllowEvents(dpy,ReplayPointer,CurrentTime);
	case KeyPress:
	  done = TRUE;
	  break;

	case ButtonRelease:
	  finished = TRUE;
	  done = TRUE;
	  break;

	case MotionNotify:
	  x = Event.xmotion.x_root;
	  y = Event.xmotion.y_root;
	  /* need to move the viewport */
	  HandlePaging(Scr.EdgeScrollX,Scr.EdgeScrollY,&x,&y,
		       &delta_x,&delta_y,False);
	  origx -= delta_x;
	  origy -= delta_y;
	  dragx -= delta_x;
	  dragy -= delta_y;

	  DoResize(x, y, tmp_win);
	  done = TRUE;
	default:
	  break;
	}
      if(!done)
	{
	  if((!(Scr.flags & OpaqueResize))||
	     ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
	    MoveOutline(Scr.Root,0,0,0,0);

	  DispatchEvent();

	  if((!(Scr.flags & OpaqueResize))||
	     ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
	    MoveOutline(Scr.Root, dragx - tmp_win->bw, dragy - tmp_win->bw,
		      dragWidth + 2 * tmp_win->bw, dragHeight + 2 * tmp_win->bw);

	}
    } 

  /* erase the rubber-band */
  if((!(Scr.flags & OpaqueResize))||
     ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
    MoveOutline(Scr.Root, 0, 0, 0, 0);

  /* pop down the size window */
  XUnmapWindow(dpy, Scr.SizeWindow);

#ifndef NO_PAGER
  pagerOn = True;
#endif
  ConstrainSize (tmp_win, &dragWidth, &dragHeight);
  SetupFrame (tmp_win, dragx - tmp_win->bw, 
	      dragy - tmp_win->bw, dragWidth, dragHeight,FALSE);

  UninstallRootColormap();
  ResizeWindow = None; 
  XUngrabServer(dpy);
  UngrabEm();
#ifndef NO_PAGER
  RedrawPager();
#endif
  Scr.flags |= flags & (EdgeWrapX|EdgeWrapY);
  return;
}



/***********************************************************************
 *
 *  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 fvwm window
 *
 ************************************************************************/
void DoResize(int x_root, int y_root, FvwmWindow *tmp_win)
{
  int action=0;
#ifndef NO_PAGER
  unsigned int width,height;
  int ww,wh;
  int wx,wy;
  int MaxH,MaxW;
  static int last_w = -10000, last_h = -10000;
#endif
  
  if ((y_root <= origy)||((ymotion == 1)&&(y_root < origy+origHeight-1)))
    {
      dragy = y_root;
      dragHeight = origy + origHeight - y_root;
      action = 1;
      ymotion = 1;
    }
  else if ((y_root >= origy + origHeight - 1)||
	   ((ymotion == -1)&&(y_root > origy)))
    {
      dragy = origy;
      dragHeight = 1 + y_root - dragy;
      action = 1;
      ymotion = -1;
    }
  
  if ((x_root <= origx)||
      ((xmotion == 1)&&(x_root < origx + origWidth - 1)))
    {
      dragx = x_root;
      dragWidth = origx + origWidth - x_root;
      action = 1;
      xmotion = 1;
    }
  if ((x_root >= origx + origWidth - 1)||
    ((xmotion == -1)&&(x_root > origx)))
    {
      dragx = origx;
      dragWidth = 1 + x_root - origx;
      action = 1;
      xmotion = -1;
    }
  
  if (action) 
    {
      ConstrainSize (tmp_win, &dragWidth, &dragHeight);
      if (xmotion == 1)
	dragx = origx + origWidth - dragWidth;
      if (ymotion == 1)
	dragy = origy + origHeight - dragHeight;
      
#ifndef NO_PAGER
      /* update size of the pager_view window */
      if((Scr.FvwmPager != NULL)&&
	 (dragx < Scr.FvwmPager->frame_x + Scr.FvwmPager->frame_width)&&
	 (dragx+dragWidth >  Scr.FvwmPager->frame_x)&&
	 (dragy < Scr.FvwmPager->frame_y + Scr.FvwmPager->frame_height)&&
	 (dragy+dragHeight >  Scr.FvwmPager->frame_y)&&
	 ((!(Scr.flags & OpaqueResize))||
	  ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED)))))
	 MoveOutline(Scr.Root,0,0,0,0);
      if(Scr.FvwmPager)
	{
	  width = Scr.FvwmPager->frame_width - 2*Scr.FvwmPager->boundary_width;
	  height = Scr.FvwmPager->frame_height - Scr.FvwmPager->title_height 
	    - 2*Scr.FvwmPager->boundary_width;
	  
	  MaxW = Scr.VxMax + Scr.MyDisplayWidth;
	  MaxH = Scr.VyMax + Scr.MyDisplayHeight;
	  
	  if(!(tmp_win->flags & STICKY)&&
	     (!(tmp_win->flags & ICONIFIED)||(!(Scr.flags & SuppressIcons)))&&
	     (!(tmp_win->flags & ICONIFIED)||(!(Scr.flags & StickyIcons))))
	    {
	      /* show the actual window */
	      wx = (dragx + Scr.Vx)*(int)width/MaxW;
	      wy = (dragy + Scr.Vy)*(int)height/MaxH;
	      ww = dragWidth*(int)width/MaxW;
	      wh = dragHeight*(int)height/MaxH;

	      if((last_w - ww >= 2)||(last_w - ww <= -2)||
		 (last_h - wh >= 2)||(last_h - wh <= -2))
		{
		  if(ww<2)ww=2;
		  if(wh<2)wh=2;
		  XMoveResizeWindow(dpy, tmp_win->pager_view, wx, wy, ww, wh);
		  last_h = wh;
		  last_w = ww;
		}
	    }
	}
#endif

      if((!(Scr.flags & OpaqueResize))||
	 ((Scr.flags & OpaqueResize)&&(!(tmp_win->flags & MAPPED))))
	{
	  MoveOutline(Scr.Root, dragx - tmp_win->bw,dragy - tmp_win->bw,
		  dragWidth + 2 * tmp_win->bw, dragHeight + 2 * tmp_win->bw);
	}
      else
	{
	  SetupFrame (tmp_win, dragx - tmp_win->bw, 
		      dragy - tmp_win->bw, dragWidth, dragHeight,FALSE);  
	}
    }
  DisplaySize(tmp_win, dragWidth, dragHeight,False);
}



/***********************************************************************
 *
 *  Procedure:
 *      DisplaySize - display the size in the dimensions window
 *
 *  Inputs:
 *      tmp_win - the current fvwm window
 *      width   - the width of the rubber band
 *      height  - the height of the rubber band
 *
 ***********************************************************************/
void DisplaySize(FvwmWindow *tmp_win, int width, int height,Bool Init)
{
  char str[100];
  int dwidth,dheight,offset;

  if (last_width == width && last_height == height)
    return;
  
  last_width = width;
  last_height = height;
  
  dheight = height - tmp_win->title_height - 2*tmp_win->boundary_width;
  dwidth = width - 2*tmp_win->boundary_width;
  
  dwidth -= tmp_win->hints.base_width;
  dheight -= tmp_win->hints.base_height;
  dwidth /= tmp_win->hints.width_inc;
  dheight /= tmp_win->hints.height_inc;
  
  (void) sprintf (str, " %4d x %-4d ", dwidth, dheight);
  offset = (Scr.SizeStringWidth + SIZE_HINDENT*2
    - XTextWidth(Scr.StdFont.font,str,strlen(str)))/2;  
  if(Init)
    {
      XClearWindow(dpy,Scr.SizeWindow);
      if(Scr.d_depth >= 2)
	RelieveWindow(tmp_win,
		      Scr.SizeWindow,0,0,Scr.SizeStringWidth+ SIZE_HINDENT*2,
		      Scr.StdFont.height + SIZE_VINDENT*2,
		      Scr.StdReliefGC,Scr.StdShadowGC,FULL_HILITE);
    }
  else
    {
      XClearArea(dpy,Scr.SizeWindow,SIZE_HINDENT,SIZE_VINDENT,Scr.SizeStringWidth,
		 Scr.StdFont.height,False);
    }

  XDrawString (dpy, Scr.SizeWindow, Scr.NormalGC,
	       offset, Scr.StdFont.font->ascent + SIZE_VINDENT, str, 13);

}

/***********************************************************************
 *
 *  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 (FvwmWindow *tmp_win, int *widthp, int *heightp)
{
#define makemult(a,b) ((b==1) ? (a) : (((int)((a)/(b))) * (b)) )
#define _min(a,b) (((a) < (b)) ? (a) : (b))
    int minWidth, minHeight, maxWidth, maxHeight, xinc, yinc, delta;
    int baseWidth, baseHeight;
    int dwidth = *widthp, dheight = *heightp;

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

    minWidth = tmp_win->hints.min_width;
    minHeight = tmp_win->hints.min_height;
    
    baseWidth = tmp_win->hints.base_width;
    baseHeight = tmp_win->hints.base_height;

    maxWidth = tmp_win->hints.max_width;
    maxHeight =  tmp_win->hints.max_height;

/*    maxWidth = Scr.VxMax + Scr.MyDisplayWidth;
    maxHeight = Scr.VyMax + Scr.MyDisplayHeight;*/

    xinc = tmp_win->hints.width_inc;
    yinc = tmp_win->hints.height_inc;
    
    /*
     * 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
     */
#define maxAspectX tmp_win->hints.max_aspect.x
#define maxAspectY tmp_win->hints.max_aspect.y
#define minAspectX tmp_win->hints.min_aspect.x
#define minAspectY tmp_win->hints.min_aspect.y
    /*
     * 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)
      {
        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->boundary_width;
    *heightp = dheight + tmp_win->title_height + 2*tmp_win->boundary_width;
    return;
}


/***********************************************************************
 *
 *  Procedure:
 *	MoveOutline - move a window outline
 *
 *  Inputs:
 *	root	    - the window we are outlining
 *	x	    - upper left x coordinate
 *	y	    - upper left y coordinate
 *	width	    - the width of the rectangle
 *	height	    - the height of the rectangle
 *
 ***********************************************************************/
void MoveOutline(Window root, int x, int  y, int  width, int height)
{
  static int lastx = 0;
  static int lasty = 0;
  static int lastWidth = 0;
  static int lastHeight = 0;
  XRectangle rects[5];

  if (x == lastx && y == lasty && width == lastWidth && height == lastHeight)
    return;
    
  /* undraw the old one, if any */
  if (lastWidth || lastHeight)	
    {
      rects[0].x = lastx;
      rects[0].y = lasty;
      rects[0].width = lastWidth;
      rects[0].height = lastHeight;
      rects[1].x = lastx+1;
      rects[1].y = lasty+1;
      rects[1].width = lastWidth-2;
      rects[1].height = lastHeight-2;
      rects[2].x = lastx+2;
      rects[2].y = lasty+2;
      rects[2].width = lastWidth-4;
      rects[2].height = lastHeight-4;
      rects[3].x = lastx+3;
      rects[3].y = lasty+3 + (lastHeight-6)/3;
      rects[3].width = lastWidth-6;
      rects[3].height = (lastHeight-6)/3;
      rects[4].x = lastx+3 + (lastWidth-6)/3;
      rects[4].y = lasty+3;
      rects[4].width = (lastWidth-6)/3;
      rects[4].height = (lastHeight-6);
      XDrawRectangles(dpy,Scr.Root,Scr.DrawGC,rects,5);
    }
  
  lastx = x;
  lasty = y;
  lastWidth = width;
  lastHeight = height;
  
  /* draw the new one, if any */
  if (lastWidth || lastHeight)	
    {
      rects[0].x = lastx;
      rects[0].y = lasty;
      rects[0].width = lastWidth;
      rects[0].height = lastHeight;
      rects[1].x = lastx+1;
      rects[1].y = lasty+1;
      rects[1].width = lastWidth-2;
      rects[1].height = lastHeight-2;
      rects[2].x = lastx+2;
      rects[2].y = lasty+2;
      rects[2].width = lastWidth-4;
      rects[2].height = lastHeight-4;
      rects[3].x = lastx+3;
      rects[3].y = lasty+3 + (lastHeight-6)/3;
      rects[3].width = lastWidth-6;
      rects[3].height = (lastHeight-6)/3;
      rects[4].x = lastx+3 + (lastWidth-6)/3;
      rects[4].y = lasty+3;
      rects[4].width = (lastWidth-6)/3;
      rects[4].height = (lastHeight-6);
      XDrawRectangles(dpy,Scr.Root,Scr.DrawGC,rects,5);      
    }
}


