/* coolwidget.c - routines for simple widgets. Widget setup and destruction
   Copyright (C) 1996-2000 Paul Sheer

   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., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */

#define COOL_WIDGET_C

#include <X11/Xatom.h>
#include "coolwidget.h"
#include "coollocal.h"

#include "mad.h"

extern struct look *look;

/* call this for fatal errors */
void CError (const char *fmt,...)
{
    va_list s;
    char *str;
    va_start (s, fmt);
    str = vsprintf_alloc (catstrs (" ", fmt, " ", 0), s);
    CFatalErrorDialog (20, 20, str);
    va_end (s);
    free (str);
}



/* an malloc with an error check */

#ifndef HAVE_MAD

#ifndef CMalloc
void *CMalloc (size_t size)
{
    void *p;
    if ((p = malloc (size + 8)) == NULL)
/* Not essential to translate */
	CError (_("Unable to allocate memory.\n"));
    return p;
}
#endif

#ifdef DEBUG
#ifndef HAVE_MAD
void *CDebugMalloc (size_t x, int line, const char *file)
{
    void *p;
    if ((p = malloc (x)) == NULL)
/* Not essential to translate */
	CError (_("Unable to allocate memory: line %d, file %s.\n"), line, file);
    return p;
}
#endif
#endif

#endif


int allocate_color (char *color)
{
    if (!color)
	return NO_COLOR;
    if (*color >= '0' && *color <= '9') {
	return atoi (color);
    } else {
	int i;
	XColor c;
	if (!color)
	    return NO_COLOR;
	if (!XParseColor (CDisplay, CColormap, color, &c))
	    return NO_COLOR;
	if (!XAllocColor (CDisplay, CColormap, &c))
	    return NO_COLOR;
	for (i = 0; i < color_last_pixel; i++)
	    if (color_palette (i) == c.pixel)
		return i;
	color_palette (color_last_pixel) = c.pixel;
	return color_last_pixel++;
    }
}


struct cursor_state {
    int x, y, h, w;
    Window window;
    GC gc;
    XFontSet font_set;
    XFontStruct *font_struct;
    int state;
    int type;
    wchar_t chr;
    unsigned long bg, fg;
    int style;
    int font_x, font_y;
};

struct cursor_state CursorState =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

void render_cursor (struct cursor_state c);

void set_cursor_position (Window win, int x, int y, int w, int h, int type, wchar_t chr, unsigned long bg, unsigned long fg, int style)
{
    if (win == CGetFocus ()) {
	CursorState.x = x;
	CursorState.y = y;
	CursorState.h = h;
	CursorState.w = w;
	CursorState.window = win;
	CursorState.gc = CGC;
	CursorState.font_set = current_font->font_set;
	CursorState.font_struct = current_font->font_struct;
	CursorState.type = type;
	CursorState.chr = chr;
	CursorState.style = style;
	CursorState.bg = bg;
	CursorState.fg = fg;
	CursorState.font_x = FONT_OFFSET_X;
	CursorState.font_y = FONT_OFFSET_Y;
	render_cursor (CursorState);
    } else {
	if (!(win | h | w))
	    CursorState.window = 0;
    }
}

int option_never_raise_wm_windows = 0;
int option_xor_cursor = 0;
int option_flashing_cursor = 1;
unsigned long option_cursor_color;

void render_cursor (struct cursor_state c)
{
    if (!CursorState.window)
	return;
    if (c.type == CURSOR_TYPE_EDITOR) {
	if (CursorState.window != CGetFocus ())
	    return;
	CPushFont ("editor", 0);
	if (!option_xor_cursor) {
	    if (c.state || !option_flashing_cursor)
		CSetColor (option_cursor_color);
	    else
		CSetColor (c.bg);
	    if (c.style & MOD_REVERSE) {
		CLine (c.window, c.x + c.w - 1, c.y + FONT_OVERHEAD, c.x + c.w - 1, c.y + c.h - 1);
		CLine (c.window, c.x + c.w - 2, c.y + FONT_OVERHEAD, c.x + c.w - 2, c.y + c.h - 1);
	    } else {
		CLine (c.window, c.x, c.y + FONT_OVERHEAD, c.x, c.y + c.h - 1);
		CLine (c.window, c.x + 1, c.y + FONT_OVERHEAD, c.x + 1, c.y + c.h - 1);
	    }
	    CLine (c.window, c.x, c.y + FONT_OVERHEAD, c.x + c.w - 1, c.y + FONT_OVERHEAD);
	    CLine (c.window, c.x, c.y + FONT_OVERHEAD + 1, c.x + c.w - 1, c.y + FONT_OVERHEAD + 1);
	}
	if (!c.state && option_flashing_cursor) {
	    XSetBackground (CDisplay, c.gc, c.bg);
	    XSetForeground (CDisplay, c.gc, c.fg);
	    CImageTextWC (c.window, c.x + c.font_x, c.y + c.font_y, 0, &(c.chr), 1);
	} else {
	    if (option_xor_cursor) {
		XSetBackground (CDisplay, c.gc, c.fg);
		XSetForeground (CDisplay, c.gc, c.bg);
		CImageTextWC (c.window, c.x + c.font_x, c.y + c.font_y, 0, &(c.chr), 1);
	    }
	}
	CPopFont ();
    } else {
	if (CursorState.window != CGetFocus ()) {
	    CSetColor (COLOR_FLAT);
	    CLine (c.window, c.x, c.y, c.x, c.y + c.h - 6);
	} else {
	    render_bevel (c.window, c.x - 1, c.y - 1, c.x, c.y + c.h - 5, 1, CursorState.state ? 0 : 1);
	}
    }
}


void CSetCursorColor (unsigned long c)
{
    option_cursor_color = c;
}

/* this is called from CNextEvent if an alarm event comes */
void toggle_cursor (void)
{
    CursorState.state = 1 - CursorState.state;
    render_cursor (CursorState);
}

