/*****************************************************************************/
/**               Copyright 1990 by Solbourne Computer Inc.                 **/
/**                          Longmont, Colorado                             **/
/**                                                                         **/
/**                           All Rights Reserved                           **/
/**                                                                         **/
/**    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 and that both  that  copyright  notice  and  this  permis-    **/
/**    sion  notice appear in supporting  documentation,  and  that  the    **/
/**    name of Solbourne not be used in advertising                         **/
/**    in publicity pertaining to distribution of the  software  without    **/
/**    specific, written prior permission.                                  **/
/**                                                                         **/
/**    SOLBOURNE COMPUTER INC. DISCLAIMS ALL WARRANTIES WITH REGARD         **/
/**    TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES  OF  MERCHANT-    **/
/**    ABILITY  AND  FITNESS,  IN  NO  EVENT SHALL SOLBOURNE                **/
/**    BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAM-           **/
/**    AGES OR  ANY DAMAGES WHATSOEVER  RESULTING FROM LOSS OF USE, DATA    **/
/**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
/**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
/**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
/*****************************************************************************/

/**********************************************************************
 *
 * $XConsortium: vdt.c,v 1.140 90/03/23 11:42:33 jim Exp $
 *
 * $Log: vdt.c,v $
 * Revision 1.2  1991/09/25  23:04:32  cross
 * Modified all calls to FindBitmap to make them FindPixmap
 * Also, took out the code that makes the returned bitmap into
 * a pixmap, cause XcprFilePixmapFile now returns a pixmap all
 * the time...
 *
 * Revision 1.1  1991/09/25  22:54:13  cross
 * Initial revision
 *
 * Revision 10.0  91/06/12  09:05:53  toml
 * Revision bump
 * 
 * Revision 9.1  91/06/12  08:39:24  toml
 * Added patch to do gridded movement in panner
 * 
 * Revision 9.0  91/04/23  07:40:54  toml
 * Revision bump
 * 
 * Revision 8.5  91/04/18  08:19:20  toml
 * Disable backing store on the virtual desktop
 * 
 * Revision 8.4  91/04/15  14:54:18  toml
 * Fixes panner interaction under OpenWindows
 * 
 * Revision 8.3  90/12/29  15:26:03  toml
 * Made PannerOpaqueScroll only take effect if StickyAbove is also set.
 * 
 * Revision 8.2  90/12/29  11:24:22  toml
 * Added PannerOpaqueScroll
 * 
 * Revision 8.1  90/12/29  09:56:37  toml
 * Disallow unmapnotify is window is panner
 * 
 * Revision 8.0  90/11/15  20:02:56  toml
 * Revision bump
 * 
 * Revision 7.4  90/11/12  21:34:52  toml
 * Implemented Scr->StickyAbove
 * 
 * Revision 7.3  90/11/12  20:39:37  toml
 * Fixes to stickyroot patches
 * 
 * Revision 7.2  90/11/12  19:57:21  toml
 * Patches to allow sticky windows to lower
 * 
 * Revision 1.3  90/11/04  19:23:50  brossard
 * When mapping a window and warping the cursor to it, only scroll
 * the desktop if the window is not visible on screen.  (This was
 * actually done in the previous revistion, this one just fixes
 * the maths to do it right).
 * 
 * Revision 1.2  90/11/04  18:38:36  brossard
 * Sticky windows are now child of the virtual root.
 * This has the advantage that they can now be raised and lowered like
 * any other window.  They no longuer are above everything else.
 * It has the disadvantage that when you move the desktop, the
 * sticky windows have to be moved back after scrolling the desktop.
 * 
 *
 * Virtual Desktop routines
 *
 * 22-Aug-90 Tom LaStrange        Initial Version.
 *
 **********************************************************************/

#if !defined(lint) && !defined(SABER)
static char RCSinfo[]=
"$XConsortium: vdt.c,v 1.140 90/03/23 11:42:33 jim Exp $";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include "twm.h"
#include "screen.h"
#include "vdt.h"
#include "parse.h"
#include "util.h"
#include "move.h"
#include "resize.h"

#define MAX_VDT_WIDTH	32000
#define MAX_VDT_HEIGHT	32000

/* private swm atoms */
/* it would be nice at some point to get these atoms blessed so that
 * we don't have to make them swm specific
 */

