/* $Id: Shell.c,v 1.2 1997/05/01 12:05:55 archer Exp $
 *	SHELL.C - Handle shell objects
 *
 * Shells interact with the window manager, and dispatch events for grabs
 * and focused events
 */
#include <Widget/ShellP.h>
#include <Widget/Manage.h>
#include <Widget/drawP.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#ifdef SUPPORT_EDITRES
#if XT_REVISION > 4
#include <X11/IntrinsicP.h>
#include <X11/Xmu/EditresP.h>
#endif
#endif
#include <string.h>


/**
 ** Public variable
 **/
KeySym		x11__key_sym;
unsigned int	x11__key_mods;
char		x11__key_buffer[32];


/**
 ** Private variables
 **/
static X11_icon	*generic_icon;
static int	keyed;
#define NB_PROTOCOLS	1
static Atom	wm_protocols[NB_PROTOCOLS];
#ifdef SUPPORT_EDITRES
static Atom	editres_atom;
#endif


/**
 ** Internal: SHELL_CHILD
 **	First child of a shell *must* be the managed child
 **/
static X11_widget shell_child(x11__shell *sh)
{
X11_widget	p, n;
/*
 * Already sorted?
 */
	if ((p = sh->core.child) == 0 || !p->unmanaged)
		return p;
/*
 * Scan list
 */
	for (n = p->sibling; n; n = n->sibling)
		if (n->unmanaged)
			break;
	if (n) {
		p->sibling = n->sibling;
		n->sibling = sh->core.child;
		sh->core.child = n;
	}
	return n;
}


/**
 ** X11_SHELL_CLEANUP
 **	Free the additional data for a shell
 **/
void x11__shell_cleanup(X11_widget wdg, XEvent *dummy)
{
x11__key	*k;
/*
 * Free all key accelerators
 */
	while ((k = W_SHELL->accelerators) != 0) {
		W_SHELL->accelerators = k->next;
		free(k);
	}
/*
 * Free the icon
 */
	X11_IconRelease(W_SHELL->icon);
/*
 * Free the title
 */
	if (W_SHELL->title) {
		free(W_SHELL->title);
		W_SHELL->title = 0;
	}
}


/**
 ** Internal: SHELL_MESSAGE
 **	Got a message from WM
 **/
static void shell_message(x11__shell *wdg, XClientMessageEvent *ev)
{
/*
 * Is this a close-down message?
 */
/*** HACK ***/
/*
 * Is this an EditRes message?
 */
#ifdef SUPPORT_EDITRES
#if XT_REVISION > 4
	if (!editres_atom)
		editres_atom = XInternAtom(x11__screen, EDITRES_NAME, True);
/*** HACK ***/
	if (ev->message_type == editres_atom)
printf("EditRes protocol\n");
#endif
#endif
/*
 * Unknown message
 */
#ifdef DEBUGGING_X11
printf("Message type %d for Shell \"%s\"\n",(int)ev->message_type,wdg->basic.name);
#endif
/*** HACK ***/
}


/**
 ** Internal: SHELL_EVENT
 **	Receive a non-standard event from outside
 **/
static void shell_event(X11_widget wdg, XEvent *event)
{
X11_widget	f;
/*
 * Dispatch event
 */
	switch (event->type) {
/*
 * Message from WM
 */
	    case ClientMessage:
		shell_message(W_SHELL, &event->xclient);
		break;
/*
 * Shell gets the keyboard focus
 */
	    case FocusIn:
		W_SHELL->has_keyboard_focus = True;
		if ((f = W_SHELL->current_focus) != 0) {
			f->has_focus = True;
			if (f->on_focus_change)
				(*f->on_focus_change)(f, (XEvent *)0);
			else
				if (f->on_expose && !f->unmapped && f->id)
					(*f->on_expose)(f, (XEvent *)0);
		}
		break;
/*
 * Shell loses the keyboard focus
 */
	    case FocusOut:
		W_SHELL->has_keyboard_focus = False;
		if ((f = W_SHELL->current_focus) != 0) {
			f->has_focus = False;
			if (f->on_focus_change)
				(*f->on_focus_change)(f, (XEvent *)0);
			else
				if (f->on_expose && !f->unmapped && f->id)
					(*f->on_expose)(f, (XEvent *)0);
		}
		break;
/*
 * Other events...
 */
#ifdef X11_DEBUGGING
	    default:
		printf("Strange event %d for Shell %s\n",event->type,wdg->name);
		break;
#endif
	}
}


