/* 
 * @OPENGROUP_COPYRIGHT@
 * COPYRIGHT NOTICE
 * Copyright (c) 1989, 1990, 1991, 1992 Open Software Foundation, Inc. 
 * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
 * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
 * the full copyright text.
 * 
 * This software is subject to an open license. It may only be
 * used on, with or for operating systems which are themselves open
 * source systems. You must contact The Open Group for a license
 * allowing distribution and sublicensing of this software on, with,
 * or for operating systems which are not Open Source programs.
 * 
 * See http://www.opengroup.org/openmotif/license for full
 * details of the license agreement. Any use, reproduction, or
 * distribution of the program constitutes recipient's acceptance of
 * this agreement.
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
 * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
 * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
 * OR FITNESS FOR A PARTICULAR PURPOSE
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
 * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
 * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
*/ 
/* 
 * Motif Release 1.2
*/ 
#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$XConsortium: WmCPlace.c /main/5 1996/08/09 15:18:04 rswiston $"
#endif
#endif
/*
 * (c) Copyright 1987, 1988, 1989, 1990 HEWLETT-PACKARD COMPANY */

/*
 * Included Files:
 */
#include "WmGlobal.h"
#define XK_MISCELLANY
#define XK_LATIN1
#include <X11/keysymdef.h>

/* absolute value macro */
#ifndef ABS
#define ABS(x) (((x) > 0) ? (x) : (-(x)))
#endif

#define GRAB_MASK (KeyPressMask | ButtonPressMask | ButtonReleaseMask |\
		   PointerMotionMask)
#define PGRAB_MASK (ButtonPressMask | ButtonReleaseMask |\
		    PointerMotionMask | PointerMotionHintMask)

#define NOFRZ_GRAB_MASK (KeyPressMask | ButtonPressMask |\
			 ButtonReleaseMask)
#define NOFRZ_PGRAB_MASK (ButtonPressMask | ButtonReleaseMask)
		    	  
/*
 * include extern functions
 */
#include "WmCDInfo.h"
#include "WmCDecor.h"
#include "WmFeedback.h"
#include "WmWinConf.h"

/*
 * Global Variables:
 */
static int placeX;
static int placeY;
static unsigned int placeWidth;
static unsigned int placeHeight;

static Boolean placeResize;
static Boolean placementDone;
static int placePointerX;
static int placePointerY;
static int placeKeyMultiplier;

static int placeOffsetX;
static int placeOffsetY;



/*************************************<->*************************************
 *
 *  SetupPlacement (pcd)
 *
 *
 *  Description:
 *  -----------
 *  Perform the initialization for interactive placement
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * o sets up global data and puts initial display on the screen
 * 
 *************************************<->***********************************/
void SetupPlacement (ClientData *pcd)
{
    int cX, cY, junk;
    Window junk_win;

    /*
     * Restore the state of the last "depressed" frame gadget
     */

    if (wmGD.gadgetClient && wmGD.gadgetDepressed)
    {
	PopGadgetOut(wmGD.gadgetClient, wmGD.gadgetDepressed);

    }
	
    /* get offset of frame origin from window origin */
    placeOffsetX = pcd->clientOffset.x;
    placeOffsetY = pcd->clientOffset.y;

    XQueryPointer (DISPLAY, ACTIVE_ROOT, 
		   &junk_win, &junk_win,
		   &cX, &cY, &junk, &junk, (unsigned int *)&junk);

    /* convert to frame coordinates */
    placePointerX = placeX = cX;
    placePointerY = placeY = cY;
    placeWidth = pcd->clientWidth;
    placeHeight = pcd->clientHeight;
    ClientToFrame (pcd, &cX, &cY, &placeWidth, &placeHeight);

    /* in "position" mode to start with */
    placeResize = FALSE;
    wmGD.preMove = FALSE;

    /* normal window being dealt with, not icon */
    wmGD.movingIcon = FALSE;

    if (wmGD.showFeedback & WM_SHOW_FB_PLACEMENT)
    {
        DoFeedback (pcd, placeX, placeY, placeWidth, placeHeight, 
		    (FB_SIZE | FB_POSITION), placeResize);
    }

    /* set up initial visual feedback */
    MoveOutline (placeX, placeY, placeWidth, placeHeight);

} /* END OF FUNCTION SetupPlacement  */


