
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * 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 in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/lib/RCS/xtutil.c,v 2.2 92/10/19 16:55:45 ken Exp $
 *
 * $Log:	xtutil.c,v $
 * Revision 2.2  92/10/19  16:55:45  ken
 * Trivial change
 * 
 * Revision 1.1  92/10/19  16:37:11  ken
 * Initial revision
 * 
 */

#include <malloc.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>

#include "util.h"
#include "skip.h"
#include "xtutil.h"


/* A handy interface to XtGetValues and XtSetValues.
 * Call SetValue and GetValue up to XT_VALUE_MAX times,
 * pass NULL for widget until the last call.
 */
#define XT_VALUE_MAX	50

void GetValue(String resource, void *dest, Widget widget)
{
    static unsigned	count = 0;
    static Arg		arg[XT_VALUE_MAX];
    
    XtSetArg(arg[count], resource, dest);
    count++;
    
    if (widget) {
	XtGetValues(widget, arg, count);
	count = 0;
    } else 
	Verify(count < XT_VALUE_MAX, "Too many calls to GetValue");
}


void SetValue(String resource, XtArgVal val, Widget widget)
{
    static unsigned	count = 0;
    static Arg		arg[XT_VALUE_MAX];
    
    XtSetArg(arg[count], resource, val);
    count++;

    if (widget) {
	XtSetValues(widget, arg, count);
	count = 0;
    } else
	Verify(count < XT_VALUE_MAX, "Too many calls to SetValue");
}


void CenterWidget(Widget widget, int x, int y)
{
    /* Position the widget underneath the cursor, for handy clicking.
     * Stolen from xrn.
     */
    Dimension	w, h;
    
    GetValue(XtNwidth,  (void *) &w, (Widget) NULL);
    GetValue(XtNheight, (void *) &h, widget);
    
    x -= (int) w / 2;
    y -= (int) h / 2;
    
    /* Make sure widget is on the screen
     */
    if (x + (int) w > WidthOfScreen(XtScreen(widget)))
	x = WidthOfScreen(XtScreen(widget))  - (int) w;
    if (y + (int) h > HeightOfScreen(XtScreen(widget)))
	y = HeightOfScreen(XtScreen(widget)) - (int) h;
    
    if (x < 0)
	x = 0;
    if (y < 0)
	y = 0;
    
    SetValue(XtNx, (XtArgVal) x, NULL);
    SetValue(XtNy, (XtArgVal) y, widget);
}


void CenterWidgetOverCursor(Widget widget)
{
    /* Position a widget (probably a dialog) over the cursor,
     * for easy clicking.
     */
    Window		root, child;
    int			x, y, dummy;
    unsigned int	mask;
    
    (void) XQueryPointer(XtDisplay(widget), XtWindow(widget),
			 &root, &child,
			 &x, &y, &dummy, &dummy,
			 &mask);
    
    CenterWidget(widget, x, y);
}


/*ARGSUSED*/
void PositionDialogCB(Widget w, XtPointer closure, XtPointer callData)
{
    /* A handy widget callback interface to CenterWidgetOverCursor
     */
    XtRealizeWidget(w);
    CenterWidgetOverCursor(w);
}


Widget ShellOfWidget(Widget w)
{
    /* Walk up widget tree 'till a shell is reached.
     * This may be a dialog shell, not the top level.
     */
    Widget	shell = w;
    
    while (shell  &&  !XtIsShell(shell))
	shell = XtParent(shell);
    
    return shell;
}


Widget TopLevel(Widget w)
{
    /* Walk up the widget tree to the top.  The top level widget
     * has a NULL (i.e. no) parent.
     */
    Widget	top = w;
    Widget	parent;
    
    while (parent = XtParent(top))
	top = parent;
    
    return top;
}


/* Routines for handling visuals
 */