/**
 ** Internal: SHELL_RESIZE
 **	Resize from WM
 **/
static void shell_resize(X11_widget wdg, XEvent *size)
{
X11_widget	child;
int		w, h;
/*
 * Resized
 */
	if (size) {
		wdg->w = size->xconfigure.width;
		wdg->h = size->xconfigure.height;
	}
/*
 * Find which child we're managing...
 */
	if ((child = shell_child(W_SHELL)) == 0)
		return;
	W_SHELL->reshuffling = True;
/*
 * Child resize
 */
	w = wdg->w;
	h = wdg->h;
/*
 * Special! Our child didn't geometrise first!
 */
	if (!child->h || !child->w)
		W_SHELL->shuffle_again = True;
	else {
		child->w = w;
		child->h = h;
		(*child->on_resize_window)(child, (XEvent *)0);
	}
/*
 * Reshuffle occurs?
 */
	if (W_SHELL->shuffle_again) {
		W_SHELL->shuffle_again = False;
		(*wdg->on_geometry_check)(wdg, (XEvent *)0);
	}
	W_SHELL->reshuffling = False;
}


/**
 ** Internal:SHELL_GEOMETRY
 **	Apply geometry from a shell
 **/
static void shell_geometry(X11_widget wdg, XEvent *dummy)
{
X11_widget	child;
XEvent		fake_size;
/*
 * Find child
 */
	if ((child = shell_child(W_SHELL)) == 0)
		return;
	W_SHELL->reshuffling = True;
/*
 * Actual geometry is from child
 */
	(*child->on_geometry_check)(child, dummy);
/*
 * Size hints
 */
	wdg->w_min   = child->w_min;
	wdg->fixed_w = child->fixed_w;
	wdg->h_min   = child->h_min;
	wdg->fixed_h = child->fixed_h;
/*
 * Actual hints
 */
	if (wdg->w < wdg->w_min || wdg->fixed_w)
		wdg->w = child->w;
	if (wdg->h < wdg->h_min || wdg->fixed_h)
		wdg->h = child->h;
/*
 * Window sized
 */
	x11__core_resize(wdg, dummy);
/*
 * Fake resize event (just in case)
 */
	fake_size.xconfigure.width  = wdg->w;
	fake_size.xconfigure.height = wdg->h;
	(*wdg->on_resize_window)(wdg, &fake_size);
}


/**
 ** X11_KEY_TRANSLATE
 **	Translate an X11 key event into correct string
 **/
char *x11__key_translate(XKeyEvent *e)
{
/*
 * First time parse?
 */
	if (e != 0) {
		keyed = XLookupString(e, x11__key_buffer,
					sizeof(x11__key_buffer), &x11__key_sym,
					(XComposeStatus *)0);
		x11__key_mods = e->state;
	}
/*
 * Return value?
 */
	if (keyed > 0) {
		x11__key_buffer[keyed] = 0;
		return x11__key_buffer;
	}
	return 0;
}


/**
 ** X11_KEY_ACT
 **	Act upon key in the given shell
 **/
void x11__key_act(X11_widget wdg, XEvent *e)
{
X11_widget	h;
X11_widget	o;
x11__key	*k;
int		mod;
/*
 * First translations
 */
	(void)x11__key_translate((XKeyEvent *)e);
	h = shell_child(W_SHELL);
/*
 * Check translations...
 */
	for (k = W_SHELL->accelerators; k; k = k->next)
		if (k->key == x11__key_sym) {
			mod = k->modifiers;
			if (mod == 0 &&
			    x11__key_sym >= XK_a &&
			    x11__key_sym <= XK_z)
				mod = ShiftMask;
			mod |= Mod4Mask|ControlMask;
			if (k->modifiers == (x11__key_mods & mod) &&
			    X11_IsManaged(k->requester)) {
				(*k->call)(k->requester, k->data);
				return;
			}
		}
/*
 * If there's a default, handle appropriate keysyms
 */
	if ((o = W_SHELL->current_default) != 0 && o->on_key_press &&
	    (x11__key_sym == XK_Return || x11__key_sym == XK_Execute ||
	     x11__key_sym == XK_KP_Enter) && X11_IsManaged(o)) {
		(*o->on_key_press)(o, (XEvent *)0);
		return;
	}
/*
 * If all else fails, current focus gets the event
 */
	if ((o = W_SHELL->current_focus) != 0 && o->on_key_press)
		(*o->on_key_press)(o, (XEvent *)0);
}


