/*
 * Copyright (C) 1995 Bo Yang
 * Copyright (C) 1993 Robert Nation
 *
 * 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 of the License, 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/***********************************************************************
 *
 * code for moving windows
 *
 ***********************************************************************/

#include "../configure.h"

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <X11/keysym.h>
#include "afterstep.h"
#include "menus.h"
#include "misc.h"
#include "parse.h"
#include "screen.h"
#include "module.h"

extern XEvent Event;
extern int menuFromFrameOrWindowOrTitlebar;
Bool NeedToResizeToo;

/****************************************************************************
 *
 * Start a window move operation
 *
 ****************************************************************************/
void move_window(XEvent * eventp, Window w, ASWindow * tmp_win, int context,
		 int val1, int val2, int val1_unit, int val2_unit)
{
    int FinalX, FinalY;

    /* gotta have a window */
    if (tmp_win == NULL)
	return;

    w = tmp_win->frame;
    if (tmp_win->flags & ICONIFIED) {
#ifdef NO_ICON_BACKGROUND
	if (tmp_win->icon_pixmap_w != None) {
	    XUnmapWindow(dpy, tmp_win->icon_w);
#endif
	    w = tmp_win->icon_pixmap_w;
#ifdef NO_ICON_BACKGROUND
	} else
	    w = tmp_win->icon_w;
#endif
    }
    if ((val1 != 0) || (val2 != 0)) {
	FinalX = val1 * val1_unit / 100;
	FinalY = val2 * val2_unit / 100;
    } else
	InteractiveMove(&w, tmp_win, &FinalX, &FinalY, eventp);

    if (w == tmp_win->frame) {
	SetupFrame(tmp_win, FinalX, FinalY,
		   tmp_win->frame_width, tmp_win->frame_height, FALSE);
    } else {			/* icon window */
	tmp_win->flags |= ICON_MOVED;
	tmp_win->icon_x_loc = FinalX;
#ifdef NO_ICON_BACKGROUND
	tmp_win->icon_xl_loc = FinalX - (tmp_win->icon_w_width - tmp_win->icon_p_width) / 2;
#endif
	tmp_win->icon_y_loc = FinalY;
	Broadcast(M_ICON_LOCATION, 7, tmp_win->w, tmp_win->frame,
		  (unsigned long) tmp_win,
		  tmp_win->icon_x_loc, tmp_win->icon_y_loc,
		  tmp_win->icon_p_width,
		  tmp_win->icon_p_height);
#ifdef NO_ICON_BACKGROUND
	XMoveWindow(dpy, tmp_win->icon_w,
		  tmp_win->icon_xl_loc, FinalY + tmp_win->icon_p_height);
	XMapWindow(dpy, tmp_win->icon_w);
#endif
	if (tmp_win->icon_pixmap_w != None) {
	    XMoveWindow(dpy, tmp_win->icon_pixmap_w, tmp_win->icon_x_loc, FinalY);
	    XMapWindow(dpy, w);
	}
    }

    UpdateVisibility();
    return;
}



/****************************************************************************
 *
 * Move the rubberband around, return with the new window location
 *
 ****************************************************************************/