/*************************************<->*************************************
 *
 *  IsRepeatedKeyEvent (dpy, pEvent, pOldEvent)
 *
 *
 *  Description:
 *  -----------
 *  Returns TRUE if the event passed in is a repeat of the key event
 *  indicated in pOldEvent
 *
 *
 *  Inputs:
 *  ------
 *  dpy		- display
 *  pEvent	- pointer to this event
 *  pOldEvent	- pointer to previous event (cast to the correct type)
 *
 * 
 *  Outputs:
 *  -------
 *  IsRepeatedKeyEvent	- True if the events are "the same," 
 *			  False otherwise.
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/
Bool IsRepeatedKeyEvent (Display *dpy, XEvent *pEvent, char *pOldEvent)
{
    XEvent *pOld = (XEvent *) pOldEvent;

    if ((pEvent->type == KeyPress) &&			/* key press event */
	(pEvent->type == pOld->type) &&			/* same event type */
	(pEvent->xkey.keycode == pOld->xkey.keycode) &&	/* same key code */
	(pEvent->xkey.state == pOld->xkey.state) &&	/* same modifiers */
	(pEvent->xkey.window == pOld->xkey.window) &&	/* same window */
	(pEvent->xkey.root == pOld->xkey.root))		/* same root */
    {
	return (True);
    }
    else 
    { 
	return (False);
    }
}


/*************************************<->*************************************
 *
 *  StartInteractiveSizing (pcd, time)
 *
 *
 *  Description:
 *  -----------
 *  Switch from "position" mode to "resize" mode
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 *  time	- time stamp of event for pointer regrab
 *
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/
void StartInteractiveSizing (ClientData *pcd, Time time)
{
    unsigned int gmask;

    /* regrab pointer to change cursor */
    gmask = (wmGD.freezeOnConfig)? PGRAB_MASK : NOFRZ_PGRAB_MASK;
    XChangeActivePointerGrab (DISPLAY, gmask, 
			      wmGD.sizePlacementCursor, time);

    /* put cursor at lower-right corner */
    if (wmGD.enableWarp)
    {
	XWarpPointer (DISPLAY, None, ACTIVE_ROOT, 0, 0, 0, 0, 
		  (int) (placeX+placeWidth-1), (int) (placeY+placeHeight-1));
    }

    /*
     * Don't go into resize mode if resize is turned off.
     */
    if (pcd->clientFunctions & MWM_FUNC_RESIZE)
    {
	/* update flags */
	placeResize = TRUE;
	wmGD.preMove = FALSE;
    }
}