/**
 ** Internal: X11_SHELL_TITLE_CHANGED
 **	Sets WM title
 **/
static void x11__shell_title_changed(x11__shell *shell)
{
XTextProperty	name;
char		*title;
char		*s;
char		iconic[64];
int		l;
/*
 * Set Name hints
 */
	if ((title = shell->title) == 0)
		return;
	XStringListToTextProperty(&title, 1, &name);
	XSetWMName(x11__screen, shell->core.id, &name);
	if ((s = strchr(title, ' ')) != 0) {
		l = s - title;
/*
 * Icon name is same as window name?
 */
		if (l > 0 && l < 64) {
			XFree(name.value);
			strncpy(iconic, title, l);
			iconic[l] = '\0';
			s = iconic;
			XStringListToTextProperty(&s, 1, &name);
		}
	}
/*
 * Set icon name
 */
	XSetWMIconName(x11__screen, shell->core.id, &name);
	XFree(name.value);
}


/**
 ** Internal: SHELL_CREATE
 **	Create a shell window, and set title & icon
 **/
static void shell_create(X11_widget wdg, XEvent *dummy)
{
/*
 * Use CORE method
 */
	x11__core_create(wdg, dummy);
/*
 * Special for WM-managed windows
 */
	XSetWMProtocols(x11__screen, wdg->id, wm_protocols, NB_PROTOCOLS);
	x11__shell_title_changed(W_SHELL);
}


/**
 ** X11_DEFAULT_LOSES
 **	Widget loses the default
 **/
void x11__default_loses(X11_widget f)
{
x11__shell	*s;
/*
 * Set default for which shell?
 */
	s = (x11__shell *)f->shell;
	if (s->current_default != f)
		return;
/*
 * Default disappear
 */
	s->current_default = 0;
	if (!f->unmapped && f->on_expose)
		(*f->on_expose)(f, (XEvent *)0);
}


/**
 ** X11_DEFAULT_GETS
 **	Widget gets the default
 **/
void x11__default_gets(X11_widget f)
{
x11__shell	*s;
X11_widget	o;
/*
 * Set default for which shell?
 */
	s = (x11__shell *)f->shell;
	if ((o = s->current_default) == f)
		return;
/*
 * Old loses default
 */
	if (o)
		x11__default_loses(o);
/*
 * New gains default
 */
	s->current_default = f;
}


/**
 ** X11_FOCUS_LOSES
 **	Widget loses the focus
 **/
void x11__focus_loses(X11_widget wdg)
{
x11__shell	*s;
/*
 * Set focus for which shell?
 */
	s = (x11__shell *)wdg->shell;
	if (s->current_focus != wdg)
		return;
/*
 * Focus disappaear
 */
	s->current_focus = 0;
	wdg->has_focus   = False;
	if (wdg->on_focus_change)
		(*wdg->on_focus_change)(wdg, (XEvent *)0);
	else
		if (wdg->on_expose && !wdg->unmapped && wdg->id)
			(*wdg->on_expose)(wdg, (XEvent *)0);
/*
 * Focus reverts to?
 */
/*** HACK ***/
}


/**
 ** X11_SHELL_FOCUS
 **	Widget gets the focus
 **/
void X11_ShellFocus(X11_widget wdg)
{
x11__shell	*s;
X11_widget	o;
/*
 * Set focus for which shell?
 */
	s = (x11__shell *)wdg->shell;
	if ((o = s->current_focus) == wdg)
		return;
/*
 * Old loses focus
 */
	if (o && o->has_focus) {
		o->has_focus = False;
		if (o->on_focus_change)
			(*o->on_focus_change)(o, (XEvent *)0);
		else
			if (o->on_expose && o->id && !o->unmapped)
				(*o->on_expose)(o, (XEvent *)0);
	}
/*
 * New gains focus
 *	(we do not expose: Usually, our caller will expose a few lines after
 *	calling focus_gets)
 */
	s->current_focus = wdg;
	if (wdg && s->has_keyboard_focus) {
		wdg->has_focus = True;
		if (wdg->on_focus_change)
			(*wdg->on_focus_change)(wdg, (XEvent *)0);
	}
}