/* version 1.2 of swm sends synthetic ConfigureNotify events
 * with respect to the actual root window rather than a 
 * clients logical root window.  OI clients lokk for this 
 * version string on the root window to determine how to
 * interpret the ConfigureNotify event
 */
#define SWM_VERSION "1.2"

Atom XA_SWM_ROOT = None;
Atom XA_SWM_VROOT = None;
Atom XA_SWM_VERSION = None;

/***********************************************************************
 *
 *  Procedure:
 *	InitVirtualDesktop - init vdt stuff
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
InitVirtualDesktop()
{
    XA_SWM_ROOT          = XInternAtom(dpy, "__SWM_ROOT", False);
    XA_SWM_VROOT         = XInternAtom(dpy, "__SWM_VROOT", False);
    XA_SWM_VERSION       = XInternAtom(dpy, "__SWM_VERSION", False);
}

/***********************************************************************
 *
 *  Procedure:
 *	SetSWM_ROOT - set the XA_SWM_ROOT property 
 *		This property always indicates to the application
 *		what its "root" window is.  This is currently needed
 *		by OI based clients, other toolkits don't know about
 *		virtual desktops.
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
SetSWM_ROOT(TwmWindow *tmp_win)
{
    if (Scr->VirtualDesktop && !tmp_win->iconmgr)
    {
	XChangeProperty(dpy, tmp_win->w, XA_SWM_ROOT, XA_WINDOW,
	    32, PropModeReplace, (unsigned char *)&tmp_win->root, 1);
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	SetSWM_VERSION - set the XA_SWM_VERSION property 
 *		This property always indicates to the application
 *		what its "root" window is.  This is needed by OI
 *		clients so they can know how to interpret the
 *		incoming synthetic ConfigureNotify events.
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
SetSWM_VERSION(void)
{
    static char *version = SWM_VERSION;
    XChangeProperty(dpy,Scr->Root,XA_SWM_VERSION, XA_STRING, 8,
	PropModeReplace, (unsigned char *)version, strlen(version));
}

/***********************************************************************
 *
 *  Procedure:
 *	RemoveSWM_VERSION - remove the XA_SWM_VERSION property 
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
RemoveSWM_VERSION(void)
{
    XDeleteProperty(dpy, Scr->Root, XA_SWM_VERSION);
}

/***********************************************************************
 *
 *  Procedure:
 *	MakeVirtual - make a small virtual window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

Window
MakeVirtual(TwmWindow *tmp_win, int x, int y, int width, int height,
	    long background, long border)
{
    Window virtual;

    width /= Scr->PannerScale;
    height /= Scr->PannerScale;
    if (width <= 0) width = 1;
    if (height <= 0) height = 1;
    if (width > MAX_VDT_WIDTH) width = MAX_VDT_WIDTH;
    if (height > MAX_VDT_HEIGHT) height = MAX_VDT_HEIGHT;
    virtual = XCreateSimpleWindow(dpy, Scr->Panner, x, y,
	width, height, 1, border, background);
    XGrabButton(dpy, Button2, 0L, virtual,
	True, ButtonPressMask | ButtonReleaseMask,
	GrabModeAsync, GrabModeAsync, Scr->Panner, None);
    XSelectInput(dpy, virtual, KeyPressMask | ExposureMask );
    XSaveContext(dpy, virtual, TwmContext, (caddr_t) tmp_win);
    XSaveContext(dpy, virtual, VirtualContext, (caddr_t) tmp_win);
    XSaveContext(dpy, virtual, ScreenContext, (caddr_t) Scr);
    return (virtual);
}

/***********************************************************************
 *
 *  Procedure:
 *	ResizeVirtual - resize one of the small virtual windows
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
ResizeVirtual(Window window, int width, int height)
{
    if (window) {
	width /= Scr->PannerScale;
	height /= Scr->PannerScale;
	if (width <= 0) width = 1;
	if (height <= 0) height = 1;
	XResizeWindow(dpy, window, width, height);
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	MapFrame - map a client window and its frame
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MapFrame(TwmWindow *tmp_win)
{
    XMapWindow(dpy, tmp_win->w);
    XMapWindow(dpy, tmp_win->frame);
    if (tmp_win->virtualWindow && !tmp_win->sticky)
	XMapWindow(dpy, tmp_win->virtualWindow);
}

/***********************************************************************
 *
 *  Procedure:
 *	UnmapFrame - unmap a client window and its frame
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
UnmapFrame(TwmWindow *tmp_win)
{
    XUnmapWindow(dpy, tmp_win->frame);
    if (!tmp_win->iconmgr && tmp_win->w != Scr->Panner)
	XUnmapWindow(dpy, tmp_win->w);
    if (tmp_win->virtualWindow && !tmp_win->sticky)
	XUnmapWindow(dpy, tmp_win->virtualWindow);
}

/***********************************************************************
 *
 *  Procedure:
 *	RaiseFrame - raise a client window and its frame
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
RaiseFrame(TwmWindow *tmp_win)
{
    XRaiseWindow(dpy, tmp_win->frame);
    if (tmp_win->virtualWindow && !tmp_win->sticky)
	XRaiseWindow(dpy, tmp_win->virtualWindow);
}

/***********************************************************************
 *
 *  Procedure:
 *	LowerFrame - lower a client window and its frame
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
LowerFrame(TwmWindow *tmp_win)
{
    XWindowChanges xwc;

    if (Scr->StickyAbove && tmp_win->sticky && Scr->VirtualDesktop) {
	xwc.sibling = Scr->VirtualDesktop;
	xwc.stack_mode = Above;
	XConfigureWindow(dpy, tmp_win->frame, CWSibling|CWStackMode, &xwc);
    }
    else
	XLowerWindow(dpy, tmp_win->frame);
    if (tmp_win->virtualWindow && !tmp_win->sticky)
	XLowerWindow(dpy, tmp_win->virtualWindow);
}

/***********************************************************************
 *
 *  Procedure:
 *	MapIcon - map the icon window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MapIcon(TwmWindow *tmp_win)
{
    XMapRaised(dpy, tmp_win->icon_w);
    if (tmp_win->virtualIcon && !tmp_win->sticky)
	XMapRaised(dpy, tmp_win->virtualIcon);
}

/***********************************************************************
 *
 *  Procedure:
 *	UnmapIcon - unmap the icon window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
UnmapIcon(TwmWindow *tmp_win)
{
    XUnmapWindow(dpy, tmp_win->icon_w);
    if (tmp_win->virtualIcon && !tmp_win->sticky)
	XUnmapWindow(dpy, tmp_win->virtualIcon);
}

/***********************************************************************
 *
 *  Procedure:
 *	RaiseIcon - raise a client icon
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
RaiseIcon(TwmWindow *tmp_win)
{
    XRaiseWindow(dpy, tmp_win->icon_w);
    if (tmp_win->virtualIcon && !tmp_win->sticky)
	XRaiseWindow(dpy, tmp_win->virtualIcon);
}

/***********************************************************************
 *
 *  Procedure:
 *	LowerIcon - lower an icon
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
LowerIcon(TwmWindow *tmp_win)
{
    XWindowChanges xwc;

    if (Scr->StickyAbove && tmp_win->sticky && Scr->VirtualDesktop) {
	xwc.sibling = Scr->VirtualDesktop;
	xwc.stack_mode = Above;
	XConfigureWindow(dpy, tmp_win->icon_w, CWSibling|CWStackMode, &xwc);
    }
    else
	XLowerWindow(dpy, tmp_win->icon_w);
    if (tmp_win->virtualIcon && !tmp_win->sticky)
	XLowerWindow(dpy, tmp_win->virtualIcon);
}

/***********************************************************************
 *
 *  Procedure:
 *	MoveIcon - move an icon
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MoveIcon(TwmWindow *tmp_win, int x, int y)
{
    XMoveWindow(dpy, tmp_win->icon_w, x, y);
    tmp_win->icon_loc_x = x;
    tmp_win->icon_loc_y = y;
    if (tmp_win->virtualIcon)
	XMoveWindow(dpy, tmp_win->virtualIcon,
		    x/Scr->PannerScale, y/Scr->PannerScale);
}

/***********************************************************************
 *
 *  Procedure:
 *	MakeVirtualDesktop - create the virtual desktop window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MakeVirtualDesktop(int width, int height)
{
    Pixmap pm;
    XSetWindowAttributes attr;
    unsigned attrMask;
    extern char *display_name;
    int fres;

/*
 *  Make vdt size an integral multiple of physical screen size
 *  if the given width and/or height are less than physical
 *  width and/or height.
 *  WM Kules, wmk@fed.frb.gov, Oct 15, 1990
 */
    if (width < Scr->MyDisplayWidth)
       width = width * Scr->MyDisplayWidth;
    if (height < Scr->MyDisplayHeight)
       height = height * Scr->MyDisplayHeight;
  
    pm = None;
    if (Scr->vdtPixmap) {
#ifndef XLOADIMAGE
/*
 *   Code segment here is original.  If XLOADIMAGE is not defined, this will
 * be used.  Else, use the xloadimage program (fork & exec) to load root
 * images.  This allows you to load more type of images than just Xbm and
 * Xpm.  If XLOADIMAGE is defined, it should be defined as the full-path
 * name of the xloadimage executable.
 *                     Chris P. Ross	12-90
 */
	pm = FindPixmap(Scr->vdtPixmap, &pmWidth, &pmHeight, &pmPixmap,
			NULL, NULL);
#else  /* defined(XLOADIMAGE) */
		fres = fork();
		if (fres < 0) fprintf(stderr, "error in fork()...\n");
		if (fres == 0) {		/* This is the child */
			execlp("xloadimage", "xsetbg", "-display",
				XDisplayName(display_name),
			       Scr->vdtPixmap, NULL);
			/* No xloadimage in path, call it explicitly... */
			execlp(XLOADIMAGE, "xsetbg", "-display",
				XDisplayName(display_name),
			       Scr->vdtPixmap, NULL);
			/* Give up, you don't get a background... */
			exit(0);
	    }
	    /* fres > 0 :  Parent, therefore continue */
#endif  /* XLOADIMAGE */
    }
    attrMask = CWOverrideRedirect | CWEventMask |
               CWBackPixmap | CWBackingStore;
    attr.override_redirect = True;
    attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask;
    attr.backing_store = NotUseful;
    attr.background_pixmap = Scr->rootWeave;
    if (pm)
	attr.background_pixmap = pm;
    else if (Scr->vdtBackgroundSet) {
	attrMask &= ~CWBackPixmap;
	attrMask |= CWBackPixel;
	attr.background_pixel = Scr->vdtC.back;
    }

    Scr->VirtualDesktop = XCreateWindow(dpy, Scr->Root, 0, 0, 
	    width+50, height+50, 0, Scr->d_depth, CopyFromParent,
	    Scr->d_visual, attrMask, &attr);
    Scr->vdtWidth = width;
    Scr->vdtHeight = height;

    /* this property allows ssetroot to find the virtual root window */
    XChangeProperty(dpy, Scr->VirtualDesktop, XA_SWM_VROOT, XA_WINDOW, 32,
	PropModeReplace, (unsigned char *)&Scr->VirtualDesktop, 1);

    if (pm)
	XFreePixmap(dpy, pm);
}

