/*
 * src/screen.c - screen management
 *
 * Copyright (C) 1999, 2000, 2001 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
 *
 * This 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 software 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 this package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.  
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif 

#include <assert.h>
#include <stdio.h>		/* Needed for sprintf */

#include "client.h"
#include "display.h"
#include "screen.h"
#include "workset.h"

/*
 * Define FAST if you do not want opaque window moves.
 */
#define FAST

/*
 * Draw the outline of client C, indicating a resizing operation.
 */
static void
draw_outline (client_t c)
{
  char buf[40];
  int basex, basey;
  int width, height;

  assert (c != NULL);

  width = c->width;
  height = c->height;

  if (c->size->flags & PResizeInc)
    {
      basex = (c->size->flags & PBaseSize) ? c->size->base_width :
	(c->size->flags & PMinSize) ? c->size->min_width : 0;
      basey = (c->size->flags & PBaseSize) ? c->size->base_height :
	(c->size->flags & PMinSize) ? c->size->min_height : 0;
      if (c->size->width_inc > 0)
	width = (c->width - basex) / c->size->width_inc;
      if (c->size->height_inc > 0)
	height = (c->height - basey) / c->size->height_inc;
    }

  XDrawRectangle(display, root_window, invert_gc,
		 c->x + border_width / 2, c->y - c->title_height +
		 border_width / 2,
		 c->width + border_width, c->height + c->title_height +
		 border_width);
  XDrawLine(display, root_window, invert_gc, c->x + border_width, c->y +
	    border_width / 2,
	    c->x + c->width + border_width, c->y + border_width / 2);

  sprintf(buf, "%dx%d+%d+%d", width, height, c->x, c->y);
  XDrawString(display, root_window, invert_gc,
	      c->x + (c->width - XTextWidth(font, buf, strlen(buf))) / 2,
	      c->y + c->height / 2,
	      buf, strlen(buf));
}

/*
 * Return the position of the mouse pointer in the variables pointed
 * to by X and Y.
 */
void
get_mouse_position (int * x, int * y)
{
  Window root;
  Window win;
  int win_x;
  int win_y;
  unsigned mask;

  XQueryPointer(display, root_window, &root, &win, x, y, &win_x, &win_y,
		&mask);
}

/*
 * Adjust the geometry values CX, CY, CWIDTH and CHEIGHT and adjust
 * them to be compatible with the hints given by the client C.
 */
static void
correct_geometry (client_t c, int * cx, int * cy,
		  int * cwidth, int * cheight,
		  int correct_left, int correct_top)
{
  int basex, basey;
  int x,  y, width, height;
  int dispx = 0, dispy = 0;

  assert (c != NULL);

  x = *cx;
  y = *cy;
  width = *cwidth;
  height = *cheight;

  if (c->size->flags & PResizeInc)
    {
      basex = (c->size->flags & PBaseSize) ? c->size->base_width : 0;
      basey = (c->size->flags & PBaseSize) ? c->size->base_height : 0;
      dispx = (width - basex) % c->size->width_inc;
      dispy = (height - basey) % c->size->height_inc;
      width -= dispx;
      height -= dispy;
    }

  if (c->size->flags & PMinSize)
    {
      if (width < c->size->min_width)
	width = c->size->min_width;
      if (height < c->size->min_height)
	height = c->size->min_height;
    }

  if (c->size->flags & PMaxSize)
    {
      if (width > c->size->max_width)
	width = c->size->max_width;
      if (height > c->size->max_height)
	height = c->size->max_height;
    }
  if (correct_left)
    x += dispx;
  if (correct_top)
    y += dispy;
  *cx = x;
  *cy = y;
  *cwidth = width;
  *cheight = height;
}

/*
 * Trace mouse movements until a button is released and resize the
 * window of client C accordingly.
 */