/*************************************<->*************************************
 *
 *  HandlePlacementKeyEvent (pcd, pev)
 *
 *
 *  Description:
 *  -----------
 *  Handle key presses during interactive placement
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 *  pev		- pointer to key press event
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/

void HandlePlacementKeyEvent (ClientData *pcd, XKeyEvent *pev)
{
    XEvent KeyEvent;
    KeySym keysym;
    Boolean control, valid;
    int big_inc;
    int tmpX = 0;
    int tmpY = 0;
    int warpX, warpY, newX, newY;
    int keyPlaceX = placeX;
    int keyPlaceY = placeY;
    unsigned int keyPlaceWidth = placeWidth;
    unsigned int keyPlaceHeight = placeHeight;

    /* filter out repeating keys */
    placeKeyMultiplier = 1;
    if (pev->type == KeyPress)
    {
	while (placeKeyMultiplier <= 10 && 
		  XCheckIfEvent (DISPLAY, &KeyEvent, IsRepeatedKeyEvent, 
		  (char *) pev))
	{
	      placeKeyMultiplier++;
	}
    }

    /* convert event data to useful key data */
    keysym = XKeycodeToKeysym (DISPLAY, pev->keycode, 0);
    control = (pev->state & ControlMask) != 0;
    big_inc = DisplayWidth(DISPLAY, ACTIVE_PSD->screen) / 20;

    /* interpret key data */
    valid = FALSE;
    switch (keysym) 
    {
	case XK_Left:
	    tmpX = (control) ? (-big_inc) : -1;
	    valid = TRUE;
	    break;

	case XK_Up:
	    tmpY = (control) ? (-big_inc) : -1;
	    valid = TRUE;
	    break;

	case XK_Right:
	    tmpX = (control) ? (big_inc) : 1;
	    valid = TRUE;
	    break;

	case XK_Down:
	    tmpY = (control) ? (big_inc) : 1;
	    valid = TRUE;
	    break;

	case XK_space:
	    StartInteractiveSizing(pcd, pev->time);
	    break;

	case XK_Return:
	    placementDone = TRUE;	/* global "done" flag */
	    break;

	default:
	    break;
    }


    /* if a valid key was pressed, then react to it */
    if (valid) {
	tmpX *= placeKeyMultiplier;
	tmpY *= placeKeyMultiplier;

	if (placeResize)
	{
	    keyPlaceWidth += tmpX;		/* change size of outline */
	    keyPlaceHeight += tmpY;

	    FixFrameValues(pcd, &keyPlaceX, &keyPlaceY, &keyPlaceWidth, 
		           &keyPlaceHeight, placeResize);

	    warpX = keyPlaceX+keyPlaceWidth-1;
	    warpY = keyPlaceY+keyPlaceHeight-1;

	    SetPointerPosition (warpX, warpY, &newX, &newY);

	    if ((warpX == newX) && (warpY == newY))
	    {
		placeWidth = keyPlaceWidth;
		placeHeight = keyPlaceHeight;
	    }
	    else 
	    {
		placeWidth = newX - keyPlaceX + 1;
		placeHeight = newY - keyPlaceY + 1;
	    }
	}
	else 
	{
	    keyPlaceX += tmpX;		/* change position of outline */
	    keyPlaceY += tmpY;

	    FixFrameValues(pcd, &keyPlaceX, &keyPlaceY, &keyPlaceWidth, 
		           &keyPlaceHeight, placeResize);

	    warpX = keyPlaceX;
	    warpY = keyPlaceY;

	    SetPointerPosition (warpX, warpY, &newX, &newY);

	    placeX = newX;
	    placeY = newY;
	}
	placePointerX = newX;
	placePointerY = newY;
    }
    FixFrameValues (pcd, &placeX, &placeY, &placeWidth, &placeHeight,
		    placeResize);

    MoveOutline (placeX, placeY, placeWidth, placeHeight);

    if (wmGD.showFeedback & WM_SHOW_FB_PLACEMENT)
    {
	DoFeedback (pcd, placeX, placeY, placeWidth, placeHeight, 
	            0, placeResize);
    }
} /* END OF FUNCTION HandlePlacementKeyEvent */


/*************************************<->*************************************
 *
 *  HandlePlacementButtonEvent (pev)
 *
 *
 *  Description:
 *  -----------
 *  XXDescription ...
 *
 *
 *  Inputs:
 *  ------
 *  pev		- pointer to button event
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/

void HandlePlacementButtonEvent (XButtonEvent *pev)
{
    /*
     * Only listen to button 1
     */
    if (pev->button == 1)
    {
	/* 
	 * Complete interactive placement on button release
	 */
	if (pev->type == ButtonRelease) 
	{
	    placementDone = TRUE;		/* global done flag */
	}

	else if (pev->type == ButtonPress) 
	{

	/*
	 * Button press, go to pre-resize mode
	 */
	    wmGD.preMoveX = pev->x_root;
	    wmGD.preMoveY = pev->y_root;
	    wmGD.preMove = TRUE;
	}
    }
}