/***********************************************************************
 *
 *  Procedure:
 *	MakePanner - create the virtual desktop panner window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MakePanner(void)
{
    static XTextProperty wName={(unsigned char *) "Virtual Desktop",XA_STRING,8,15};
    static XTextProperty iName={(unsigned char *) "Desktop",XA_STRING,8,7};
    XSetWindowAttributes attr;
    XSizeHints *sizeHints;
    XWMHints *wmHints;
    XClassHint *classHints;
    int x, y, width, height;
    Pixmap pm;
    unsigned int pmWidth, pmHeight, pmPixmap;
    unsigned attrMask;

    width = Scr->vdtWidth/Scr->PannerScale;
    height = Scr->vdtHeight/Scr->PannerScale;

    sizeHints = XAllocSizeHints();
    sizeHints->flags = PBaseSize;
    sizeHints->base_width = width;
    sizeHints->base_height = height;
    sizeHints->min_width = Scr->MyDisplayWidth/Scr->PannerScale;
    sizeHints->min_height = Scr->MyDisplayHeight/Scr->PannerScale;
    sizeHints->max_width = MAX_VDT_WIDTH/Scr->PannerScale;
    sizeHints->max_height = MAX_VDT_HEIGHT/Scr->PannerScale;
    XWMGeometry(dpy, Scr->screen, Scr->PannerGeometry, NULL,
	1, sizeHints, &x, &y, &width, &height, &sizeHints->win_gravity);
    sizeHints->flags = USPosition|PWinGravity|PMinSize|PMaxSize;

    wmHints = XAllocWMHints();
    wmHints->initial_state = Scr->PannerState;
    wmHints->flags = StateHint;

    classHints = XAllocClassHint();
    classHints->res_name = "virtualDesktop";
    classHints->res_class = "Twm";

    pm = None;
    if (Scr->PannerPixmap) {
	pm = FindPixmap(Scr->PannerPixmap, &pmWidth, &pmHeight, &pmPixmap,
		NULL, NULL);
    }

    attrMask = CWBackPixmap|CWEventMask;
    attr.background_pixmap = Scr->rootWeave;
    attr.event_mask = ExposureMask | ButtonPressMask;
    if (pm)
	attr.background_pixmap = pm;
    else if (Scr->PannerBackgroundSet) {
	attrMask &= ~CWBackPixmap;
	attrMask |= CWBackPixel;
	attr.background_pixel = Scr->PannerC.back;
    }
    Scr->Panner = XCreateWindow(dpy, Scr->Root, x, y, 
	    sizeHints->base_width, sizeHints->base_height, 1,
	    Scr->d_depth, CopyFromParent,
	    Scr->d_visual, attrMask, &attr);
    
    Scr->PannerWidth = sizeHints->base_width;
    Scr->PannerHeight = sizeHints->base_height;

    XGrabButton(dpy, Button1, 0L, Scr->Panner,
	True, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
	GrabModeAsync, GrabModeAsync, Scr->Panner, None);
    XGrabButton(dpy, Button3, 0L, Scr->Panner,
	True, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
	GrabModeAsync, GrabModeAsync, Scr->Panner, None);
    XSetWMProperties(dpy, Scr->Panner, &wName, &iName, NULL, 0,
	sizeHints, wmHints, classHints);

    if (Scr->PannerState != WithdrawnState)
	XMapRaised(dpy, Scr->Panner);
    XSaveContext(dpy, Scr->Panner, ScreenContext, (caddr_t) Scr);
}

void
HandlePannerExpose(XEvent *ev)
{
    XEvent dummy;

    if (ev->xexpose.count)
	return;

    if (!Scr->PannerOutlineWidth) {
	Scr->PannerOutlineWidth = Scr->MyDisplayWidth / Scr->PannerScale -1;
	Scr->PannerOutlineHeight = Scr->MyDisplayHeight / Scr->PannerScale -1;
    }
    XClearWindow(dpy, Scr->Panner);
    XDrawRectangle(dpy, Scr->Panner, Scr->PannerGC,
	Scr->PannerOutlineX, Scr->PannerOutlineY,
	Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);

    /* flush other expose events */
    while (XCheckTypedWindowEvent (dpy, Scr->Panner, Expose, &dummy)) ;
}

