/*
 * resize.cc
 *
 * Copyright (C) 1995-2001 Kenichi Kourai
 *
 * This program 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.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with qvwm; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#ifdef USE_SHAPE
#include <X11/extensions/shape.h>
#endif
#include "main.h"
#include "misc.h"
#include "qvwm.h"
#include "event.h"
#include "taskbar.h"
#include "tbutton.h"
#include "qvwmrc.h"
#include "paging.h"
#include "pager.h"
#include "mini.h"
#include "info_display.h"

#ifdef USE_SHAPE
static Bool ResizeWithShape(Display* display, XEvent* ev, char* arg);
#endif

/*
 * ResizeWindow --
 *   Resize the window with mouse or keyboard.
 */
void Qvwm::ResizeWindow(unsigned int pos, Bool mouseResize)
{
  XEvent ev;
  Rect rcNew, rcReal;
  Dim dNeed;
  Point ptBase, ptNew, ptMove;
  Window junkRoot;
  unsigned int junkBW, junkDepth;
  Bool first = True;
  Bool pointer = False;
  int borderWidth, topBorder, titleHeight, titleEdge;
  int ctype;

  if (this == rootQvwm)
    return;

  if (!CheckStatus(PRESS_FRAME))
    return;

  XGrabKeyboard(display, root, True, GrabModeAsync, GrabModeAsync,
		CurrentTime);
  if (ClickToFocus)
    XSetInputFocus(display, root, RevertToParent, CurrentTime);
  if (!OpaqueResize)
    XGrabServer(display);
  
  XGetGeometry(display, frame, &junkRoot, &rcNew.x, &rcNew.y,
	       (unsigned int *)&rcNew.width, (unsigned int *)&rcNew.height,
	       &junkBW, &junkDepth);

  if (!OpaqueResize)
    XDrawRectangle(display, root, gcXor,
		   rcNew.x, rcNew.y, rcNew.width, rcNew.height);

  /*
   * Remember base line.
   */
  if (pos & P_LEFT)
    ptBase.x = rcNew.x + rcNew.width;
  else if (pos & P_RIGHT)
    ptBase.x = rcNew.x;

  if (pos & P_TOP)
    ptBase.y = rcNew.y + rcNew.height;
  else if (pos & P_BOTTOM)
    ptBase.y = rcNew.y;

  /*
   * Calculate necessary frame size.
   */
  GetBorderAndTitle(borderWidth, topBorder, titleHeight, titleEdge);
  
  dNeed.width = borderWidth * 2;
  dNeed.height = borderWidth + topBorder + titleHeight + titleEdge;

  if (UseInfoDisplay) {
    Rect rect(rcNew.x + paging->origin.x, rcNew.y + paging->origin.y,
	      rcNew.width, rcNew.height);
    infoDisp->NotifyShape(rect);
    infoDisp->Map();
  }

  while (1) {
#ifdef USE_SHAPE
    if (OpaqueResize)
      XIfEvent(display, &ev, ResizeWithShape, NULL);
    else
#endif
    XMaskEvent(display, KeyPressMask | PointerMotionMask | ButtonReleaseMask |
	       ButtonPressMask | ExposureMask, &ev);
    switch (ev.type) {
    case KeyPress:
      // when resizing with mouse, ignore key events
      if (mouseResize)
	break;

      if (first) {
	pointer = True;
	XGrabPointer(display, root, True, ButtonPressMask | PointerMotionMask,
		     GrabModeAsync, GrabModeAsync, root, None, CurrentTime);
      }
      ptNew = Point(ev.xkey.x_root, ev.xkey.y_root);

      switch (XKeycodeToKeysym(display, ev.xkey.keycode, 0)) {
      case XK_Up:
	if (first) {
	  pos = P_TOP;
	  ptBase.y = rcNew.y + rcNew.height;
	  ptNew.y = rcNew.y;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[Y_RESIZE]);
	  first = False;
	}
	if (pos == P_LEFT) {
	  pos |= P_TOP;
	  ptBase.y = rcNew.y + rcNew.height;
	  ptNew.y = rcNew.y;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[RD_RESIZE]);
	}
	else if (pos == P_RIGHT) {
	  pos |= P_TOP;
	  ptBase.y = rcNew.y + rcNew.height;
	  ptNew.y = rcNew.y;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[LD_RESIZE]);
	}
	ptNew.y -= MOVEMENT;
	break;

      case XK_Down:
	if (first) {
	  pos = P_BOTTOM;
	  ptBase.y = rcNew.y;
	  ptNew.y = rcNew.y + rcNew.height;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[Y_RESIZE]);
	  first = False;
	}
	if (pos == P_LEFT) {
	  pos |= P_BOTTOM;
	  ptBase.y = rcNew.y;
	  ptNew.y = rcNew.y + rcNew.height;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[LD_RESIZE]);
	}
	else if (pos == P_RIGHT) {
	  pos |= P_BOTTOM;
	  ptBase.y = rcNew.y;
	  ptNew.y = rcNew.y + rcNew.height;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[RD_RESIZE]);
	}
	ptNew.y += MOVEMENT;
	break;

      case XK_Right:
	if (first) {
	  pos = P_RIGHT;
	  ptBase.x = rcNew.x;
	  ptNew.x = rcNew.x + rcNew.width;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[X_RESIZE]);
	  first = False;
	}
	if (pos == P_TOP) {
	  pos |= P_RIGHT;
	  ptBase.x = rcNew.x;
	  ptNew.x = rcNew.x + rcNew.width;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[LD_RESIZE]);
	}
	else if (pos == P_BOTTOM) {
	  pos |= P_RIGHT;
	  ptBase.x = rcNew.x;
	  ptNew.x = rcNew.x + rcNew.width;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[RD_RESIZE]);
	}
	ptNew.x += MOVEMENT;
	break;

      case XK_Left:
	if (first) {
	  pos = P_LEFT;
	  ptBase.x = rcNew.x + rcNew.width;
	  ptNew.x = rcNew.x;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[X_RESIZE]);
	  first = False;
	}
	if (pos == P_TOP) {
	  pos |= P_LEFT;
	  ptBase.x = rcNew.x + rcNew.width;
	  ptNew.x = rcNew.x;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[RD_RESIZE]);
	}
	else if (pos == P_BOTTOM) {
	  pos |= P_LEFT;
	  ptBase.x = rcNew.x + rcNew.width;
	  ptNew.x = rcNew.x;
	  XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
	  XDefineCursor(display, root, cursor[LD_RESIZE]);
	}
	ptNew.x -= MOVEMENT;
	break;

      case XK_Return:
	goto decide;
      }
      XWarpPointer(display, None, root, 0, 0, 0, 0, ptNew.x, ptNew.y);
      // continue

    case MotionNotify:
      {
	Window w = ev.xmotion.window;
	XEvent ev;
	while (XCheckTypedWindowEvent(display, w, MotionNotify, &ev))
	  ;
      }

      if (!OpaqueResize)
	XDrawRectangle(display, root, gcXor, rcNew.x, rcNew.y,
		       rcNew.width, rcNew.height);

      if (ev.type == MotionNotify)
	ptNew = Point(ev.xbutton.x_root, ev.xbutton.y_root);

      ASSERT(paging);

      ptMove = paging->HandlePaging(ptNew);
      ptNew -= ptMove;
      ptBase -= ptMove;
      rcNew.x -= ptMove.x;
      rcNew.y -= ptMove.y;
	
      FixWindowShape(rcNew, pos, ptBase, ptNew, dNeed);

      if (OpaqueResize) {
	rcReal = Rect(rcNew.x + paging->origin.x, rcNew.y + paging->origin.y,
		      rcNew.width, rcNew.height);
	ConfigureNewRect(rcReal, !FullOpaque);
	RedrawWindow(!FullOpaque);
      }
      else
	XDrawRectangle(display, root, gcXor, rcNew.x, rcNew.y,
		       rcNew.width, rcNew.height);

      if (UseInfoDisplay) {
	Rect rect(rcNew.x + paging->origin.x, rcNew.y + paging->origin.y,
		  rcNew.width, rcNew.height);
	infoDisp->NotifyShape(rect);
      }

      break;
      
    case ButtonRelease:
      if (LockDragState) {
	pointer = True;
	if (pos == P_LEFT || pos == P_RIGHT)
	  ctype = X_RESIZE;
	else if (pos == P_TOP || pos == P_BOTTOM)
	  ctype = Y_RESIZE;
	else if (pos == (P_TOP | P_LEFT) || pos == (P_BOTTOM | P_RIGHT))
	  ctype = RD_RESIZE;
	else
	  ctype = LD_RESIZE;
	XGrabPointer(display, root, True, ButtonPressMask | PointerMotionMask,
		     GrabModeAsync, GrabModeAsync, root, cursor[ctype],
		     CurrentTime);
	break;
      }
      else
	goto decide;

    case ButtonPress:
      if (pointer)
	goto decide;
      break;

    case Expose:
      event.ExposeProc((const XExposeEvent &)ev);
      if (UseInfoDisplay)
	infoDisp->Draw();
      break;