XVisualInfo *FindDeepestPseudoVisual(Display *display, Screen *screen)
{
    /* On some X servers (like the SGI) there exist visuals with
     * more colors than the default visual.  This routine
     * finds the deepest pseudocolor visual on the given screen.
     */
    int		i, n;
    XVisualInfo	*vi, vtmpl, *best;
    
    vtmpl.class = PseudoColor;
    vtmpl.screen = XScreenNumberOfScreen(screen);
    vi = XGetVisualInfo(display, VisualClassMask | VisualScreenMask, 
			&vtmpl, &n);
    if (n == 0)
	return NULL;
    
    best = vi;
    for (i = 1; i < n; i++)
	if (best->depth < vi[i].depth)
	    best = vi + i;

    return best;
}


XVisualInfo *FindRootVisual(Display *display, Screen *screen)
{
    /* Sometimes it's nice to have access to the XVisualInfo
     * information, even for the default visual.
     * This routine is meant to be symetric with FindDeepestPseudoVisual.
     */
    int		n;
    XVisualInfo	*vi, vtmpl;
    
    vtmpl.visualid = XVisualIDFromVisual(DefaultVisualOfScreen(screen));
    vi = XGetVisualInfo(display, VisualIDMask, &vtmpl, &n);

    if (n == 0)
	return NULL;
    
    return vi;
}


void SwallowCells(Display *display, Colormap cmap, 
		  Pixel *holdPix, int *holdNum)
{
    /* Allocate as many cells as possible below a pixel value,
     * There should be holdNum entries allocated in holdPix.
     * The final number of entries grabbed is returned in holdNum.
     */
    int		ncolor = *holdNum;
    int		holdOver;
    int		i;

    while (*holdNum)
	if (XAllocColorCells(display, cmap, 0, NULL, 0, holdPix, *holdNum)) {
	    holdOver = 0;
	    for (i = 0; i < *holdNum; ++i)
		if (holdPix[i] >= ncolor)
		    ++holdOver;
	    
	    if (holdOver == 0)
		break;
	    
	    XFreeColors(display, cmap, holdPix, *holdNum, 0L);
	    *holdNum -= holdOver;
	} else
	    *holdNum -= 1;
}


void CopyColormap(Display *display, Colormap srcmap, 
		  Colormap dstmap, int ncolor)
{
    /* Allocate and copy as many of the lower 'ncolor' cells as possible.
     */
    Pixel	*holdPix;
    XColor	*srcColor;
    XColor	*dstColor;
    int		holdNum = ncolor;
    int		i;
   
    holdPix = CallocType(Pixel, ncolor);
    MemCheck(holdPix);
    srcColor = CallocType(XColor, ncolor);
    MemCheck(srcColor);
    
    SwallowCells(display, dstmap, holdPix, &holdNum);
    
    dstColor = CallocType(XColor, holdNum);
    MemCheck(dstColor);

    /* Only copy over cells that we were able to grab.
     */
    for (i = 0; i < ncolor; ++i)
	srcColor[i].pixel = i;
    XQueryColors(display, srcmap, srcColor, ncolor);

    for (i = 0; i < holdNum; ++i)
	dstColor[i] = srcColor[holdPix[i]];

    XStoreColors(display, dstmap, dstColor, holdNum);

    free(holdPix);
    free(srcColor);
    free(dstColor);
}


void MirrorColormap(Display *display, Colormap srcmap, 
		    Colormap dstmap, int ncolor)
{
    /* Just copy the lower 'ncolor' cells.
     */
    XColor	*srcColor = CallocType(XColor, ncolor);
    int		i;

    MemCheck(srcColor);

    for (i = 0; i < ncolor; ++i)
	srcColor[i].pixel = i;

    XQueryColors(display, srcmap, srcColor, ncolor);
    XStoreColors(display, dstmap, srcColor, ncolor);

    free(srcColor);
}


void AllocBlack(Display *display, Screen *screen, Colormap cmap)
{
    /* Allocate black from the color map.
     */
    XColor	black;
    
    black.pixel = BlackPixelOfScreen(screen);
    black.red = 0;
    black.green = 0;
    black.blue = 0;
    black.flags = DoRed | DoGreen | DoBlue;

    XAllocColor(display, cmap, &black);
}