static int pannerButton = 0;
static int use_grid;

static void
RefreshDesktop(void)
{
	int xx = Scr->PannerOutlineX * Scr->PannerScale;
	int yy = Scr->PannerOutlineY * Scr->PannerScale;
	if (use_grid) {
		xx = xx - xx % Scr->MyDisplayWidth;
		yy = yy - yy % Scr->MyDisplayHeight;
	}
	MoveDesktop(xx, yy);
}


static int
TRUNCATE(int pos, int grid)
{
    pos *= Scr->PannerScale;
    return ((pos - pos % grid) + Scr->PannerScale - 1) / Scr->PannerScale;
}

void
HandlePannerButtonPress(XEvent *ev)
{
    if (!pannerButton) {
	/* remember the button and move the pointer to the current window */
	pannerButton = ev->xbutton.button;
	if (pannerButton == 3) {
		int pointerX = Scr->PannerOutlineX + Scr->PannerOutlineWidth/2;
		int pointerY = Scr->PannerOutlineY + Scr->PannerOutlineHeight/2;
		XWarpPointer(dpy, None, Scr->Panner, 0,0,0,0,
			     pointerX, pointerY);
		use_grid = 0;
	} else {
		use_grid = 1;
		/* figure out the position of the next outline */
		Scr->PannerOutlineX = TRUNCATE(ev->xbutton.x, Scr->MyDisplayWidth);
		Scr->PannerOutlineY = TRUNCATE(ev->xbutton.y, Scr->MyDisplayHeight);
		RefreshDesktop();
	}
	/* cause the window to be painted, and then draw the panner rectangle. */
	XClearWindow(dpy, Scr->Panner);
	XDrawRectangle(dpy, Scr->Panner, Scr->DrawGC,
	    Scr->PannerOutlineX, Scr->PannerOutlineY,
	    Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);
    }
}