#ifdef USE_SHAPE
    default:
      if (shapeSupport) {
	if (ev.type == event.shapeEventBase + ShapeNotify) {
	  const XShapeEvent& xev = (const XShapeEvent &)ev;
	  event.ShapeNotifyProc(xev);
	}
      }
      break;
#endif
    }
  }

 decide:
  XDefineCursor(display, root, cursor[SYS]);

  if (!OpaqueResize) {
    XDrawRectangle(display, root, gcXor, rcNew.x, rcNew.y,
		   rcNew.width, rcNew.height);
    XUngrabServer(display);
  }

  if (UseInfoDisplay)
    infoDisp->Unmap();

  if (ClickToFocus)
    SetFocusToActiveWindow();
  if (pointer)
    XUngrabPointer(display, CurrentTime);
  XUngrabKeyboard(display, CurrentTime);
  
  if (!OpaqueResize) {
    ASSERT(paging);
    rcNew.x += paging->origin.x;
    rcNew.y += paging->origin.y;
    ConfigureNewRect(rcNew);
    RedrawWindow();
  }
  else if (!FullOpaque) {
    ConfigureNewRect(rc);
    RedrawWindow();
  }    
  
  if (UseTaskbar && OnTopTaskbar)
    taskBar->RaiseTaskbar();
  if (UsePager && OnTopPager)
    pager->RaisePager();

  ResetStatus(PRESS_FRAME);
}