/*************************************<->*************************************
 *
 *  HandlePlacementMotionEvent (pcd, pev)
 *
 *
 *  Description:
 *  -----------
 *  Handles pointer motion events during interactive placement
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 *  pev		- pointer to mouse motion event
 *
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/

void HandlePlacementMotionEvent (ClientData *pcd, XMotionEvent *pev)
{
    int diffx, diffy;

    /*
     * If in pre-resize mode, check for motion crossing threshhold before 
     * switching modes
     */
    if (wmGD.preMove) {
	diffx = pev->x_root - wmGD.preMoveX;
	diffy = pev->y_root - wmGD.preMoveY;
	if ((ABS(diffx) > wmGD.moveThreshold) ||
	    (ABS(diffy) > wmGD.moveThreshold))
	{
	    StartInteractiveSizing(pcd, pev->time);
	}
	return; 
    }

    if (placeResize) {
	/*
	 * Track lower right corner
	 */
	if (pev->x_root > placeX)
	    placeWidth = pev->x_root - placeX + 1;
	if (pev->y_root > placeY)
	    placeHeight = pev->y_root - placeY + 1;
    }
    else {
	/*
	 * track window position
	 */
	placeX = pev->x_root;
	placeY = pev->y_root;
    }

    placePointerX = pev->x_root;
    placePointerY = pev->y_root;

    FixFrameValues (pcd, &placeX, &placeY, &placeWidth, &placeHeight,
		    placeResize);

    MoveOutline (placeX, placeY, placeWidth, placeHeight);

    if (wmGD.showFeedback & WM_SHOW_FB_PLACEMENT)
    {
	DoFeedback (pcd, placeX, placeY, placeWidth, placeHeight, 
	            0, placeResize);
    }
} /* END OF FUNCTION HandlePlacementMotionEvent */



/*************************************<->*************************************
 *
 *  DoPlacement (pcd)
 *
 *
 *  Description:
 *  -----------
 *  Gets window configuration from the user via pointer/keyboard interaction
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 *
 * 
 *  Outputs:
 *  -------
 *  pcd		- clientX, clientY, clientWidth, and clientHeight members
 *		  could be changed
 *
 *  Comments:
 *  --------
 *  We try to be careful only to remove events that we need from the 
 *  event queue while we're in our own event processing loop.
 * 
 *************************************<->***********************************/
void DoPlacement (ClientData *pcd)
{
    XEvent event;

    /*
     * Initialization
     */
    SetupPlacement (pcd);

    /*
     * Process events
     */
    placementDone = FALSE;
    while (!placementDone)
    {
	GetConfigEvent (DISPLAY, ACTIVE_ROOT, GRAB_MASK,
		placePointerX, placePointerY, placeX, placeY, 
		placeWidth, placeHeight, &event);

	switch (event.type) {
	    case KeyPress:
		HandlePlacementKeyEvent(pcd, (XKeyEvent *)&event);
		break;

	    case ButtonPress:
		HandlePlacementButtonEvent((XButtonEvent *)&event);
		break;

	    case ButtonRelease:
		HandlePlacementButtonEvent((XButtonEvent *)&event);
		break;

	    case MotionNotify:
		HandlePlacementMotionEvent(pcd, (XMotionEvent *)&event);
		break;

	}
    }
    
    /* copy back the configuration information */
    pcd->clientX = placeX + placeOffsetX;
    pcd->clientY = placeY + placeOffsetY; 
    pcd->clientWidth = placeWidth - 2*placeOffsetX;
    pcd->clientHeight = placeHeight - placeOffsetX - placeOffsetY;

    /* clean up */
    MoveOutline (0,0,0,0);
    HideFeedbackWindow(pcd->pSD);

} /* END OF FUNCTION DoPlacement  */



/*************************************<->*************************************
 *
 *  PlaceWindowInteractively(pcd)
 *
 *
 *  Description:
 *  -----------
 *  Gets the clients size and position information interactively from the
 *  user.
 *
 *
 *  Inputs:
 *  ------
 *  pcd		- pointer to client data
 *
 * 
 *  Outputs:
 *  -------
 *
 *
 *  Comments:
 *  --------
 * 
 *************************************<->***********************************/
void PlaceWindowInteractively (ClientData *pcd)
{
    unsigned int gmask;

    gmask = (wmGD.freezeOnConfig)? PGRAB_MASK : NOFRZ_PGRAB_MASK;

    /*
     * Return if config is in progress or if grabs fail
     */
    if (!DoGrabs (ACTIVE_ROOT, wmGD.movePlacementCursor, 
	gmask, CurrentTime, pcd, True))
    {
	return;
    }

    /*
     *  Get the size and position of the window from the user.
     *  Do our own event processing until a button-up event occurs
     */
    DoPlacement(pcd);

    /*
     * Finish up and return the data
     */
    UndoGrabs();
    wmGD.preMove = FALSE;
}