void
HandlePannerButtonRelease(XEvent *ev)
{
    if (ev->xbutton.button == pannerButton) {
	pannerButton = 0;

	XDrawRectangle(dpy, Scr->Panner, Scr->DrawGC,
	    Scr->PannerOutlineX, Scr->PannerOutlineY,
	    Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);
	XClearWindow(dpy, Scr->Panner);
	XDrawRectangle(dpy, Scr->Panner, Scr->PannerGC,
	    Scr->PannerOutlineX, Scr->PannerOutlineY,
	    Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);
    }
}

void
HandlePannerMotionNotify(XEvent *ev)
{
    XEvent dummyev;
    int newOutlineX, newOutlineY;

    if (use_grid) {
	newOutlineX = TRUNCATE(ev->xmotion.x, Scr->MyDisplayWidth);
	newOutlineY = TRUNCATE(ev->xmotion.y, Scr->MyDisplayHeight);
    } else {
	newOutlineX = ev->xmotion.x - Scr->PannerOutlineWidth/2;
	newOutlineY = ev->xmotion.y - Scr->PannerOutlineHeight/2;
	if (newOutlineX < 0)
	    newOutlineX = 0;
	if (newOutlineY < 0)
	    newOutlineY = 0;
	if ((newOutlineX + Scr->PannerOutlineWidth) > Scr->PannerWidth)
	    newOutlineX = Scr->PannerWidth - Scr->PannerOutlineWidth - 1;
	if ((newOutlineY + Scr->PannerOutlineHeight) > Scr->PannerHeight)
	    newOutlineY = Scr->PannerHeight - Scr->PannerOutlineHeight - 1;
    }

    if (Scr->PannerOutlineX != newOutlineX ||
	Scr->PannerOutlineY != newOutlineY) {

	/* get rid of the last outline */
	XDrawRectangle(dpy, Scr->Panner, Scr->DrawGC,
		       Scr->PannerOutlineX, Scr->PannerOutlineY,
		       Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);

	Scr->PannerOutlineX = newOutlineX;
	Scr->PannerOutlineY = newOutlineY;

	/* draw the new outline */
	XDrawRectangle(dpy, Scr->Panner, Scr->DrawGC,
		       Scr->PannerOutlineX, Scr->PannerOutlineY,
		       Scr->PannerOutlineWidth, Scr->PannerOutlineHeight);
	RefreshDesktop();
    } else if (Scr->PannerOpaqueScroll && Scr->StickyAbove) {
	RefreshDesktop();
    }
    while(XCheckTypedEvent(dpy,MotionNotify,&dummyev))
	    ;
}