void QueryBackground(Widget w, XColor *bgc)
{
    /* Get the backgrounf RGB values for a widget.
     */
    Colormap	cmap;

    GetValue(XtNcolormap, (void *) &cmap, NULL);
    GetValue(XtNbackground, (void *) &bgc->pixel, w);
    
    (void) XQueryColor(XtDisplay(w), cmap, bgc);
}


void GrabUntilClick(Widget w, XEvent *ev)
{
    /* Stab at another application's window.
     * The window clicked on is returned in ev->window.
     */
    Display		*dpy = XtDisplay(w);
    Window		root = RootWindowOfScreen(XtScreen(w));

    static Cursor	xp_cursor = 0;
    
    if (xp_cursor == 0)
	xp_cursor = XCreateFontCursor(dpy, XC_crosshair);
    
    XGrabPointer(dpy, root, 0, ButtonPressMask|ButtonReleaseMask,
		 GrabModeSync, GrabModeAsync, None, 
		 xp_cursor, CurrentTime);
    do {
	XAllowEvents(dpy, SyncPointer, CurrentTime);
	XWindowEvent(dpy, root, ButtonPressMask|ButtonReleaseMask, ev);
    } while (ev->type != ButtonPress);
    
    XUngrabPointer(dpy, CurrentTime);
}


/*ARGSUSED*/
void ManageCB(Widget w, XtPointer closure, XtPointer callData)
{
    /* Handy for popup callbacks.
     */
    Widget	mw = (Widget) closure;
    
    if (!mw)
	mw = w;
    XtManageChild(mw);
}


/*ARGSUSED*/
void UnmanageCB(Widget w, XtPointer closure, XtPointer callData)
{
    /* Handy for popdown callbacks.
     */
    Widget	mw = (Widget) closure;
    
    if (!mw)
	mw = w;
    XtUnmanageChild(mw);
}


/*ARGSUSED*/
void SensitizeCB(Widget w, XtPointer closure, XtPointer callData)
{
    Widget	mw = (Widget) closure;
    
    if (!mw)
	mw = w;
    XtSetSensitive(mw, TRUE);
}


/*ARGSUSED*/
void DesensitizeCB(Widget w, XtPointer closure, XtPointer callData)
{
    Widget	mw = (Widget) closure;
    
    if (!mw)
	mw = w;
    XtSetSensitive(mw, FALSE);
}


/* The following routines, MakeChildren, BuildWidgetTree and GetWidget,
 * constitute a simple, table driven method of creating widget trees.
 * After creating a shell, call BuildWidgetTree with a table of widget
 * specifications.  BuildWidgetTree calls MakeChildren, which in turn
 * recurses to make children as needed.
 *
 * The flag byte in the widget table specifies whether this widget is
 * a parent, whether it is the last child of its parent, and whether
 * to keep the widget unmanaged.
 *
 * All tables passed to BuildWidgetTree are sorted into a master table
 * by the ascii widget name.  Thus it is possible to quickly (via
 * bsearch) look up a widget by name, assuming the interesting
 * widgets all have unique names (hint, hint).
 *
 * After calling BuildWidgetTree I usually call a routine named 
 * DressWidgetTree, which places callbacks on the widgets and 
 * does a few other things unavailable from standard app defaults,
 * like unmanaging help widgets in Motif dialogs.
 *
 * Advantages of this scheme:
 *   It's smaller and simpler than WCL.  No extra library needed.
 *   A widget can be refered to by name in both the app defaults file
 *      C code.  (A big win IMHO).
 *
 * Disadvantages:
 *   The widget tree is defined in C code (note that most everything
 *      else is still in the app defaults file).
 *   You have to be carefull with the parent/last-child flags in the
 *      widget table.
 */

typedef struct _KeyWidget {
    char	*key;
    Widget	widget;
} KeyWidget;

static SkipList	widgetList = NULL;
static Widget	lastBuilt;