void set_cursor_visible (void)
{
    CursorState.state = 1;
    render_cursor (CursorState);
}

void set_cursor_invisible (void)
{
    CursorState.state = 0;
    render_cursor (CursorState);
}



/*
   These three routines are not much slower than doing the same
   thing with integers as identifiers instead of strings.
   It returns the index in the global array of widgets of
   the widget named ident. Returns 0 if not found.
 */
static int find_ident (const char *ident)
{
    int i = last_widget + 1;
    u_32bit_t p;

    if (!ident)
	return 0;
    if (!ident[0])
	return 0;
    memcpy (&p, ident, sizeof (u_32bit_t));	/* we need four byte alignment for some machines */

    if (!ident[1])
	goto word_search;
    if (ident[2]) {		/* can compare first four bytes at once */
	while (--i)
	    if (CIndex (i))
		if (*((u_32bit_t *) CIndex (i)->ident) == p)
		    if (!strcmp (CIndex (i)->ident, ident))
			return i;
	return 0;
    } else {
	word s;
      word_search:
	s = *((word *) (&p));
	while (--i)
	    if (CIndex (i))
		if (*((word *) CIndex (i)->ident) == s)
		    if (!strcmp (CIndex (i)->ident, ident))
			return i;

    }
    return 0;
}

CWidget *CIdent (const char *ident)
{
    return CIndex (find_ident (ident));
}

CWidget *CWidgetOfWindow (Window win)
{
    return CIndex (widget_of_window (win));
}

int CSystem (const char *string)
{
    int r;
    CDisableAlarm ();
    r = system (string);
    CEnableAlarm ();
    return r;
}

extern int (*global_alarm_callback[33]) (CWidget *, XEvent *, CEvent *);

void CAddCallback (const char *ident, int (*callback) (CWidget *, XEvent *, CEvent *))
{
    CWidget *w = CIdent (ident);
    if (w)
	w->callback = callback;
    else {
	if (!strcmp (ident, "AlarmCallback"))
	    global_alarm_callback[0] = callback;
	if (!strncmp (ident, "AlarmCallback", 13))
	    global_alarm_callback[atoi (ident + 13) + 1] = callback;
    }
}

void CAddBeforeCallback (const char *ident, int (*callback) (CWidget *, XEvent *, CEvent *))
{
    CWidget *w = CIdent (ident);
    if (w)
	w->callback_before = callback;
}

#ifdef DEBUG_DOUBLE
/* checks the magic numbers */
int widget_check_magic ()
{
    int i = 0;

    while (last_widget > i++)
	if (CIndex (i) != NULL)
	    if (CIndex (i)->magic_begin != WIDGET_MAGIC_BEGIN || CIndex (i)->magic_end != WIDGET_MAGIC_END)
/* NLS ? */
		CError ("Cool widget internal error - magic number overwritten overwritten.\n");
    return 0;
}
#endif

/* sends a full expose event to the widget */
void CExpose (const char *ident)
{
    CWidget *w = CIdent (ident);
    if (w)
	CSendExpose (w->winid, 0, 0, w->width, w->height);
}

/* Returns the widgets window or 0 if not found */
Window CWindowOfWidget (const char *ident)
{
    CWidget *w = CIdent (ident);
    if (w)
	return w->winid;
    else
	return 0;
}

/* send an expose event to the internel queue */
void CExposeWindowArea (Window win, int count, int x, int y, int w, int h)
{
    if (x < 0) {
	w = x + w;
	x = 0;
    }
    if (y < 0) {
	h = y + h;
	y = 0;
    }
    if (w <= 0 || h <= 0)
	return;

    CSendExpose (win, x, y, w, h);
}


/* Returns the first NULL list entry. Exits if list full. */
CWidget **find_empty_widget_entry ()
{
    int i = 0;

/* widget can be added to an empty point in the list (created from an
   undraw command, or to the end of the list. */
    while (last_widget > i++) {
	if (CIndex (i) == NULL)
	    break;
    }

    if (i == MAX_NUMBER_OF_WIDGETS - 2)
/* NLS ? */
	CError ("No more space in widget list\nIncrease MAX_NUMBER_OF_WIDGETS in coolwidget.h\n");

    if (i == last_widget)
	last_widget++;		/* increase list length if an entry was added to the end */

    return &(CIndex (i));
}


/* Fills in the widget structure. */
CWidget *allocate_widget (Window newwin, const char *ident, Window parent, int x, int y,
			  int width, int height, int kindofwidget)
{
    CWidget *w = CMalloc (sizeof (CWidget));
    memset (w, 0, sizeof (CWidget));	/*: important, 'cos free's check if NULL before freeing many parems */

    w->magic_begin = WIDGET_MAGIC_BEGIN;
    w->winid = newwin;
    w->parentid = parent;
    w->width = width;
    w->height = height;
    w->x = x;
    w->y = y;
    strncpy (w->ident, ident, 32);

    w->kind = kindofwidget;
    w->magic_end = WIDGET_MAGIC_END;
    return w;
}

static int override_redirect = 0;

void CSetOverrideRedirect (void)
{
    override_redirect = 1;
}

void CClearOverrideRedirect (void)
{
    override_redirect = 0;
}


/*
   Sets up the widget's window and calls allocate_widget()
   to allocate space and set up the data structures.
   What is set up here is common to all widgets, so
   it will always be the first routine called by a CDraw...()
   function.
 */