static void
manually_resize (client_t c)
{
  XEvent event;
  int top = 0, left = 0, bottom = 0, right = 0;
  int x, y;
  int width, height;

  assert (c != NULL);

  XGrabPointer (display, root_window, True,
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
		GrabModeAsync, GrabModeAsync,
		None, resize_curs, CurrentTime);

  XGrabServer(display);

  x = c->x;
  y = c->y;
  width = c->width;
  height = c->height;

  draw_outline(c);

  for (;;)
    {
      XMaskEvent(display,
		 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
		 &event);
      switch (event.type)
	{
	case MotionNotify:

	  /* FIXME: there *must* be a better way for doing that! */

	  if (!right && event.xmotion.x < x)
	    {
	      width += x - event.xmotion.x;
	      x = event.xmotion.x;
	      left = 1;
	    }
	  else if (left && event.xmotion.x > x)
	    {
	      width -= event.xmotion.x - x;
	      x = event.xmotion.x;
	    }

	  if (!left && event.xmotion.x > x + width)
	    {
	      width += event.xmotion.x - (x + width);
	      right = 1;
	    }
	  else if (right && event.xmotion.x < x + width)
	    {
	      width -= (x + width) - event.xmotion.x;
	    }

	  if (!bottom && event.xmotion.y < y - c->title_height)
	    {
	      height += y - event.xmotion.y - c->title_height;
	      y = event.xmotion.y + c->title_height;
	      top = 1;
	    }
	  else if (top && event.xmotion.y > y - c->title_height)
	    {
	      height -= event.xmotion.y - y + c->title_height;
	      y = event.xmotion.y + c->title_height;
	    }

	  if (!top && event.xmotion.y > y + height)
	    {
	      height += event.xmotion.y - (y + height);
	      bottom = 1;
	    }
	  else if (bottom && event.xmotion.y < y + height)
	    {
	      height -= (y + height) - event.xmotion.y;
	    }

	  correct_geometry(c, &x, &y, &width, &height, left, top);

	  if (x != c->x || y != c->y ||
	      width != c->width || height != c->height)
	    {
	      draw_outline (c); /* clear */
	      c->x = x;
	      c->y = y;
	      c->width = width;
	      c->height = height;
	      draw_outline (c);
	    }
	  break;

	case ButtonRelease:
	  draw_outline (c); /* clear */
	  XUngrabServer (display);
	  XUngrabPointer (display, CurrentTime);
	  return;
	}
    }
}

/*
 * Let the user interactively resize the window of client C.
 */
void
resize_client (client_t c)
{
  assert (c != NULL);

  manually_resize (c);
#if 0
  XMoveResizeWindow (display, c->parent,
		     c->x, c->y/*   - c->title_height */,
		     c->width, c->height/*   + c->title_height */);
  XMoveResizeWindow (display, c->window,
		     0, 0/*  c->title_height */, c->width, c->height);
  send_config(c);
#endif
  client_realize_size (c);
}

/*
 * Resize client C relatively by REL_X and REL_Y units, the units
 * depending on the client.
 */
void
resize_client_to (client_t c, int rel_x, int rel_y)
{
  int incx, incy;

  assert (c != NULL);

  if (c->size->flags & PResizeInc)
    {
      incx = c->size->width_inc;
      incy = c->size->height_inc;
    }
  else
    {
      incx = 16;
      incy = 16;
    }

  incx *= rel_x;
  incy *= rel_y;

  c->width += incx;
  c->height += incy;

  if (c->size->flags & PMinSize)
    {
      if (c->width < c->size->min_width)
	c->width = c->size->min_width;
      if (c->height < c->size->min_height)
	c->height = c->size->min_height;
    }

  if (c->size->flags & PMaxSize)
    {
      if (c->width > c->size->max_width)
	c->width = c->size->max_width;
      if (c->height > c->size->max_height)
	c->height = c->size->max_height;
    }
#if 0
  XMoveResizeWindow (display, c->parent,
		     c->x, c->y/*   - c->title_height */,
		     c->width, c->height/*   + c->title_height */);
  XMoveResizeWindow (display, c->window,
		     0, 0/*  c->title_height */, c->width, c->height);
  send_config(c);
#endif
  client_realize_size (c);
}

/*
 * Change the position of client C in such a way that it snaps to other
 * client's borders or the screen borders if it is close enough.
 */
static void
snap_client (client_t c)
{
/*    int snapped = 0; */
/*    client_t cc; */

  assert (c != NULL);

  if (abs(c->x) < BIG_SNAP)
    {
      c->x = 0;
    }
  else if (abs((c->x + c->width) - DisplayWidth(display,
					   default_screen)) < BIG_SNAP)
    {
      c->x = DisplayWidth(display, default_screen) - c->width
	- 2 * border_width;
    }

  if (abs(c->y - c->title_height) < BIG_SNAP)
    {
      c->y = c->title_height;
    }
  else if (abs((c->y + c->height) - DisplayHeight(display,
					     default_screen)) < BIG_SNAP)
    {
      c->y = DisplayHeight(display, default_screen) - c->height
	- 2 * border_width;
    }

  /* 
   * FIXME: check out whether this should stay in or not.
   */
#if 0
  snapped = 0;
  cc = client_root;
  while (cc && !snapped)
    {
      /* FIXME: gotta ignore hidden clients here! */
      if (cc != c)
	{
	  /*
	   * FIXME: not a beautiful algorithm. Maybe there's a better
	   * one.
	   */
	  if (abs(c->x - cc->x) < SMALL_SNAP)
	    {
	      c->x = cc->x;
	      snapped = 1;
	    }
	  else if (abs(c->x + c->width - (cc->x + cc->width)) < SMALL_SNAP)
	    {
	      c->x = cc->x + cc->width - c->width;
	      snapped = 1;
	    }
	  else if (abs(c->x - (cc->x + cc->width)) < SMALL_SNAP)
	    {
	      c->x = cc->x + cc->width + border_width;
	      snapped = 1;
	    }
	  else if (abs(c->x + c->width - cc->x) < SMALL_SNAP)
	    {
	      c->x = cc->x - c->width - border_width;
	      snapped = 1;
	    }

	  if (abs((c->y - c->title_height) - (cc->y - c->title_height))
	      < SMALL_SNAP)
	    {
	      c->y = cc->y;
	      snapped = 1;
	    }
	  else if (abs(c->y + c->height - (cc->y + cc->height)) < SMALL_SNAP)
	    {
	      c->y = cc->y + cc->height - c->height;
	      snapped = 1;
	    }
	  else if (abs((c->y - c->title_height) - (cc->y + cc->height))
		   < SMALL_SNAP)
	    {
	      c->y = cc->y + cc->height + border_width + c->title_height;
	      snapped = 1;
	    }
	  else if (abs(c->y + c->height - (cc->y - cc->title_height))
		   < SMALL_SNAP)
	    {
	      c->y = cc->y - cc->title_height - c->height - border_width;
	      snapped = 1;
	    }
	}
      cc = cc->next;
    }
#endif /* 0 */
}