void moveLoop(ASWindow * tmp_win, int XOffset, int YOffset, int Width,
	      int Height, int *FinalX, int *FinalY, Bool opaque_move,
	      Bool AddWindow)
{
    Bool finished = False;
    Bool done;
    int xl, yt, delta_x, delta_y;
    XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &xl, &yt,
		  &JunkX, &JunkY, &JunkMask);
    xl += XOffset;
    yt += YOffset;

    if ((!opaque_move) || (AddWindow))
	MoveOutline(Scr.Root, xl, yt, Width, Height);

    DisplayPosition(tmp_win, xl + Scr.Vx, yt + Scr.Vy, True);

    while (!finished) {
	/* block until there is an interesting event */
	XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | KeyPressMask |
	    PointerMotionMask | ButtonMotionMask | ExposureMask, &Event);
	StashEventTime(&Event);

	/* discard any extra motion events before a logical release */
	if (Event.type == MotionNotify) {
	    while (XCheckMaskEvent(dpy, PointerMotionMask | ButtonMotionMask |
			      ButtonPressMask | ButtonRelease, &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 KeyPress:
	    done = TRUE;
	    break;
	case ButtonPress:
	    XAllowEvents(dpy, ReplayPointer, CurrentTime);
	    if (Event.xbutton.button == 2) {
		NeedToResizeToo = True;
		/* Fallthrough to button-release */
	    } else {
		done = 1;
		break;
	    }
	case ButtonRelease:
	    if (!opaque_move)
		MoveOutline(Scr.Root, 0, 0, 0, 0);
	    xl = Event.xmotion.x_root + XOffset;
	    yt = Event.xmotion.y_root + YOffset;

	    /* Resist moving windows over the edge of the screen! */
	    if (((xl + Width) >= Scr.MyDisplayWidth) &&
		((xl + Width) < Scr.MyDisplayWidth + Scr.MoveResistance))
		xl = Scr.MyDisplayWidth - Width - tmp_win->bw;
	    if ((xl <= 0) && (xl > -Scr.MoveResistance))
		xl = 0;
	    if (((yt + Height) >= Scr.MyDisplayHeight) &&
	      ((yt + Height) < Scr.MyDisplayHeight + Scr.MoveResistance))
		yt = Scr.MyDisplayHeight - Height - tmp_win->bw;
	    if ((yt <= 0) && (yt > -Scr.MoveResistance))
		yt = 0;

	    *FinalX = xl;
	    *FinalY = yt;

	    done = TRUE;
	    finished = TRUE;
	    break;

	case MotionNotify:
	    /* update location of the pager_view window */
	    xl = Event.xmotion.x_root;
	    yt = Event.xmotion.y_root;
	    HandlePaging(Scr.MyDisplayWidth, Scr.MyDisplayHeight, &xl, &yt,
			 &delta_x, &delta_y, False);
	    /* redraw the rubberband */
	    xl += XOffset;
	    yt += YOffset;

	    /* Resist moving windows over the edge of the screen! */
	    if (((xl + Width) >= Scr.MyDisplayWidth) &&
		((xl + Width) < Scr.MyDisplayWidth + Scr.MoveResistance))
		xl = Scr.MyDisplayWidth - Width - tmp_win->bw;
	    if ((xl <= 0) && (xl > -Scr.MoveResistance))
		xl = 0;
	    if (((yt + Height) >= Scr.MyDisplayHeight) &&
	      ((yt + Height) < Scr.MyDisplayHeight + Scr.MoveResistance))
		yt = Scr.MyDisplayHeight - Height - tmp_win->bw;
	    if ((yt <= 0) && (yt > -Scr.MoveResistance))
		yt = 0;

	    if (!opaque_move)
		MoveOutline(Scr.Root, xl, yt, Width, Height);
	    else {
		if (tmp_win->flags & ICONIFIED) {
		    tmp_win->icon_x_loc = xl;
#ifdef NO_ICON_BACKGROUND
		    tmp_win->icon_xl_loc = xl -
			(tmp_win->icon_w_width - tmp_win->icon_p_width) / 2;
#endif
		    tmp_win->icon_y_loc = yt;
		    if (tmp_win->icon_pixmap_w != None)
			XMoveWindow(dpy, tmp_win->icon_pixmap_w,
				    tmp_win->icon_x_loc, yt);
#ifdef NO_ICON_BACKGROUND
		    else if (tmp_win->icon_w != None)
			XMoveWindow(dpy, tmp_win->icon_w, tmp_win->icon_xl_loc,
				    yt + tmp_win->icon_p_height);
#endif
		} else
		    XMoveWindow(dpy, tmp_win->frame, xl, yt);
	    }
	    DisplayPosition(tmp_win, xl + Scr.Vx, yt + Scr.Vy, False);
	    done = TRUE;
	    break;

	default:
	    break;
	}
	if (!done) {
	    if (!opaque_move)
		MoveOutline(Scr.Root, 0, 0, 0, 0);
	    DispatchEvent();
	    if (!opaque_move)
		MoveOutline(Scr.Root, xl, yt, Width, Height);
	}
    }
}

/***********************************************************************
 *
 *  Procedure:
 *      DisplayPosition - display the position in the dimensions window
 *
 *  Inputs:
 *      tmp_win - the current afterstep window
 *      x, y    - position of the window
 *
 ************************************************************************/