CWidget *CSetupWidget (const char *identifier, Window parent, int x, int y,
	    int width, int height, int kindofwidget, unsigned long input,
		       unsigned long bgcolor, int takes_focus)
{
    XSetWindowAttributes xswa;
    Window newwin;
    CWidget **w;

/* NLS ? */
    if (CIdent (identifier) && kindofwidget == C_BUTTON_WIDGET)
/* Not essential to translate */
	CError (_ ("Trying to create a button with the same identifier as an existing widget.\n"));

    xswa.colormap = CColormap;
    xswa.bit_gravity = NorthWestGravity;
    xswa.background_pixel = bgcolor;
    switch (kindofwidget) {
    case C_MENU_WIDGET:
    case C_TOOLHINT_WIDGET:
    case C_ICON_WIDGET:
	xswa.override_redirect = 1;
	break;
    default:
	xswa.override_redirect = override_redirect;
	break;
    }

    newwin = XCreateWindow (CDisplay, parent, x, y, width, height, 0,
			    CDepth, InputOutput, CVisual,
	    CWOverrideRedirect | CWColormap | CWBackPixel | CWBitGravity,
			    &xswa);

    w = find_empty_widget_entry ();	/* find first unused list entry in list of widgets */
    *w = allocate_widget (newwin, identifier, parent, x, y,
			  width, height, kindofwidget);

    (*w)->mainid = CFindParentMainWindow (parent);
    (*w)->eh = default_event_handler (kindofwidget);
    (*w)->takes_focus = takes_focus;

    XSelectInput (CDisplay, newwin, input);
    switch ((*w)->kind) {
    case C_WINDOW_WIDGET:	/* window widgets must only be mapped 
				   once all there children are drawn */
#ifdef USE_XIM
	if (CIM) {
	    create_input_context (*w, get_input_style ());
	    set_status_position (*w);
	}
#endif
	break;
    default:
	XMapWindow (CDisplay, newwin);	/* shows the window */
	XFlush (CDisplay);
	break;
    }
    return (*w);
}

extern Atom ATOM_WM_PROTOCOLS, ATOM_WM_DELETE_WINDOW, ATOM_WM_TAKE_FOCUS, ATOM_WM_NAME, ATOM_WM_NORMAL_HINTS;

void CSetWindowSizeHints (CWidget * wdt, int min_w, int min_h, int max_w, int max_h)
{
    XSizeHints size_hints;
    long d;
    size_hints.min_width = min_w;
    size_hints.min_height = min_h;
    size_hints.max_width = max_w;
    size_hints.max_height = max_h;
    size_hints.width_inc = FONT_MEAN_WIDTH;
    size_hints.height_inc = FONT_PIX_PER_LINE;
    size_hints.base_width = min_w;
    size_hints.base_height = min_h;
    size_hints.flags = PBaseSize | PResizeInc | PMaxSize | PMinSize;

    if (wdt->options & WINDOW_USER_POSITION) {
	size_hints.x = wdt->x;
	size_hints.y = wdt->y;
	size_hints.flags |= USPosition | PPosition;
    }
    if (wdt->options & WINDOW_USER_SIZE) {
	size_hints.width = wdt->width;
	size_hints.height = wdt->height;
	size_hints.flags |= USSize | PSize;
    }
/* used as check if hints are not set when CMap is called */
    wdt->options |= WINDOW_SIZE_HINTS_SET;

    XSetWMNormalHints (CDisplay, wdt->winid, &size_hints);
    XSync (CDisplay, 0);
    XGetWMNormalHints (CDisplay, wdt->winid, &size_hints, &d);
    XSync (CDisplay, 0);
}

void CSetWindowResizable (const char *ident, int min_width, int min_height, int max_width, int max_height)
{
    Window w;
    int width, height;
    CWidget *wdt;

    wdt = CIdent (ident);
    w = wdt->winid;
    width = wdt->width;
    height = wdt->height;

    min_width = width - ((width - min_width) / FONT_MEAN_WIDTH) * FONT_MEAN_WIDTH;
    min_height = height - ((height - min_height) / FONT_PIX_PER_LINE) * FONT_PIX_PER_LINE;
    max_width = width - ((width - max_width) / FONT_MEAN_WIDTH) * FONT_MEAN_WIDTH;
    max_height = height - ((height - max_height) / FONT_PIX_PER_LINE) * FONT_PIX_PER_LINE;

    if (wdt->parentid == CRoot) {
	/* set the window manager to manage resizing */
	XWMHints wm_hints;
	XClassHint class_hints;

	class_hints.res_name = CAppName;
	class_hints.res_class = CAppName;
	wm_hints.flags = (InputHint | StateHint);
	wm_hints.input = True;
	wm_hints.initial_state = NormalState;

	XSetWMProperties (CDisplay, w, 0, 0, 0, 0, 0, &wm_hints, &class_hints);
	CSetWindowSizeHints (wdt, min_width, min_height, max_width, max_height);
    } else {
/* else, we must manage our own resizing. */
/* the member names do not mean what they are named here */
	XSelectInput (CDisplay, w, INPUT_MOTION | StructureNotifyMask);
	wdt->position |= WINDOW_RESIZABLE;
	wdt->mark1 = min_width;
	wdt->mark2 = min_height;
	wdt->firstcolumn = width;
	wdt->firstline = height;
/* we are not going to specify a maximum */
	wdt->numlines = FONT_PIX_PER_LINE;	/* resizing granularity x */
	wdt->textlength = FONT_MEAN_WIDTH;	/* resizing granularity y */
    }
}

extern char *init_geometry;