/***********************************************************************
 *
 *  Procedure:
 *	MoveDesktop - move the desktop to a pixel location
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
MoveDesktop(int x, int y)
{
    TwmWindow *tmp;
    XEvent ev;
    register int delta_x, delta_y;

    if (x != Scr->vdtPositionX || y != Scr->vdtPositionY) {
	Scr->vdtSaveX = Scr->vdtPositionX;
	Scr->vdtSaveY = Scr->vdtPositionY;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if ((x + Scr->MyDisplayWidth) > Scr->vdtWidth)
	    x = Scr->vdtWidth - Scr->MyDisplayWidth;
	if ((y + Scr->MyDisplayHeight) > Scr->vdtHeight)
	    y = Scr->vdtHeight - Scr->MyDisplayHeight;
	XMoveWindow(dpy, Scr->VirtualDesktop, -x, -y);
	Scr->PannerOutlineX = x / Scr->PannerScale;
	Scr->PannerOutlineY = y / Scr->PannerScale;
	delta_x = x - Scr->vdtPositionX; delta_y = y - Scr->vdtPositionY;
	Scr->vdtPositionX = x;
	Scr->vdtPositionY = y;

	for (tmp = Scr->TwmRoot.next; tmp != NULL; tmp = tmp->next) {
	    if (!tmp->sticky)
		SendSyntheticConfigureNotify(tmp);
	    else if (!Scr->StickyAbove) {
		if (tmp->icon_w)
		    MoveIcon(tmp, tmp->icon_loc_x + delta_x, tmp->icon_loc_y + delta_y);
		SetupFrame( tmp, tmp->frame_x + delta_x, tmp->frame_y + delta_y,
				tmp->frame_width, tmp->frame_height, -1, True );
	    }
	}

	/* go repaint the panner */
	ev.xexpose.count = 0;
	HandlePannerExpose(&ev);
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	ScrollDesktop - scroll the desktop by screenfuls
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
ScrollDesktop(int func, char *pos)
{
    int x, y;
    int status;
    unsigned int width, height;

    x = Scr->vdtPositionX;
    y = Scr->vdtPositionY;

    switch (func)
    {
	case F_SCROLLBACK:
	    x = Scr->vdtSaveX;
	    y = Scr->vdtSaveY;
	    break;
	case F_SCROLL:
	    status = XParseGeometry(pos, &x, &y, &width, &height);
	    if ((status & (XValue & YValue)) != (XValue & YValue)) {
		fprintf (stderr,
			 "twm: ignoring invalid f.scroll geometry \"%s\"\n", pos);
		return;
	    }
	    x = abs(x) * Scr->MyDisplayWidth;
	    y = abs(y) * Scr->MyDisplayHeight;
	    if (status & XNegative)
		x = Scr->vdtWidth - Scr->MyDisplayWidth - x;
	    if (status & YNegative)
		y = Scr->vdtHeight - Scr->MyDisplayHeight - y;
	    break;
	case F_SCROLLHOME:
	    x = 0;
	    y = 0;
	    break;
  	case F_SCROLLRIGHT:
 	    x += Scr->vdtScrollDistanceX;
  	    break;
  	case F_SCROLLLEFT:
 	    x -= Scr->vdtScrollDistanceX;
  	    break;
  	case F_SCROLLDOWN:
 	    y += Scr->vdtScrollDistanceY;
  	    break;
  	case F_SCROLLUP:
 	    y -= Scr->vdtScrollDistanceY;
  	    break;
    }
    if (x < 0)
	x = 0;
    if (y < 0)
	y = 0;
    if ((x + Scr->MyDisplayWidth) > Scr->vdtWidth)
	x = Scr->vdtWidth - Scr->MyDisplayWidth;
    if ((y + Scr->MyDisplayHeight) > Scr->vdtHeight)
	y = Scr->vdtHeight - Scr->MyDisplayHeight;
    MoveDesktop(x, y);
}