#ifdef USE_SHAPE
static Bool ResizeWithShape(Display* display, XEvent* ev, char* arg)
{
  switch (ev->type) {
  case KeyPress:
  case ButtonPress:
  case ButtonRelease:
  case MotionNotify:
  case Expose:
    return True;
    
  default:
    if (shapeSupport)
      if (ev->type == event.shapeEventBase + ShapeNotify)
	return True;
    break;
  }
  
  return False;
}
#endif // USE_SHAPE

void Qvwm::FixWindowShape(Rect& rcNew, unsigned int pos, const Point& ptBase,
			const Point& ptNew, const Dim& dNeed)
{
  if (pos & P_LEFT) {
    rcNew.width = RoundOff((ptBase.x - ptNew.x) - dNeed.width -
			   hints.base_width, hints.width_inc) +
			     dNeed.width + hints.base_width;
    rcNew.x = ptBase.x - rcNew.width;
  }
  else if (pos & P_RIGHT)
    rcNew.width = RoundOff((ptNew.x - ptBase.x) - dNeed.width -
			   hints.base_width, hints.width_inc) +
			     dNeed.width + hints.base_width;

  if (pos & P_TOP) {
    rcNew.height = RoundOff((ptBase.y - ptNew.y) - dNeed.height -
			    hints.base_height, hints.height_inc) +
			      dNeed.height + hints.base_height;
    rcNew.y = ptBase.y - rcNew.height;
  }
  else if (pos & P_BOTTOM)
    rcNew.height = RoundOff((ptNew.y - ptBase.y) - dNeed.height -
			    hints.base_height, hints.height_inc) +
			      dNeed.height + hints.base_height;
  
  /*
   * Check minimum limit of size.
   */
  if ((signed)rcNew.width - dNeed.width <= 0) {
    rcNew.width = dNeed.width + Max(1, hints.base_width);
    if (pos & P_LEFT)
      rcNew.x = ptBase.x - rcNew.width;
  }
  else if ((signed)rcNew.width - dNeed.width < hints.min_width) {
    if (pos & P_LEFT)
      rcNew.x = ptBase.x - (hints.min_width + dNeed.width);
    rcNew.width = hints.min_width + dNeed.width;
  }
  if ((signed)rcNew.height - dNeed.height <= 0) {
    rcNew.height = dNeed.height + Max(1, hints.base_height);
    if (pos & P_TOP)
      rcNew.y = ptBase.y - rcNew.height;
  }
  else if ((signed)rcNew.height - dNeed.height < hints.min_height) {
    if (pos & P_TOP)
      rcNew.y = ptBase.y - (hints.min_height + dNeed.height);
    rcNew.height = hints.min_height + dNeed.height;
  }

  /*
   * Check maximum limit of size.
   */
  if (rcNew.width - dNeed.width > hints.max_width) {
    if (pos & P_LEFT)
      rcNew.x = ptBase.x - (hints.max_width + dNeed.width);
    rcNew.width = hints.max_width + dNeed.width;
  }
  if (rcNew.height - dNeed.height > hints.max_height) {
    if (pos & P_TOP)
      rcNew.y = ptBase.y - (hints.max_height + dNeed.height);
    rcNew.height = hints.max_height + dNeed.height;
  }

  /*
   * Check if over taskbar
   */
  if (UseTaskbar && NoResizeOverTaskbar) {
    switch (taskBar->GetPos()) {
    case Taskbar::BOTTOM:
      if ((pos & P_BOTTOM) &&
	  rcNew.y + rcNew.height > taskBar->GetRect().y)
	rcNew.height = taskBar->GetRect().y - rcNew.y;
      break;
    case Taskbar::TOP:
      if ((pos & P_TOP) && rcNew.y < rcScreen.y) {
	rcNew.height -= (rcScreen.y - rcNew.y);
	rcNew.y = rcScreen.y;
      }
      break;
    case Taskbar::LEFT:
      if ((pos & P_LEFT) && rcNew.x < rcScreen.x) {
	rcNew.width -= rcScreen.x - rcNew.x;
	rcNew.x = rcScreen.x;
      }
      break;
    case Taskbar::RIGHT:
      if ((pos & P_RIGHT) && rcNew.x + rcNew.width > taskBar->GetRect().x)
	rcNew.width = taskBar->GetRect().x - rcNew.x;
      break;
    }
    
    if (pos & P_LEFT) {
      rcNew.width = RoundDown(rcNew.width - dNeed.width -
			      hints.base_width, hints.width_inc) +
				dNeed.width + hints.base_width;
      rcNew.x = ptBase.x - rcNew.width;
    }
    else if (pos & P_RIGHT)
      rcNew.width = RoundDown(rcNew.width - dNeed.width -
			      hints.base_width, hints.width_inc) +
				dNeed.width + hints.base_width;
    
    if (pos & P_TOP) {
      rcNew.height = RoundDown(rcNew.height - dNeed.height -
			       hints.base_height, hints.height_inc) +
				 dNeed.height + hints.base_height;
      rcNew.y = ptBase.y - rcNew.height;
    }
    else if (pos & P_BOTTOM)
      rcNew.height = RoundDown(rcNew.height - dNeed.height -
			       hints.base_height, hints.height_inc) +
				 dNeed.height + hints.base_height;
  }
}
      
/*
 * GetFixSize --
 *   Adjust the position and the size of the window according to the window
 *   property.
 */
Rect Qvwm::GetFixSize(const Rect& rc, int max_width, int max_height,
		      int width_inc, int height_inc,
		      int base_width, int base_height)
{
  Rect rect;
  int borderWidth, topBorder, titleHeight, titleEdge;
  int outerw, outerh;

  GetBorderAndTitle(borderWidth, topBorder, titleHeight, titleEdge);
  
  outerw = borderWidth * 2;
  outerh = borderWidth + topBorder + titleHeight + titleEdge;

  // clip to window defined maximum
  rect.width = Min(max_width, rc.width - outerw) + outerw;
  rect.height = Min(max_height, rc.height - outerh) + outerh;

  // clip to screen
  rect.width = Min(rect.width, rcScreen.width);
  rect.height = Min(rect.height, rcScreen.height);

  rect.x = rc.x;
  rect.y = rc.y;
  rect.width = RoundDown(rect.width - base_width - outerw, width_inc)
    + base_width + outerw;
  rect.height = RoundDown(rect.height - base_height - outerh, height_inc)
    + base_height + outerh;

  return rect;
}


syntax highlighted by Code2HTML, v. 0.9.1