Window CDrawHeadedDialog (const char *identifier, Window parent, int x, int y, const char *label)
{
    Window win;
    CWidget *wdt;

    if ((parent == CRoot || !parent) && !override_redirect) {
	int width, height, bitmask;
	bitmask = 0;
	x = 0;
	y = 0;
	width = 10;
	height = 10;
	parent = CRoot;
	if (!CFirstWindow) {
	    if (init_geometry)
		bitmask = XParseGeometry (init_geometry, &x, &y, (unsigned int *) &width, (unsigned int *) &height);
	}
	win = (wdt = CSetupWidget (identifier, CRoot, x, y,
	width, height, C_WINDOW_WIDGET, INPUT_MOTION | StructureNotifyMask
			       | FocusChangeMask, COLOR_FLAT, 0))->winid;
	if (!CFirstWindow) {	/* create the GC the first time round and
				   CFirstWindow. when the window is closed,
				   the app gets a QuitApplication event */
	    CFirstWindow = win;

/* these options tell CSetWindowSizeHints() to give those hints to the WM: */
	    if (bitmask & (XValue | YValue))
		wdt->options |= WINDOW_USER_POSITION;
	    if (bitmask & (WidthValue | HeightValue))
		wdt->options |= WINDOW_USER_SIZE;
	}
	wdt->label = (char *) strdup (label);
	XSetIconName (CDisplay, win, wdt->label);
	XStoreName (CDisplay, win, wdt->label);
	{
	    Atom a[2];
	    a[0] = ATOM_WM_DELETE_WINDOW;
#if 0
	    a[1] = ATOM_WM_TAKE_FOCUS;
	    XChangeProperty (CDisplay, win, ATOM_WM_PROTOCOLS, XA_ATOM, 32,
		PropModeReplace, (unsigned char *) a, 2);
#else
	    XChangeProperty (CDisplay, win, ATOM_WM_PROTOCOLS, XA_ATOM, 32,
		PropModeReplace, (unsigned char *) a, 1);
#endif
	}
	reset_hint_pos (WIDGET_SPACING + 2, WIDGET_SPACING + 2);
	wdt->position |= WINDOW_UNMOVEABLE;
	wdt->options |= WINDOW_NO_BORDER;
    } else {
	int w, h;
	CTextSize (&w, &h, label);
	win = CDrawDialog (identifier, parent, x, y);
	(CDrawText (catstrs (identifier, ".header", 0), win, WIDGET_SPACING, WIDGET_SPACING + 2, label))->position |= POSITION_CENTRE;
	CGetHintPos (&x, &y);
#ifndef NEXT_LOOK
	(CDrawBar (win, WIDGET_SPACING, y, 10))->position |= POSITION_FILL;
	CGetHintPos (&x, &y);
#endif
	reset_hint_pos (WIDGET_SPACING + 2, y);
    }
    return win;
}

Window CDrawDialog (const char *identifier, Window parent, int x, int y)
{
    Window w;
    CWidget *wdt;
    w = (wdt = CSetupWidget (identifier, parent, x, y,
	     2, 2, C_WINDOW_WIDGET, INPUT_MOTION, COLOR_FLAT, 0))->winid;
    reset_hint_pos (WIDGET_SPACING + 2, WIDGET_SPACING + 2);
    return w;
}

/* returns the actual window that is a child  of the root window */
/* this is not the same as a main window, since the WM places each */
/* main window inside a cosmetic window */
Window CGetWMWindow (Window win)
{
    Window root, parent, *children;
    unsigned int nchildren;

    for (;;) {
	if (!XQueryTree (CDisplay, win, &root, &parent, &children, &nchildren))
	    break;
	if (parent == CRoot)
	    return win;
	if (children)
	    XFree ((char *) children);
	win = parent;
    }
    return 0;
}

/* I tested this procedure with kwm, fvwm95, fvwm, twm, olwm, without problems */
void CRaiseWMWindow (char *ident)
{
    Window wm_win;
    CWidget *w;
    XWindowChanges c;
    XEvent ev;

    w = CIdent (ident);
    if (!w)
	return;
    wm_win = CGetWMWindow (w->mainid);
    if (!wm_win)
	return;
    c.stack_mode = Above;
    XConfigureWindow (CDisplay, wm_win, CWStackMode, &c);
    XFlush (CDisplay);
}

void CSetBackgroundPixmap (const char *ident, const char *data[], int w, int h, char start_char)
{
    XSetWindowAttributes xswa;
    CWidget *wdt;
    wdt = CIdent (ident);
    if (wdt->pixmap)
	XFreePixmap (CDisplay, wdt->pixmap);
    xswa.background_pixmap = wdt->pixmap = CCreatePixmap (data, w, h, start_char);
    if (xswa.background_pixmap)
	XChangeWindowAttributes (CDisplay, wdt->winid, CWBackPixmap, &xswa);
}

#define MAX_KEYS_IN_DIALOG 64

int find_letter_at_word_start (unsigned char *label, unsigned char *used_keys, int n)
{
    int c, j;
    for (j = 0; label[j]; j++) {	/* check for letters with an & in front of them */
	c = my_lower_case (label[j + 1]);
	if (!c)
	    break;
	if (label[j] == '&')
	    if (!memchr (used_keys, c, n))
		return label[j + 1];
    }
    c = my_lower_case (label[0]);
    if (c >= 'a' && c <= 'z')
	if (!memchr (used_keys, c, n))	/* check if first letter has not already been used */
	    return label[0];
    for (j = 1; label[j]; j++) {	/* check for letters at start of words that have not already been used */
	c = my_lower_case (label[j]);
	if (label[j - 1] == ' ' && c >= 'a' && c <= 'z')
	    if (!memchr (used_keys, c, n))
		return label[j];
    }
    for (j = 1; label[j]; j++) {	/* check for any letters that have not already been used */
	c = my_lower_case (label[j]);
	if (c >= 'a' && c <= 'z')
	    if (!memchr (used_keys, c, n))
		return label[j];
    }
    return 0;
}

int find_hotkey (CWidget * w)
{
    unsigned char used_keys[MAX_KEYS_IN_DIALOG + 3];
    const char *label;
    int n = 0;
    CWidget *p = w;
    label = w->label ? w->label : w->text;	/* text for text-widgets which don't have a label */
    if (!label)
	return 0;
    if (!*label)
	return 0;
    do {
	w = CNextFocus (w);
	if (!w || n == MAX_KEYS_IN_DIALOG)
	    return 0;
	if (w->hotkey < 256)
	    used_keys[n++] = my_lower_case (w->hotkey);
    } while ((unsigned long) w != (unsigned long) p);
    if (!n)
	return 0;
    return find_letter_at_word_start ((unsigned char *) label, used_keys, n);
}


