/* windMove.c -
 *
 *	This file contains the functions which move windows around on
 *	the screen.  It does not contain the functions that change the
 *	contents of the windows.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * 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.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header$";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>

#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "graphics/glyphs.h"
#include "windows/windInt.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "utils/malloc.h"

/* The following own variable is used to pass information between
 * WindReframe and windFindUnobscured.
 */

static MagWindow *sharedW;

/*
 * By default, new windows have scroll bars, border, a title caption,
 * and allow standard window commands.
 */
int WindDefaultFlags = WIND_SCROLLBARS | WIND_CAPTION |
			WIND_BORDER | WIND_COMMANDS |
			WIND_SCROLLABLE;

/*
 * A mask of the current window IDs, as well as a limit on the number of
 * windows we can create.
 */

int windWindowMask = 0;  /* One bit per window ID */
int windMaxWindows = 32; /* May be decreased via the WIND_MAX_WINDOWS() macro */
int windCurNumWindows = 0;


/*
 * ----------------------------------------------------------------------------
 * windUnlink --
 *
 *	Unlink a window from the doubly linked list of windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is unlinked.
 * ----------------------------------------------------------------------------
 */

void
windUnlink(w)
    MagWindow *w;
{
    ASSERT(w != (MagWindow *) NULL, "windUnlink");
    ASSERT(windTopWindow != (MagWindow *) NULL, "windUnlink");
    ASSERT(windBottomWindow != (MagWindow *) NULL, "windUnlink");
    ASSERT(windTopWindow->w_prevWindow == (MagWindow *) NULL, "windUnlink");
    ASSERT(windBottomWindow->w_nextWindow == (MagWindow *) NULL, "windUnlink");

    if ( (windTopWindow == w) || (windBottomWindow == w) )
    {
	if (windTopWindow == w)
	{
	    windTopWindow = w->w_nextWindow;
	    if (windTopWindow != (MagWindow *) NULL)
		windTopWindow->w_prevWindow = (MagWindow *) NULL;
	}
	if (windBottomWindow == w)
	{
	    windBottomWindow = w->w_prevWindow;
	    if (windBottomWindow != (MagWindow *) NULL)
		windBottomWindow->w_nextWindow = (MagWindow *) NULL;
	}
    }
    else
    {
       w->w_nextWindow->w_prevWindow = w->w_prevWindow;
       w->w_prevWindow->w_nextWindow = w->w_nextWindow;
    }

    w->w_nextWindow = (MagWindow *) NULL;
    w->w_prevWindow = (MagWindow *) NULL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * windFree --
 *
 * 	This local procedure does the dirty work of freeing up
 *	memory in a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All storage associated with w is returned to the free pool.
 *
 * ----------------------------------------------------------------------------
 */

void
windFree(w)
    MagWindow *w;
{
    windWindowMask &= ~(1 << w->w_wid);
    windCurNumWindows--;
    if (w->w_caption != (char *) NULL) freeMagic(w->w_caption);
    if (w->w_iconname != (char *) NULL) freeMagic(w->w_iconname);
    if (GrFreeBackingStorePtr != NULL) (*GrFreeBackingStorePtr)(w);
    if (w->w_redrawAreas != (ClientData) NULL) {
	DBFreePaintPlane( (Plane *) w->w_redrawAreas);
	TiFreePlane( (Plane *) w->w_redrawAreas);
    }
    freeMagic( (char *) w);
}

/*
 * ----------------------------------------------------------------------------
 * WindSetWindowAreas --
 *
 *	Given the location of the window on the screen, compute w->w_allArea
 *	and w->w_screenArea in the window's own coordinate system.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies w->w_allArea and w->w_screenArea.
 * ----------------------------------------------------------------------------
 */

void
WindSetWindowAreas(w)
    MagWindow *w;
{
    switch ( WindPackageType )
    {
	case WIND_X_WINDOWS:
	    /* Windows have origin at lower-left corner */
	    w->w_allArea.r_xbot = w->w_allArea.r_ybot = 0;
	    w->w_allArea.r_xtop = w->w_frameArea.r_xtop - w->w_frameArea.r_xbot;
	    w->w_allArea.r_ytop = w->w_frameArea.r_ytop - w->w_frameArea.r_ybot;
	    break;

	default:
	    /* Windows are all in the same coordinate system */
	    w->w_allArea = w->w_frameArea;
    }
    WindOutToIn(w, &w->w_allArea, &w->w_screenArea);
}


/*
 * ----------------------------------------------------------------------------
 * windSetWindowPosition --
 *
 *	(deprecated function)
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windSetWindowPosition(w)
    MagWindow *w;
{
}

/*
 * ----------------------------------------------------------------------------
 * WindDelete --
 *
 *	Delete a window.
 *
 * Results:
 *	TRUE if the window was deleted, FALSE otherwise.
 *
 * Side effects:
 *	The window disappears from the sreen.  The window's client is notified
 *	that this is about to happen, and it may refuse to let it happen.
 * ----------------------------------------------------------------------------
 */

bool
WindDelete(w)
    MagWindow *w;
{
    clientRec *cr;

    ASSERT(w != (MagWindow *) NULL, "WindDelete");

    cr = (clientRec *) w->w_client;
    if ( (cr->w_delete == NULL) || (*(cr->w_delete))(w) )
    {
	WindAreaChanged(w, &(w->w_allArea) );
	if (GrDeleteWindowPtr != NULL) (*GrDeleteWindowPtr)(w);
	windUnlink(w);
	windReClip();
	windFree(w);
	return TRUE;
    }
    else
	return FALSE;
}


/*
 * ----------------------------------------------------------------------------
 * WindCreate --
 *
 *	Create a new window for the specified client.
 *
 * Results:
 *	A pointer to the new window, or NULL if one couldn't be created.
 *
 * Side effects:
 *	An empty window is created, and it is displayed.
 *	The new window is place on top of all the other
 *	windows.
 * ----------------------------------------------------------------------------
 */

MagWindow *
WindCreate(client, frameArea, isHint, argc, argv)
    WindClient client;		/* The client that will control this window */
    Rect *frameArea;		/* The area that the window is to occupy */
    bool isHint;		/* TRUE if the above rectangle is only a
				 * hint and it is OK for a window package to
				 * override it to maintain a consistent
				 * user interface.
				 */
    int argc;			/* Passed to the client */
    char *argv[];
{
    MagWindow *w;
    clientRec *cr;
    bool OK;
    int id;

    if (windCurNumWindows + 1 > windMaxWindows) {
	TxError("Can't have more than %d windows.\n", windMaxWindows);
	return NULL;
    }
    windCurNumWindows++;

    cr = (clientRec *) client;

    /* initialize the window */
    w = (MagWindow *) mallocMagic( sizeof(MagWindow) );
    w->w_client = client;
    w->w_flags = WindDefaultFlags;
    w->w_clipAgainst = (LinkedRect *) NULL;
    w->w_caption = (char *) NULL;
    w->w_stippleOrigin.p_x = 0;
    w->w_stippleOrigin.p_y = 0;
    w->w_bbox = NULL;
    w->w_grdata = (ClientData) NULL;
    w->w_grdata2 = (ClientData) NULL;
    w->w_backingStore = (ClientData)NULL;
    w->w_redrawAreas = (ClientData) NULL;
    w->w_surfaceID = (ClientData) NULL;
    w->w_iconname = NULL;
    for (id = 0; ((1 << id) & windWindowMask) != 0; id++) /* advance id */ ;
    windWindowMask |= (1 << id);
    w->w_wid = id;

    /* locate window on the screen */
    if (frameArea == (Rect *) NULL)
    {
	switch ( WindPackageType )
	{
	    case WIND_X_WINDOWS:
		/*
		 * Create default size window in upper left corner
		 * of screen.
		 */
		w->w_frameArea.r_xbot = GrScreenRect.r_xbot;
		w->w_frameArea.r_ytop = GrScreenRect.r_ytop;
		w->w_frameArea.r_xtop =
		    (GrScreenRect.r_xtop - GrScreenRect.r_xbot) / 2;
		w->w_frameArea.r_ybot =
		    (GrScreenRect.r_ytop - GrScreenRect.r_ybot) / 2;
		break;

	    default:
		w->w_frameArea = GrScreenRect;
	}
    }
    else
	w->w_frameArea = *frameArea;

    WindSetWindowAreas(w);

    /* now link the window in on top */
    w->w_nextWindow = windTopWindow;
    w->w_prevWindow = (MagWindow *) NULL;
    if (windTopWindow == (MagWindow *) NULL)
	windBottomWindow = w;
    else
	windTopWindow->w_prevWindow = w;
    windTopWindow = w;

    /* notify the client */
    OK = ((cr->w_create == NULL) || (*(cr->w_create))(w, argc, argv));

#ifdef THREE_D
    if (strcmp(cr->w_clientName, "wind3d"))
#endif

    if (OK && (GrCreateWindowPtr != NULL))
	OK = (*GrCreateWindowPtr)(w, (argc > 1) ? argv[1] : NULL);

    if (OK)
    {
	WindSetWindowAreas(w);
	windSetWindowPosition(w);
	WindAreaChanged(w, &(w->w_allArea));
    }
    else
    {
	/* the client refused the new window */
	windUnlink(w);
	windFree(w);
	w = (MagWindow *) NULL;
    }
    windReClip();
    if ((GrCreateBackingStorePtr != NULL) && (w != NULL) &&
		(!(w->w_flags & WIND_OBSCURED)))
	(*GrCreateBackingStorePtr)(w);

    return w;
}

/*
 * ----------------------------------------------------------------------------
 *
 * WindOutToIn and WindInToOut --
 *
 * 	The two procedures on this page translate from window inside
 *	area (the area used to display the surface) to window
 *	outside area (the total area of the window including caption),
 *	and vice versa.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Each procedure modifies its third parameter.  WindOutToIn
 *	fills in the third parameter with the inside area of the
 *	window whose outside area is out, and WindInToOut does the
 *	opposite.
 *
 * ----------------------------------------------------------------------------
 */

void
WindOutToIn(w, out, in)
    MagWindow *w;			/* Window under consideration */
    Rect *out;			/* Pointer to rectangle of outside area of
				 * a window.
				 */
    Rect *in;			/* Pointer to rectangle to be filled in with
				 * inside area corresponding to out.
				 */
{
    *in = *out;
    in->r_xbot += LEFT_BORDER(w);
    in->r_xtop -= RIGHT_BORDER(w);
    in->r_ybot += BOT_BORDER(w);
    in->r_ytop -= TOP_BORDER(w);
}

void WindInToOut(w, in, out)
    MagWindow *w;			/* Window under consideration */
    Rect *in;			/* Pointer to rectangle of outside area of
				 * a window.
				 */
    Rect *out;			/* Pointer to rectangle to be filled in with
				 * inside area corresponding to out.
				 */
{
    *out = *in;
    out->r_xbot -= LEFT_BORDER(w);
    out->r_xtop += RIGHT_BORDER(w);
    out->r_ybot -= BOT_BORDER(w);
    out->r_ytop += TOP_BORDER(w);
}


/*
 * ----------------------------------------------------------------------------
 * WindUnder --
 *
 *	Move a window underneath the rest.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is moved so that it is underneath the rest.  This will
 *	cause portions of uncovered windows to be redisplayed.
 * ----------------------------------------------------------------------------
 */

void
WindUnder(w)
    MagWindow *w;		/* the window to be moved */
{
    Rect area;
    MagWindow *w2;

    switch ( WindPackageType )
    {
	case WIND_X_WINDOWS:
	    if ( GrUnderWindowPtr )
		(*GrUnderWindowPtr)(w);
	    break;
	default:
	    /* Mark for redisplay all the areas that this window
	     * currently obscures.
	     */

	    for (w2 = w->w_nextWindow; w2 != NULL; w2 = w2->w_nextWindow)
	    {
		area = w2->w_allArea;
		GeoClip(&area, &w->w_allArea);
		if ((area.r_xbot <= area.r_xtop) && (area.r_ybot <= area.r_ytop))
		    WindAreaChanged(w, &area);
	    }

	    /* take the window out of the linked list */
	    windUnlink(w);

	    /* now link it back in at the bottom */
	    w->w_prevWindow = windBottomWindow;
	    if (windBottomWindow != (MagWindow *) NULL)
		windBottomWindow->w_nextWindow = w;
	    else
		windTopWindow = w;
	    windBottomWindow = w;

	    windReClip();
    }
}


/*
 * ----------------------------------------------------------------------------
 * WindOver --
 *
 *	Move a window so that it is over (on top of) the other windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is moved to the top.  This may obscure some other windows.
 *	The window that is moved will be redisplayed.
 * ----------------------------------------------------------------------------
 */

void
WindOver(w)
    MagWindow *w;		/* the window to be moved */
{
    LinkedRect *r;
    Rect area;

    switch ( WindPackageType )
    {
	case WIND_X_WINDOWS:
	    if ( GrOverWindowPtr )
		(*GrOverWindowPtr)(w);
	    break;

	default:
	    /* Mark for redisplay all of the areas of the screen that
	     * currently obscure this window.
	     */

	    for (r = w->w_clipAgainst; r != NULL; r = r->r_next)
	    {
		area = r->r_r;
		GeoClip(&area, &w->w_frameArea);
		if ((area.r_xbot <= area.r_xtop) && (area.r_ybot <= area.r_ytop))
		    WindAreaChanged((MagWindow *) NULL, &area);
	    }

	    /* take the window out of the linked list */
	    windUnlink(w);

	    /* now link it back in at the top */
	    w->w_nextWindow = windTopWindow;
	    if (windTopWindow != (MagWindow *) NULL)
		windTopWindow->w_prevWindow = w;
	    else
		windBottomWindow = w;
	    windTopWindow = w;

	    windReClip();
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * windFindUnobscured --
 *
 * 	Locates one portion of a rectangle that is unobscured, if
 *	any part of the rectangle is unobscured.  Used only by
 *	WindReframe.
 *
 * Results:
 *	Always returns TRUE.
 *
 * Side effects:
 *	The caller must place in the shared variable sharedW the
 *	name of a window, or NULL.  That window, and all windows
 *	above it, are checked to see if any obscure area.  If
 *	there is any unobscured part of area, it is placed in
 *	okArea.  If several distinct parts of area are unobscured,
 *	one, but only one, of them will be placed in okArea.
 *
 * ----------------------------------------------------------------------------
 */

int
windFindUnobscured(area, okArea)
    Rect *area;				/* Area that may be obscured. */
    Rect *okArea;			/* Modified to contain one of the
					 * unobscured areas.
					 */
{
    MagWindow *w;
    w = sharedW;
    if (w == NULL)
    {
	*okArea = *area;
	return FALSE;
    }
    sharedW = w->w_prevWindow;
    (void) GeoDisjoint(area, &w->w_frameArea,
	windFindUnobscured, (ClientData) okArea);
    return FALSE;
}

/*
 * ----------------------------------------------------------------------------
 * WindReframe --
 *
 *	Change the size or location of a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is moved, and areas of the screen are marked for
 *	redisplay.  This routine tries to be tricky in order to avoid
 *	massive redisplay for small changes.
 * ----------------------------------------------------------------------------
 */

void
WindReframe(w, r, inside, move)
    MagWindow *w;		/* the window to be reframed */
    Rect *r;		/* the new location in screen coordinates */
    bool inside;	/* TRUE if the rectangle is the screen location of
			 * the inside of the window, FALSE if the above
			 * rectangle includes border areas.
			 */
    bool move;		/* Move the coordinate system of the window the same
			 * amount as the lower left corner of the window?
			 */
{
    Rect newFrameArea;			/* New w_frameArea. */
    Rect dontRedisplay;			/* Used to record an area that does
					 * not have to be redisplayed.
					 */
    int xmove, ymove;			/* Distance window is moving. */
    extern int windReframeFunc();	/* Forward declaration. */
    clientRec *cr;

    cr = (clientRec *) w->w_client;

    /* Ensure that the new window size is not inside out and has some size to
     * it.  Compute the new w_frameArea (in newFrameArea).
     */

    GeoCanonicalRect(r, &newFrameArea);
    if (inside) WindInToOut(w, &newFrameArea, &newFrameArea);

    if ((w->w_flags & WIND_ISICONIC) == 0) {
	/* Not iconic -- enforce a minimum size */
	newFrameArea.r_xtop = MAX(newFrameArea.r_xtop,
	    newFrameArea.r_xbot + WIND_MIN_WIDTH);
	newFrameArea.r_ytop = MAX(newFrameArea.r_ytop,
	newFrameArea.r_ybot + WIND_MIN_HEIGHT);
    }

    /* Give the client a chance to modify the change. */
    if (cr->w_reposition != NULL)
	(*(cr->w_reposition))(w, &newFrameArea, FALSE);


    /* If the window coordinates are moving, update the transform
     * so that the lower-left corner of the window remains at the
     * same location in surface coordinates.
     */

    if (move)
    {
	xmove = newFrameArea.r_xbot - w->w_frameArea.r_xbot;
	w->w_origin.p_x += xmove << SUBPIXELBITS;
	ymove = newFrameArea.r_ybot - w->w_frameArea.r_ybot;
	w->w_origin.p_y += ymove << SUBPIXELBITS;
	w->w_stippleOrigin.p_x += xmove;
	w->w_stippleOrigin.p_y += ymove;
    }

    switch ( WindPackageType )
    {
	case WIND_X_WINDOWS:
	    break;

	default:
	    /* Now comes the tricky part:  figuring out what to redisplay.
	     * The simple way out is to force redisplay at both the old and
	     * new window positions.  Naturally this code is going to be more
	     * ambitious.  There are two steps.  First, figure out what piece
	     * of the old window must be redisplayed, then move the window,
	     * then figure out what pieces of the new window must be redisplayed.
	     * If the window coordinates aren't moving, then any screen area
	     * common to the old and new positions needn't be redisplayed,
	     * since its contents won't change.
	     */

	     if (!move)
	     {
		/* Compute the intersection of the old and new areas in
		 * dontRedisplay.  Mark old areas outside of this common area as
		 * needing to be redisplayed.
		 */
		WindOutToIn(w, &newFrameArea, &dontRedisplay);
		GeoClip(&dontRedisplay, &w->w_screenArea);
		(void) GeoDisjoint(&w->w_frameArea, &dontRedisplay, windReframeFunc,
		    (ClientData) w);
	    }
	    else
	    {
		/* Record the entire old area as needing to be redisplayed. */

		WindAreaChanged(w, &w->w_allArea);
		dontRedisplay = w->w_allArea;
	    }
    }

    /* At this point, we've recorded any old area that needs to be
     * redisplayed.
     */

    w->w_frameArea = newFrameArea;
    WindSetWindowAreas(w);
    windSetWindowPosition(w);
    windFixSurfaceArea(w);
    windReClip();

    switch (WindPackageType)
    {
	case WIND_X_WINDOWS:
	    /* Regenerate backing store, if enabled */
	    if ((GrCreateBackingStorePtr != NULL) &&
			(!(w->w_flags & WIND_OBSCURED)))
		(*GrCreateBackingStorePtr)(w);
	    break;

	default:
	    /* Now that the window has been moved, record any of the new area that
	     * has to be redisplayed.
	     */

	    (void) GeoDisjoint(&w->w_allArea, &dontRedisplay, windReframeFunc,
		(ClientData) w);
    }

    /* Give the client a chance to do things like windMove().
     */
    if (cr->w_reposition != NULL)
	(*(cr->w_reposition))(w, &newFrameArea, TRUE);
}

int
windReframeFunc(area, w)
    Rect *area;			/* Area to redisplay. */
    MagWindow *w;			/* Window in which to redisplay. */

{
    WindAreaChanged(w, area);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * WindFullScreen --
 *
 * 	This procedure blows a window up so it's on top of all the others
 *	and is full-screen.  Or, if the window was already full-screen,
 *	it is put back where it came from before it was made full-screen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window's size and location are changed.
 *
 * ----------------------------------------------------------------------------
 */

void
WindFullScreen(w)
    MagWindow *w;			/* Window to be blown up or shrunk back. */
{
    int i;
    MagWindow *w2;
    Rect newFrameArea;
    clientRec *cr;
    int newDepth;

    cr = (clientRec *) w->w_client;

    /* Compute default new location. */

    if (w->w_flags & WIND_FULLSCREEN)
	newFrameArea = w->w_oldArea;
    else
	newFrameArea = GrScreenRect;

    /* Give the client a chance to modify newFrameArea. */

    if (cr->w_reposition != NULL)
	(*(cr->w_reposition))(w, &newFrameArea, FALSE);

    /*
     * Now, actually modify the window and its position.
     */

    /* Compute new stuff. */

    if (w->w_flags & WIND_FULLSCREEN)
    {
	newDepth = w->w_oldDepth;
	w->w_flags &= ~WIND_FULLSCREEN;
    }
    else
    {
	newDepth = 0;
	w->w_flags |= WIND_FULLSCREEN;

	/* Record old depth and area. */

	w->w_oldArea = w->w_frameArea;
	w->w_oldDepth = 0;
	for (w2 = windTopWindow; w2 != w; w2 = w2->w_nextWindow)
	{
	    ASSERT(w2 != (MagWindow *) NULL, "WindFullScreen");
	    w->w_oldDepth += 1;
	}
    }


    /* Change the view and screen location. */
    w->w_frameArea = newFrameArea;
    WindSetWindowAreas(w);
    windSetWindowPosition(w);
    WindMove(w, &w->w_surfaceArea);

    /* Move the window to the proper depth.  */
    if (windTopWindow != (MagWindow *) NULL)
    {
	if (newDepth == 0)
	{
	    switch ( WindPackageType )
	    {
		case WIND_X_WINDOWS:
		    break;

		default:
		    WindOver(w);
	    }
	}
	else
	{
	    windUnlink(w);
	    w2 = windTopWindow;
	    for (i=1; i<newDepth; i++)
		if (w2->w_nextWindow != NULL) w2 = w2->w_nextWindow;
	    w->w_nextWindow = w2->w_nextWindow;
	    w->w_prevWindow = w2;
	    w2->w_nextWindow = w;
	    if (w->w_nextWindow == NULL)
		windBottomWindow = w;
	    else
		w->w_nextWindow->w_prevWindow = w;
	    windReClip();
	}
    }

    /* Notify the client. */
    if (cr->w_reposition != NULL)
	(*(cr->w_reposition))(w, &newFrameArea, TRUE);

    /* Record new display areas. */
    switch (WindPackageType)
    {
	case WIND_X_WINDOWS:
	    if (GrConfigureWindowPtr != NULL)
		(*GrConfigureWindowPtr)(w);
	    if (GrCreateBackingStorePtr != NULL &&
			(!(w->w_flags & WIND_OBSCURED)))
		(*GrCreateBackingStorePtr)(w);
	    break;
	default:
	    WindAreaChanged((MagWindow *) NULL, (Rect *) NULL);
    }
}