static int CompareNames(void *v1, void *v2)
{
    return strcmp(((KeyWidget *)v1)->key, ((KeyWidget *)v2)->key);
}


/*ARGSUSED*/
void RemoveKeyCB(Widget w, XtPointer closure, XtPointer callData)
{
    KeyWidget	*kw;
    
    for (kw = SkipHead(widgetList); kw; kw = SkipNext(widgetList))
	if (kw->widget == w)
	    break;
    if (kw) {
	(void) SkipRemove(widgetList, kw);
	Free(kw->key);
	Free(kw);
    }
}


static void AddWidgetToList(Widget w, char *key)
{
    KeyWidget	*kw = MallocType(KeyWidget);
    
    MemCheck(kw);
    kw->key = NewString(key, 0);
    kw->widget = w;
    
    (void) SkipInsert(widgetList, kw);
    XtAddCallback(w, XtNdestroyCallback, RemoveKeyCB, NULL);
}


static unsigned MakeChildren(WidgetSpec *ws, Widget parent, unsigned i)
{
    /* Make widgets from location 'start' in the widget table 'ws'.
     * Recurse to create children of parent widgets.
     * Return the index of the next widget to create in the table.
     * Use the Motif style creation functions.
     */
    unsigned	moreSiblings = True;
    Widget	w;

    while (moreSiblings) {
	Verify(ws[i].name, "Bad Widget tree");
	moreSiblings = !(ws[i].flags & W_LAST_CHILD);

	w = ws[i].create(parent, ws[i].name, NULL, 0);
	AddWidgetToList(w, ws[i].name);
	lastBuilt = w;

	if (!(ws[i].flags & W_NO_MANAGE))
	    XtManageChild(w);

	if (ws[i].flags & W_PARENT)
	    i = MakeChildren(ws, w, i + 1);
	else
	    ++i;
    };
    
    return i;
}


void BuildWidgetTree(Widget parent, WidgetSpec *ws)
{
    /* Create the widgets from the spec table, 
     */
    if (!widgetList)
	widgetList = SkipNew(CompareNames, 0);
    (void) MakeChildren(ws, parent, 0);
}


Widget BuildOneWidget(Widget parent, char *name, 
		      Widget (*create)(Widget, String, ArgList, Cardinal),
		      int unmanaged)
{
    WidgetSpec	ws;
    
    ws.flags = unmanaged ? 0x011 : 0x010;
    ws.name = name;
    ws.create = create;
    BuildWidgetTree(parent, &ws);
    
    return lastBuilt;
}


Widget GetWidget(char *name)
{
    /* Find a widget by name from widget skip list.
     * !Abort! if not found.
     */
    KeyWidget	key, *kw;
    
    key.key = name;
    kw = SkipSearch(widgetList, &key);
    Verify(kw, "Widget not found!");

    return kw->widget;
}


Widget CheckWidget(char *name)
{
    /* Find a widget by name from widget skip list.
     * Return NULL if not found.
     */
    KeyWidget	key, *kw;

    key.key = name;
    kw = SkipSearch(widgetList, &key);

    if (!kw)
	return NULL;
    return kw->widget;
}


/* Put this Core dependent routine below the public routine.
 * This way lint won't complain about Widget being a different 
 * definition (since CoreP.h clarifies the Widget structure).
 */

#include <X11/IntrinsicP.h>
#include <X11/CoreP.h>
#include <X11/Xmu/Converters.h>

static XtConvertArgRec parentCvtArg[] = {
    {XtBaseOffset, (XtPointer) XtOffset(Widget, core.parent), sizeof(Widget)},
};

void AddWidgetConverter(XtAppContext app)
{
    XtAppAddConverter(app, XtRString, XtRWidget, XmuCvtStringToWidget,
		      parentCvtArg, XtNumber(parentCvtArg));
    XtAppAddConverter(app, XtRString, XtRWindow, XmuCvtStringToWidget,
		      parentCvtArg, XtNumber(parentCvtArg));
}