/***********************************************************************
 *
 *  Procedure:
 *	ResizeDesktop - resize the desktop, the coordinates are in panner
 *		units.
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
ResizeDesktop(int width, int height)
{
    int vdtWidth, vdtHeight;

    if (width != Scr->PannerWidth || height != Scr->PannerHeight) {
	Scr->PannerWidth = width;
	Scr->PannerHeight = height;

	vdtWidth = width * Scr->PannerScale;
	vdtHeight = height * Scr->PannerScale;

	if (vdtWidth < Scr->vdtWidth) {
	    if ((Scr->PannerOutlineX + Scr->PannerOutlineWidth) >= Scr->PannerWidth)
		Scr->PannerOutlineX = Scr->PannerWidth-Scr->PannerOutlineWidth - 1;
	}
	if (vdtHeight < Scr->vdtHeight) {
	    if ((Scr->PannerOutlineY+Scr->PannerOutlineHeight) >= Scr->PannerHeight)
		Scr->PannerOutlineY = Scr->PannerHeight-Scr->PannerOutlineHeight-1;
	}
	Scr->vdtWidth = vdtWidth;
	Scr->vdtHeight = vdtHeight;

	MoveDesktop(Scr->PannerOutlineX * Scr->PannerScale,
	    Scr->PannerOutlineY * Scr->PannerScale);

	/* make it just a tad larger than requested */
	XResizeWindow(dpy, Scr->VirtualDesktop, vdtWidth + 2*Scr->PannerScale,
	    vdtHeight + 2*Scr->PannerScale);
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	HandlePannerMove - prepare to move a small panner window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
HandlePannerMove(XButtonEvent *ev, TwmWindow *tmp_win)
{
    int cancel;
    int x_root, y_root;
    unsigned objWidth, objHeight;
    int moving_frame;

    tmp_win->root = Scr->Panner;

    x_root = ev->x_root;
    y_root = ev->y_root;

    if (ev->window == tmp_win->virtualWindow) {
	moving_frame = True;
	objWidth = tmp_win->frame_width + 2*tmp_win->frame_bw;
	objHeight = tmp_win->frame_height + 2*tmp_win->frame_bw;
    } else {
	moving_frame = False;
	objWidth = tmp_win->icon_w_width + 2;
	objHeight = tmp_win->icon_w_height + 2;
    }

    StartMove(tmp_win, ev->window, tmp_win->title_height,
	&x_root, &y_root, &cancel, IN_PANNER, Scr->PannerScale,
	objWidth, objHeight, False, False);

    tmp_win->root = Scr->VirtualDesktop;

    if (!cancel) {
	if (moving_frame) {
	    SetupWindow (tmp_win, x_root, y_root,
		 tmp_win->frame_width, tmp_win->frame_height, -1);
	}
	else {
	    MoveIcon(tmp_win, x_root, y_root);
	}
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	ScrollTo - scroll the virtual desktop to a window of non of
 *		the frame is visible
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
ScrollTo(TwmWindow *tmp_win)
{
    int x, y;
    int xr, yb;

    if (!tmp_win->sticky) {
	x = Scr->vdtPositionX;
	y = Scr->vdtPositionY;
	xr = x + Scr->MyDisplayWidth;
	yb = y + Scr->MyDisplayHeight;

	if ((tmp_win->frame_x + tmp_win->frame_width) <= x)
	    x = tmp_win->frame_x;
	else if (tmp_win->frame_x >= xr)
	    x = tmp_win->frame_x;

	if ((tmp_win->frame_y + tmp_win->frame_height) <= y)
	    y = tmp_win->frame_y;
	else if (tmp_win->frame_y >= yb)
	    y = tmp_win->frame_y;

	/* if we are going to scroll, we might as well do it in both
	 * directions
	 */
	if (x != Scr->vdtPositionX || y != Scr->vdtPositionY) {
	    x = tmp_win->frame_x;
	    y = tmp_win->frame_y;
	    MoveDesktop(x, y);
	}
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	ScrollToQuadrant - scroll the virtual desktop to a the quadrant 
 *		that contains the client window.
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
ScrollToQuadrant(TwmWindow *tmp_win)
{
    register int x, y;

    if (!tmp_win->sticky) {
	x=tmp_win->frame_x;
	y=tmp_win->frame_y;
	/* 16 is the average cursor width */
	if ( x + tmp_win->frame_width < Scr->vdtPositionX + 16 ||
		x >  Scr->vdtPositionX + Scr->MyDisplayWidth - 16 ||
	     y + tmp_win->frame_height < Scr->vdtPositionY + 16 ||
		y >  Scr->vdtPositionY + Scr->MyDisplayHeight - 16 ){
	    x = (x / Scr->MyDisplayWidth) * Scr->MyDisplayWidth;
	    y = (y / Scr->MyDisplayHeight) * Scr->MyDisplayHeight;
	    MoveDesktop(x, y);
	}
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	PaintVirtualWindow - paint the window name in the virtual window
 *	
 *  Returned Value:
 *	None
 *
 ***********************************************************************
 */

void
PaintVirtualWindow(TwmWindow *tmp_win)
{
    if (Scr->ShowVirtualNames) {
	FBF(tmp_win->virtual.fore, tmp_win->virtual.back,
	    Scr->VirtualFont.font->fid);

	XClearArea(dpy, tmp_win->virtualWindow, 1, 1, 9999, Scr->VirtualFont.height, False);
	XDrawString (dpy, tmp_win->virtualWindow, Scr->NormalGC,
		     1, 1+Scr->VirtualFont.y, 
		     tmp_win->name, strlen(tmp_win->name));
    }
}


syntax highlighted by Code2HTML, v. 0.9.1