CWidget *CDrawButton (const char *identifier, Window parent, int x, int y,
		      int width, int height, const char *label)
{
    CWidget *wdt;
    int w, h;
    CPushFont ("widget", 0);
    if (width == AUTO_WIDTH || height == AUTO_HEIGHT)
	CTextSize (&w, &h, label);
    if (width == AUTO_WIDTH)
	width = w + 4 + BUTTON_RELIEF * 2;
    if (height == AUTO_HEIGHT) {
	height = h + 4 + BUTTON_RELIEF * 2;
#ifdef NEXT_LOOK
	height++ ;
#endif	
    }	
    wdt = CSetupWidget (identifier, parent, x, y,
	    width, height, C_BUTTON_WIDGET, INPUT_BUTTON, COLOR_FLAT, 1);
    if (label)
	wdt->label = (char *) strdup (label);
    wdt->hotkey = find_hotkey (wdt);
    wdt->render = render_button;
    wdt->options |= WIDGET_TAKES_FOCUS_RING | WIDGET_HOTKEY_ACTIVATES;
    set_hint_pos (x + width + WIDGET_SPACING, y + height + WIDGET_SPACING);
    CPopFont ();
    return wdt;
}

CWidget *CDrawProgress (const char *identifier, Window parent, int x, int y,
			int width, int height, int p)
{
    CWidget *w;
    if ((w = CIdent (identifier))) {
	w->cursor = p;
	CSetWidgetPosition (identifier, x, y);
	CSetWidgetSize (identifier, width, height);
	CExpose (identifier);
    } else {
	w = CSetupWidget (identifier, parent, x, y,
	       width, height, C_PROGRESS_WIDGET, INPUT_EXPOSE, COLOR_FLAT, 0);
	w->cursor = p;
	set_hint_pos (x + width + WIDGET_SPACING, y + height + WIDGET_SPACING);
    }
    return w;
}

CWidget *CDrawBar (Window parent, int x, int y, int w)
{
    CWidget *wdt;
    wdt = CSetupWidget ("hbar", parent, x, y,
			 w, 3, C_BAR_WIDGET, INPUT_EXPOSE, COLOR_FLAT, 0);
    set_hint_pos (x + w + WIDGET_SPACING, y + 3 + WIDGET_SPACING);
    return wdt;
}

/* returns the text size. The result is one descent greater than the actual size */
void CTextSize (int *w, int *h, const char *str)
{
    char *p, *q = (char *) str;
    int w1, h1;
    if (!w)
	w = &w1;
    if (!h)
	h = &h1;
    *w = *h = 0;
    for (;;) {
	if (!(p = strchr (q, '\n')))
	    p = q + strlen (q);
	*h += FONT_PIX_PER_LINE;
	*w = max (CImageTextWidth (q, (unsigned long) p - (unsigned long) q), *w);
	if (!*p)
	    break;
	q = p + 1;
    }
}


CWidget *CDrawText (const char *identifier, Window parent, int x, int y, const char *fmt,...)
{
    va_list pa;
    char *str;
    int w, h;
    CWidget *wdt;

    va_start (pa, fmt);
    str = vsprintf_alloc (fmt, pa);
    va_end (pa);

    CPushFont ("widget", 0);
    CTextSize (&w, &h, str);
    w += TEXT_RELIEF * 2 + 2;
    h += TEXT_RELIEF * 2 + 2;
    wdt = CSetupWidget (identifier, parent, x, y,
			w, h, C_TEXT_WIDGET, INPUT_EXPOSE, COLOR_FLAT, 0);
    wdt->text = (char *) strdup (str);
    free (str);
    set_hint_pos (x + w + WIDGET_SPACING, y + h + WIDGET_SPACING);
    CPopFont ();
    return wdt;
}

CWidget *CDrawStatus (const char *identifier, Window parent, int x, int y, int w, char *str)
{
    CWidget *wdt;
    int h;
    h = FONT_PIX_PER_LINE + TEXT_RELIEF * 2 + 2;
    wdt = CSetupWidget (identifier, parent, x, y,
		     w, h, C_STATUS_WIDGET, INPUT_EXPOSE, COLOR_FLAT, 0);
    wdt->text = (char *) strdup (str);
    set_hint_pos (x + w + WIDGET_SPACING, y + h + WIDGET_SPACING);
    return wdt;
}

void render_text (CWidget * w);

CWidget *CRedrawText (const char *identifier, const char *fmt,...)
{
    va_list pa;
    char *str;
    CWidget *wdt;
    int w, h;

    wdt = CIdent (identifier);
    if (!wdt)
	return 0;

    va_start (pa, fmt);
    str = vsprintf_alloc (fmt, pa);
    va_end (pa);

    free (wdt->text);
    wdt->text = (char *) strdup (str);

    CTextSize (&w, &h, str);
    w += TEXT_RELIEF * 2 + 2;
    h += TEXT_RELIEF * 2 + 2;

    CSetWidgetSize (identifier, w, h);
    render_text (wdt);
    free (str);
    return wdt;
}

void focus_stack_remove_window (Window w);
void selection_clear (void);

/*
   Unmaps and destroys widget and frees memory.
   Only for a widget that has no children.
 */