/**
 ** X11_FOCUS_MIGHT
 **	Widget might have focus (if no one else has)
 **/
void x11__focus_might(X11_widget wdg)
{
/*
 * First to request focus gets it!
 */
	if (((x11__shell *)wdg->shell)->current_focus == 0)
		X11_ShellFocus(wdg);
}


/**
 ** X11_SHELL_GENERAL_ICON
 **	Set the default icon for all shells
 **/
void X11_ShellGeneralIcon(X11_icon *def)
{
	X11_IconRelease(generic_icon);
	generic_icon = def;
	X11_IconUsed(def);
}


/**
 ** X11_SHELL_POSITION
 **	Where on the screen is located this Shell
 **/
void X11_ShellPosition(X11_widget wdg, int *x, int *y,
					unsigned int *w, unsigned int *h)
{
int	p_x, p_y;
/*
 * Need X/Y?
 */
	if (x || y) {
/*
 * Need X/Y, not of the Widget, but the Window-Manager container
 */
		p_x = 0;
		p_y = 0;
/*** HACK ***/
/*
 * Position is now known
 */
		if (x)
			*x = p_x;
		if (y)
			*y = p_y;
	}
/*
 * Size is straightforward
 */
	if (w)
		*w = wdg->w;
	if (h)
		*h = wdg->h;
}


/**
 ** X11_SHELL_HINTS
 **	Locate the Shell at this position
 **/
void X11_ShellHints(X11_widget wdg, int x, int y,
					unsigned int w, unsigned int h)
{
	wdg->hint_x = x;
	wdg->hint_y = y;
	wdg->hint_w = w;
	wdg->hint_h = h;
}


/**
 ** X11_SHELL_ICON
 **	Set a specific icon for a given shell
 **/
void X11_ShellIcon(X11_widget wdg, X11_icon *icon)
{
	X11_IconRelease(W_SHELL->icon);
	W_SHELL->icon = icon;
	X11_IconUsed(icon);
}


/**
 ** X11_SHELL_TITLE
 **	Sets the Title of the shell
 **/
void X11_ShellTitle(X11_widget wdg, char *title)
{
char	*old;
/*
 * Purge previous title, if necessary
 */
	if ((old = W_SHELL->title) != 0) {
		if (strcmp(old, title) == 0)
			return;
		free(old);
	}
/*
 * Allocate
 */
	if (title)
		title = strdup(title);
	W_SHELL->title = title;
	if (wdg->id)
		x11__shell_title_changed(W_SHELL);
}


/**
 ** X11_SHELL_SET_EXIT
 **	What happens upon the exit of a shell? (default is: nothing)
 **/
void X11_ShellSetExit(X11_widget wdg, X11_callback call, void *data)
{
	W_SHELL->on_exitrequest = call;
	W_SHELL->user_data      = data;
}


/**
 ** X11_SHELL
 **	Create a shell window
 **/
X11_widget X11_Shell(char *name, char *title, X11_widget transient_for)
{
x11__shell	*object;
/*
 * Get resource
 */
	object = allocator(x11__shell);
	x11__core_setup(&object->core, x11__widget_root, "Shell", name);
/*
 * We're the shell
 */
	object->core.shell = &object->core;
/*
 * Initialize fields
 */
	object->core.on_destruction    = x11__shell_cleanup;
	object->core.on_geometry_check = shell_geometry;
	object->core.on_key_press      = x11__key_act;
	object->core.on_resize_window  = shell_resize;
	object->core.on_special_event  = shell_event;
	object->core.on_window_create  = shell_create;
	object->core.event_mask        = KeyPressMask|KeyReleaseMask|
					ButtonPressMask|ButtonReleaseMask|
					FocusChangeMask|StructureNotifyMask;
	object->core.unmapped          = True;
	object->core.unmanaged         = True;
/*
 * Fetch X11 resources
 */
	x11__resource_basic(&object->core);
	object->core.border2 = 0;	/* Window manager says this */
/*
 * Need WM_DELETE_WINDOW protocol
 */
	if (!wm_protocols[0])
		wm_protocols[0] = XInternAtom(x11__screen, "WM_DELETE_WINDOW",
									False);
/*
 * Default title
 */
	if (!title)
		title = name;
	X11_ShellTitle((X11_widget)object, title);
	return (X11_widget)object;
}