void DisplayPosition(ASWindow * tmp_win, int x, int y, int Init)
{
    char str[100];
    int offset;

    (void) sprintf(str, " %+-4d %+-4d ", x, y);
    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);
    }

    offset = (Scr.SizeStringWidth + SIZE_HINDENT * 2
	      - XTextWidth(Scr.StdFont.font, str, strlen(str))) / 2;
    XDrawString(dpy, Scr.SizeWindow, Scr.NormalGC,
		offset,
		Scr.StdFont.font->ascent + SIZE_VINDENT,
		str, strlen(str));
}


/****************************************************************************
 *
 * For menus, move, and resize operations, we can effect keyboard 
 * shortcuts by warping the pointer.
 *
 ****************************************************************************/
void Keyboard_shortcuts(XEvent * Event, int ReturnEvent)
{
    int x, y, x_root, y_root;
    int move_size, x_move, y_move;
    KeySym keysym;

    /* Pick the size of the cursor movement */
    move_size = Scr.EntryHeight;
    if (Event->xkey.state & ControlMask)
	move_size = 1;
    if (Event->xkey.state & ShiftMask)
	move_size = 100;

    keysym = XLookupKeysym(&Event->xkey, 0);

    x_move = 0;
    y_move = 0;
    switch (keysym) {
    case XK_Up:
    case XK_k:
    case XK_p:
	y_move = -move_size;
	break;
    case XK_Down:
    case XK_n:
    case XK_j:
	y_move = move_size;
	break;
    case XK_Left:
    case XK_b:
    case XK_h:
	x_move = -move_size;
	break;
    case XK_Right:
    case XK_f:
    case XK_l:
	x_move = move_size;
	break;
    case XK_Return:
    case XK_space:
	/* beat up the event */
	Event->type = ReturnEvent;
	break;
    default:
	break;
    }
    XQueryPointer(dpy, Scr.Root, &JunkRoot, &Event->xany.window,
		  &x_root, &y_root, &x, &y, &JunkMask);

    if ((x_move != 0) || (y_move != 0)) {
	/* beat up the event */
	XWarpPointer(dpy, None, Scr.Root, 0, 0, 0, 0, x_root + x_move,
		     y_root + y_move);

	/* beat up the event */
	Event->type = MotionNotify;
	Event->xkey.x += x_move;
	Event->xkey.y += y_move;
	Event->xkey.x_root += x_move;
	Event->xkey.y_root += y_move;
    }
}


void InteractiveMove(Window * win, ASWindow * tmp_win, int *FinalX, int *FinalY, XEvent * eventp)
{
    extern int Stashed_X, Stashed_Y;
    int origDragX, origDragY, DragX, DragY, DragWidth, DragHeight;
    int XOffset, YOffset;
    Window w;

    Bool opaque_move = False;


    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);
    }
    DragX = eventp->xbutton.x_root;
    DragY = eventp->xbutton.y_root;
    /* If this is left commented out, then the move starts from the button press 
     * location instead of the current location, which seems to be an
     * improvement */
    /*  XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
       &DragX, &DragY,    &JunkX, &JunkY, &JunkMask); */

    if (!GrabEm(MOVE)) {
	XBell(dpy, Scr.screen);
	return;
    }
    w = tmp_win->frame;

    if (tmp_win->flags & ICONIFIED)
#ifdef NO_ICON_BACKGROUND
	if (tmp_win->icon_pixmap_w != None) {
	    XUnmapWindow(dpy, tmp_win->icon_w);
	    w = tmp_win->icon_pixmap_w;
	} else
	    w = tmp_win->icon_w;
#else
	w = tmp_win->icon_pixmap_w;
#endif

    *win = w;

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

    if (DragWidth * DragHeight <
	(Scr.OpaqueSize * Scr.MyDisplayWidth * Scr.MyDisplayHeight) / 100)
	opaque_move = True;
    else
	XGrabServer(dpy);

    if ((!opaque_move) && (tmp_win->flags & ICONIFIED))
	XUnmapWindow(dpy, w);

    DragWidth += JunkBW;
    DragHeight += JunkBW;
    XOffset = origDragX - DragX;
    YOffset = origDragY - DragY;
    XMapRaised(dpy, Scr.SizeWindow);
    moveLoop(tmp_win, XOffset, YOffset, DragWidth, DragHeight, FinalX, FinalY,
	     opaque_move, False);

    XUnmapWindow(dpy, Scr.SizeWindow);
    UninstallRootColormap();

    if (!opaque_move)
	XUngrabServer(dpy);
    UngrabEm();


}