int free_single_widget (int i)
{
    if (i && CIndex (i)) {
	if (CIndex (i)->winid) {
	    if (CIndex (i)->options & WIDGET_TAKES_SELECTION)
		if (CIndex (i)->winid == XGetSelectionOwner (CDisplay, XA_PRIMARY))
		    XSetSelectionOwner (CDisplay, XA_PRIMARY, CFirstWindow, CurrentTime);
	    if (CursorState.window == CIndex (i)->winid)
		set_cursor_position (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	    XUnmapWindow (CDisplay, CIndex (i)->winid);
	    XDestroyWindow (CDisplay, CIndex (i)->winid);
	    if (CFirstWindow == CIndex (i)->winid)
		CFirstWindow = 0;
	    focus_stack_remove_window (CIndex (i)->winid);	/* removes the window from the focus history stack */
	}
	if (CIndex (i)->label)
	    free (CIndex (i)->label);
	if (CIndex (i)->toolhint)
	    free (CIndex (i)->toolhint);
	if (CIndex (i)->headings)
	    free (CIndex (i)->headings);
	if (CIndex (i)->gl_graphicscontext) {
	    free (CIndex (i)->gl_graphicscontext);
	    CIndex (i)->gl_graphicscontext = 0;
	}
	if (CIndex (i)->ximage) {
	    if ((long) CIndex (i)->ximage->data == (long) CIndex (i)->graphic)
		CIndex (i)->graphic = NULL;
	    if (CIndex (i)->ximage->data) {
		free (CIndex (i)->ximage->data);
		CIndex (i)->ximage->data = 0;
	    }
	    XDestroyImage (CIndex (i)->ximage);
	}
	if (CIndex (i)->pixmap) {
	    XFreePixmap (CDisplay, CIndex (i)->pixmap);
	    CIndex (i)->pixmap = 0;
	}
	if (CIndex (i)->pixmap_mask) {
	    XFreePixmap (CDisplay, CIndex (i)->pixmap_mask);
	    CIndex (i)->pixmap_mask = 0;
	}
	if (CIndex (i)->graphic)
	    free (CIndex (i)->graphic);
	if (CIndex (i)->tab)
	    free (CIndex (i)->tab);
	if (CIndex (i)->destroy)
	    (*(CIndex (i)->destroy)) (CIndex (i));
	if (CIndex (i)->text)
	    free (CIndex (i)->text);	/* for input history, this must come 
					   after the destroy, so that the text can be added to the input 
					   history by the destroy function before the text is free'd */
	if (CIndex (i)->funcs)
	    free (CIndex (i)->funcs);
	if (CIndex (i)->free_user)
	    (*(CIndex (i)->free_user)) (CIndex (i)->user);
	else if (CIndex (i)->user && (CIndex (i)->options & WIDGET_FREE_USER_ON_DESTROY))
	    free (CIndex (i)->user);
	free (CIndex (i));
	CIndex (i) = NULL;
	while (!CIndex (last_widget - 1) && last_widget > 1)
	    last_widget--;
	return 1;
    } else
	return 0;
}

/*searches for the first widget in the list that has win as its parent
   and returns index */
int find_first_child_of (Window win)
{
    int i = 0;
    while (last_widget > i++)
	if (CIndex (i) != NULL)
	    if (CIndex (i)->parentid == win)
		return i;
    return 0;
}

int find_last_child_of (Window win)
{
    int i = last_widget;
    while (--i > 0)
	if (CIndex (i) != NULL)
	    if (CIndex (i)->parentid == win)
		return i;
    return 0;
}

/* int for_all_widgets (int (callback *) (CWidget *, void *, void *), void *data1, void *data2) */
long for_all_widgets (void *call_back, void *data1, void *data2)
{
    long (*callback) (CWidget *, void *, void *) = (long (*)(CWidget *, void *, void *)) call_back;
    int i = last_widget;
    while (--i > 0)
	if (CIndex (i) != NULL)
	    if ((*callback) (CIndex (i), data1, data2))
		return 1;
    return 0;
}

int widget_of_window (Window win)
{
    int i = 0;
    while (last_widget > i++)
	if (CIndex (i) != NULL)
	    if (CIndex (i)->winid == win)
		return i;
    return 0;
}

int find_next_child_of (Window win, Window child)
{
    int i = widget_of_window (child);
    if (i)
	while (last_widget > i++)
	    if (CIndex (i) != NULL)
		if (CIndex (i)->parentid == win)
		    return i;
    return 0;
}

int find_previous_child_of (Window win, Window child)
{
    int i = widget_of_window (child);
    if (i)
	while (--i > 0)
	    if (CIndex (i) != NULL)
		if (CIndex (i)->parentid == win)
		    return i;
    return 0;
}

CWidget *CDialogOfWindow (Window window)
{
    for (;;) {
	CWidget *w;
	w = CWidgetOfWindow (window);
	if (!w)
	    break;
	if (w->kind == C_WINDOW_WIDGET)
	    return w;
	window = w->parentid;
    }
    return 0;
}

Window CFindParentMainWindow (Window parent)
{
    int i;
    if (parent == CRoot)
	return 0;
    if (!(i = widget_of_window (parent)))
	return 0;
    if (!CIndex (i)->mainid)
	return CIndex (i)->winid;
    return CIndex (i)->mainid;
}

/*recursively destroys a widget and all its descendants */
static void recursive_destroy_widgets (int i)
{
    int j;
    while ((j = find_first_child_of (CIndex (i)->winid)))
	recursive_destroy_widgets (j);
    free_single_widget (i);
}

void CFocusLast (void);

/*returns 1 on error --- not found. Destroys a widget by name and all its
   descendents */
int CDestroyWidget (const char *identifier)
{
    int i = find_ident (identifier);

    if (i) {
	recursive_destroy_widgets (i);
	CFocusLast ();
	return 0;
    } else
	return 1;
}


void CDestroyAll ()
{
    int j;
    while ((j = find_first_child_of (CRoot)))
	recursive_destroy_widgets (j);
}

void free_last_query_buttons (void);
void edit_replace_cmd (WEdit * edit, int again);
void free_selections (void);
void remove_all_watch (void);

void CShutdown (void)
{
    remove_all_watch ();
    CDestroyAll ();
    free (home_dir);
    free (temp_dir);
    home_dir = 0;
    temp_dir = 0;
    free_last_query_buttons ();
    edit_replace_cmd (0, 0);
    edit_search_cmd (0, 0);
    free_selections ();
    mouse_shut ();
    CFreeAllFonts ();
    XCloseDisplay (CDisplay);
}

void drawstring_xy (Window win, int x, int y, const char *text)
{
    if (!text)
	return;
    if (!*text)
	return;
    CImageString (win, FONT_OFFSET_X + x, FONT_OFFSET_Y + y, text);
}


char *whereis_hotchar (const char *labl, int hotkey)
{
    unsigned char *label = (unsigned char *) labl;
    int i;
    if (hotkey <= ' ' || hotkey > 255)
	return 0;
    if (*label == hotkey)
	return (char *) label;
    for (i = 1; label[i]; i++)
	if (label[i - 1] == ' ' && label[i] == hotkey)
	    return (char *) label + i;
    return (char *) strchr ((char *) label, hotkey);
}

void underline_hotkey (Window win, int x, int y, const char *text, int hotkey)
{
    char *p;
    if (hotkey <= ' ' || hotkey > 255)
	return;
    if (!(p = whereis_hotchar (text, hotkey)))
	return;
    x += CImageTextWidth (text, (unsigned long) p - (unsigned long) text);
    y += FONT_BASE_LINE + FONT_PER_CHAR_DESCENT(hotkey) + 1;
    (*look->draw_hotkey_understroke) (win, x, y, hotkey);
}

void drawstring_xy_hotkey (Window win, int x, int y, const char *text, int hotkey)
{
    drawstring_xy (win, x, y, text);
    underline_hotkey (win, x, y, text, hotkey);
}

void render_button (CWidget * wdt)
{
    (*look->render_button) (wdt);
}

void render_bar (CWidget * wdt)
{
    (*look->render_bar) (wdt);
}

#define FB (TEXT_RELIEF + 1)
#define FS (TEXT_RELIEF + 1)

/* this is a zero flicker routine */
void render_status (CWidget * wdt, int expose)
{
    static Window lastwin = 0;
    static char lasttext[1024] = "";
    Window win = CWindowOf (wdt);
    char *q, *r;
    int last_width = 0;
    int h = CHeightOf (wdt);
    int w = CWidthOf (wdt);
    int l, x, x1 = 0, color = 0;
    char *p;
    CPushFont ("widget", 0);
    q = wdt->text;
    p = lasttext;
    x = TEXT_RELIEF + 1;	/* bevel is 1 */
    if (lastwin == win && !expose) {
	for (; *p && *q && *p == *q; p++, q++) {
	    if (*q >= ' ') {
		x += CImageTextWidth (q, 1);
	    } else {
		if (*q == '\034') {
		    x1 = x;
		} else if (*q == '\035') {
		    x1 = x;
		    x += FS;
		} else
		    color = *q;
	    }
	}
    }
    for (l = x, r = q; *r; r++)
	if (*r >= ' ')
	    l += CImageTextWidth (r, 1);
	else if (*r == '\035')
	    l += FS;
    if (lastwin == win && !expose) {
	for (last_width = x, r = p; *r; r++)
	    if (*r >= ' ')
		last_width += CImageTextWidth (r, 1);
	    else if (*r == '\035')
		last_width += FS;
    }
    if (l < last_width && l < w) {
	CSetColor (COLOR_FLAT);
	CRectangle (win, l, 0, min (w - l, last_width - l), h);
    }
    CSetColor (color_palette (color % 27));
    CSetBackgroundColor (COLOR_FLAT);
    for (p = q;; p++) {
	if (*p < ' ') {
	    CImageText (win, FONT_OFFSET_X + x, FONT_OFFSET_Y + TEXT_RELIEF + 1, q, (unsigned long) p - (unsigned long) q);
	    x += CImageTextWidth (q, (unsigned long) p - (unsigned long) q);
	    if (*p == '\035') {
		XClearArea (CDisplay, win, x, TEXT_RELIEF + 1, x + FS, FONT_PIX_PER_LINE, 0);
		if (x - x1 + FB + FB - 2 > 0) {
		    render_bevel (win, x1 - FB, 0, x + FB - 1, h - 1, 1, 1);
		    XClearArea (CDisplay, win, x1 - FB + 1, 1, x - x1 + FB + FB - 2, TEXT_RELIEF + 1, 0);
		    XClearArea (CDisplay, win, x1 - FB + 1, h - TEXT_RELIEF - 1, x - x1 + FB + FB - 2, TEXT_RELIEF, 0);
		}
		x1 = x;
		x += FS;
	    } else if (*p == '\034') {
		if (x - x1 - FB - FB > 0) {
		    XClearArea (CDisplay, win, x1 + FB, 0, x - x1 - FB - FB, TEXT_RELIEF + 1, 0);
		    XClearArea (CDisplay, win, x1 + FB, h - TEXT_RELIEF - 1, x - x1 - FB - FB, TEXT_RELIEF + 1, 0);
		}
		x1 = x;
	    } else
		CSetColor (color_palette (*p % 27));
	    if (!*p)
		break;
	    q = p + 1;
	}
    }
    lastwin = win;
    strncpy (lasttext, wdt->text, 1023);
    CPopFont ();
    return;
}

void render_text (CWidget * wdt)
{
    (*look->render_text) (wdt);
}

void render_window (CWidget * wdt)
{
    (*look->render_window) (wdt);
}

void render_progress (CWidget * wdt)
{
    int w = wdt->width, h = wdt->height;
    int p = wdt->cursor;

    Window win = wdt->winid;

    if (p > 65535)
	p = 65535;
    if (p < 0)
	p = 0;
    CSetColor (COLOR_FLAT);
    CRectangle (win, 4 + p * (w - 5) / 65535, 2, (65535 - p) * (w - 5) / 65535, h - 4);
    CSetColor (color_palette (3));
    CRectangle (win, 4, 4, p * (w - 9) / 65535, h - 8);
    render_bevel (win, 2, 2, 4 + p * (w - 9) / 65535, h - 3, 2, 0);
    render_bevel (win, 0, 0, w - 1, h - 1, 2, 1);
}

void render_sunken (CWidget * wdt)
{
    int w = wdt->width, h = wdt->height;
    Window win = wdt->winid;
    render_bevel (win, 0, 0, w - 1, h - 1, 2, 1);
}

void render_bevel (Window win, int x1, int y1, int x2, int y2, int thick, int sunken)
{
    if (option_low_bandwidth)
	return;
    if (sunken & 1)
	(*look->render_sunken_bevel) (win, x1, y1, x2, y2, thick, sunken);
    else
	(*look->render_raised_bevel) (win, x1, y1, x2, y2, thick, sunken);
    CSetColor (COLOR_BLACK);
}

void expose_picture (CWidget * w);

void set_widget_position (CWidget * w, int x, int y)
{
    if (w->winid) {		/*some widgets have no window of there own */
	w->x = x;
	w->y = y;
	XMoveWindow (CDisplay, w->winid, x, y);
    } else {
#ifdef HAVE_PICTURE
	expose_picture (w);
	w->x = x;
	w->y = y;
	expose_picture (w);
#endif
    }
}

void CSetWidgetPosition (const char *ident, int x, int y)
{
    CWidget *w = CIdent (ident);
    if (!w)
	return;
    set_widget_position (w, x, y);
}

void configure_children (CWidget * wt, int w, int h)
{
    CWidget *wdt;
    int new_w, new_h, new_x, new_y, i;
    i = find_first_child_of (wt->winid);
    while (i) {
	wdt = CIndex (i);
	if (CGetFocus () == wdt->winid)		/* focus border must follow the widget */
	    destroy_focus_border ();
	if (wdt->resize) {
	    (*(wdt->resize)) (w, h, wt->width, wt->height, &new_w, &new_h, &new_x, &new_y);
	    if (wdt->height != new_h || wdt->width != new_w)
		CSetSize (wdt, new_w, new_h);
	    if (wdt->x != new_x || wdt->y != new_y)
		set_widget_position (wdt, new_x, new_y);
	} else {
	    if (wdt->position & POSITION_CENTRE)
		set_widget_position (wdt, (w - wdt->width) / 2, wdt->y);
	    if (wdt->position & POSITION_FILL)
		CSetSize (wdt, w - (WIDGET_SPACING + WINDOW_EXTRA_SPACING) - wdt->x, wdt->height);
	    if (wdt->position & POSITION_RIGHT)
		set_widget_position (wdt, wdt->x + w - wt->width, wdt->y);
	    if (wdt->position & POSITION_WIDTH)
		CSetSize (wdt, wdt->width + w - wt->width, wdt->height);
	    if (wdt->position & POSITION_BOTTOM)
		set_widget_position (wdt, wdt->x, wdt->y + h - wt->height);
	    if (wdt->position & POSITION_HEIGHT)
		CSetSize (wdt, wdt->width, wdt->height + h - wt->height);
	}
	if (CGetFocus () == wdt->winid)		/* focus border must follow the widget */
	    if ((wdt->options & WIDGET_TAKES_FOCUS_RING))
		create_focus_border (wdt, 2);
	i = find_next_child_of (wdt->parentid, wdt->winid);
    }
}

void CSetSize (CWidget * wt, int w, int h)
{
    int w_min, h_min;
    if (!wt)
	return;
    if (w == wt->width && h == wt->height)
	return;

    wt->resized = 1;

    if (w < 1)
	w = 1;
    if (h < 1)
	h = 1;

    if (wt->kind == C_WINDOW_WIDGET)
	configure_children (wt, w, h);
#if 0
    else if (wt->kind == C_RXVT_WIDGET)
	rxvt_resize_window (wt->rxvt, w, h);
#endif

/* redraw right and bottom borders */
    w_min = min (wt->width, w);
    h_min = min (wt->height, h);
    if (wt->kind == C_WINDOW_WIDGET)
	XClearArea (CDisplay, wt->winid, wt->width - 39, wt->height - 39, 39, 39, 1);
    XClearArea (CDisplay, wt->winid, w_min - 3, 0, 3, h_min, 1);
    XClearArea (CDisplay, wt->winid, 0, h_min - 3, w_min, 3, 1);
    wt->width = w;
    wt->height = h;
    if (wt->parentid == CRoot && wt->mapped)	/* afterstep doesn't like us to change the size of a mapped main window */
	return;
    XResizeWindow (CDisplay, wt->winid, w, h);
#ifdef USE_XIM
    set_status_position (wt);
#endif
}

void CSetWidgetSize (const char *ident, int w, int h)
{
    CWidget *wt = CIdent (ident);
    if (!wt)
	return;
    CSetSize (wt, w, h);
}

void CSetMovement (const char *ident, unsigned long position)
{
    CWidget *w;
    w = CIdent (ident);
    if (!w)
	return;
    w->position |= position;
}

void CCentre (char *ident)
{
    CSetMovement (ident, POSITION_CENTRE);
}

/* does a map as well */
void CSetSizeHintPos (const char *ident)
{
    int x, y;
    CWidget *w;
    get_hint_limits (&x, &y);
    w = CIdent (ident);
    x += WINDOW_EXTRA_SPACING;
    y += WINDOW_EXTRA_SPACING;
    if (!(w->options & WINDOW_NO_BORDER))
	y += (*look->get_window_resize_bar_thickness) ();
    XResizeWindow (CDisplay, w->winid, x, y);
    w->width = x;
    w->height = y;
    configure_children (w, x, y);
}

/* for mapping a main window. other widgets are mapped when created */
void CMapDialog (const char *ident)
{
    CWidget *w;
    w = CIdent (ident);
    if (!w)
	return;
    if (w->kind != C_WINDOW_WIDGET)
	return;
    if (w->parentid == CRoot
	&& !(w->options & WINDOW_SIZE_HINTS_SET)) {
/* A main window with WM size hints not configured. */
	CSetWindowSizeHints (w, w->width, w->height, w->width, w->height);
    }
    XMapWindow (CDisplay, w->winid);	/* shows the window */
    XFlush (CDisplay);
}