/*
 * Draw the outline of the client C and update its position while
 * its outline gets dragged.
 */
static void
manually_move (client_t c)
{
  XEvent event;
  int x1, y1;
  int old_cx = c->x;
  int old_cy = c->y;

  assert (c != NULL);
  if (!XGrabPointer(display, root_window, False, MouseMask, GrabModeAsync,
		    GrabModeAsync, None, move_curs,
		    CurrentTime) == GrabSuccess)
    return;

#ifdef FAST
  XGrabServer(display);
#endif
  get_mouse_position(&x1, &y1);

#ifdef FAST
  draw_outline(c);
#endif
  for (;;)
    {
      XMaskEvent(display, MouseMask, &event);
      switch (event.type)
	{
	case MotionNotify:
#ifdef FAST
	  draw_outline(c);
#endif
	  c->x = old_cx + (event.xmotion.x - x1);
	  c->y = old_cy + (event.xmotion.y - y1);

	  /*
	   * Snap-to-screen & snap-to-windows.
	   */
	  snap_client (c);
#ifndef FAST
	  XMoveWindow (display, c->parent, c->x, c->y/*   - c->title_height */);
#else
	  draw_outline(c);
#endif
	  break;

	case ButtonRelease:
#ifdef FAST
	  draw_outline(c); 
	  XUngrabServer(display);
#endif
/*  	  ungrab(); */
	  XUngrabPointer(display, CurrentTime);
	  return;
	}
    }
}

/*
 * Let the user drag the window of client C to another position on the
 * screen.
 */
void
move_client (client_t c)
{
  assert (c != NULL);

  manually_move (c);
#if 0
  XMoveWindow (display, c->parent, c->x, c->y/*   - c->title_height */);
  send_config (c);
#endif
  client_realize_move (c);
}

/*
 * Move the client's position by the amount DX pixels in horizontal
 * direction and DY pixels in vertical direction, if RELATIVE is false.
 * Otherwise, set the position to absolute DX/DY.
 */
void
move_client_to (client_t c, int relative, int dx, int dy)
{
  assert (c != NULL);

  if (relative) 
    {
      c->x += dx;
      c->y += dy;
    }
  else 
    {
      c->x = dx;
      c->y = dy;
    }

#if 0
  XMoveWindow (display, c->parent, c->x,
	       c->y/*   - c->title_height */);
  send_config (c);
#endif
  client_realize_move (c);
}

/*
 * Unmap the client C.
 */
void
hide_client (client_t c)
{
  assert (c != NULL);

  c->ignore_unmap++;
  XUnmapWindow (display, c->tab);
  XUnmapWindow (display, c->parent);
  XUnmapWindow (display, c->window);
  set_wm_state (c, IconicState);
}

/*
 * Map the window of client C back to the screen.
 */
void
unhide_client (client_t c)
{
  assert (c != NULL);

  if (c->workset >= 0)
    {
      if (c->workset != current_workset)
	make_client_visible (c);
      c->workset = current_workset;
    }
  XMapWindow (display, c->window);
  XMapRaised (display, c->parent);
  XMapRaised (display, c->tab);
  set_wm_state (c, NormalState);
}

/*
 * Make a client visible which has been hidden by a call to
 * make_client_invisible() below.  Simply move the clien window C to
 * its original position on the screen.
 */
void
make_client_visible (client_t c)
{
  assert (c != NULL);

#if 0
  XMoveWindow (display, c->parent, c->x, c->y/*   - c->title_height */);
#endif
  client_realize_move (c);
}

/*
 * Make the client window C invisible by moving it off the visible
 * screen.
 */
void
make_client_invisible (client_t c)
{
  assert (c != NULL);

  XMoveWindow (display, c->tab, DisplayWidth(display, default_screen) + 10, 
	       DisplayHeight(display, default_screen) + 10);
  XMoveWindow (display, c->parent, DisplayWidth(display, default_screen) + 10, 
	       DisplayHeight(display, default_screen) + 10);
}

