#include "global.hh"
//#define_GNU_SOURCE // for vasprintf

#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>

#include "anim.hh"
#include "bindings.hh"
#include "commands.hh"
#include "events.hh"
#include "log.hh"
#include "main.hh"
#include "menus.hh"
#include "misc.hh"
#include "pager.hh"
#include "style.hh"
#include "windows.hh"

XClassHint NoClass; // for applications with no class
char *NoName = (char *) "Untitled";

WinStyle dwin_style;
WinList win_list;
InfoWin info_window;

SWindow *swindow;

//********************************************************************

WinStyle::WinStyle ()
{
	frame = grey;
	title = black;
	outline = grey;
	border = black;
}

int
WinStyle::init ()
{
	// load colors
	frame.init ();
	title.init ();
	outline.init ();
	border.init ();

	// titleGC
	#ifdef FONTS_XCORE
	gcv.foreground = dwin_style.title.pixel;
	gcv.function = GXor;
	gcm = GCForeground | GCFunction | GCFont;
	gcv.font = swm_font.font->fid;
	titleGC = XCreateGC (dpy, root_window, gcm, &gcv);
	#endif

	gcv.foreground = dwin_style.frame.pixel;
	gcv.fill_style = FillSolid;
	gcm = GCForeground | GCFillStyle;
	titbkGC = XCreateGC (dpy, root_window, gcm, &gcv);

	gcv.foreground = dwin_style.border.pixel;
	gcv.fill_style = FillSolid;
	gcm = GCForeground | GCFillStyle;
	borderGC = XCreateGC (dpy, root_window, gcm, &gcv);

	XSync (dpy, 0);

	bkg = new MyPixmap;
	bkg->load (bkg_xf);
}

//********************************************************************

char *close_free_xf = NULL;
char *close_down_xf = NULL;
char *close_hover_xf = NULL;
char *mx_free_xf = NULL;
char *mx_down_xf = NULL;
char *mx_hover_xf = NULL;
char *hide_free_xf = NULL;
char *hide_down_xf = NULL;
char *hide_hover_xf = NULL;
char *winops_free_xf = NULL;
char *winops_down_xf = NULL;
char *winops_hover_xf = NULL;

MyPixmap *close_free_pm;
MyPixmap *close_down_pm;
MyPixmap *close_hover_pm;
MyPixmap *mx_free_pm;
MyPixmap *mx_down_pm;
MyPixmap *mx_hover_pm;
MyPixmap *hide_free_pm;
MyPixmap *hide_down_pm;
MyPixmap *hide_hover_pm;
MyPixmap *winops_free_pm;
MyPixmap *winops_down_pm;
MyPixmap *winops_hover_pm;

#include "buttons/close_free.xpm"
#include "buttons/close_down.xpm"
#include "buttons/close_hover.xpm"
#include "buttons/mx_free.xpm"
#include "buttons/mx_down.xpm"
#include "buttons/mx_hover.xpm"
#include "buttons/hide_free.xpm"
#include "buttons/hide_down.xpm"
#include "buttons/hide_hover.xpm"
#include "buttons/winops_free.xpm"
#include "buttons/winops_down.xpm"
#include "buttons/winops_hover.xpm"

int
buttons_init ()
{
	close_free_pm = new MyPixmap;
	close_down_pm = new MyPixmap;
	close_hover_pm = new MyPixmap;
	mx_free_pm = new MyPixmap;
	mx_down_pm = new MyPixmap;
	mx_hover_pm = new MyPixmap;
	hide_free_pm = new MyPixmap;
	hide_down_pm = new MyPixmap;
	hide_hover_pm = new MyPixmap;
	winops_free_pm = new MyPixmap;
	winops_down_pm = new MyPixmap;
	winops_hover_pm = new MyPixmap;

	if (!close_free_pm->load (close_free_xf)) close_free_pm->create (close_free_xpm);
	if (!close_down_pm->load (close_down_xf)) close_down_pm->create (close_down_xpm);
	if (!close_hover_pm->load (close_hover_xf)) close_hover_pm->create (close_hover_xpm);
	if (!mx_free_pm->load (mx_free_xf)) mx_free_pm->create (mx_free_xpm);
	if (!mx_down_pm->load (mx_down_xf)) mx_down_pm->create (mx_down_xpm);
	if (!mx_hover_pm->load (mx_hover_xf)) mx_hover_pm->create (mx_hover_xpm);
	if (!hide_free_pm->load (hide_free_xf)) hide_free_pm->create (hide_free_xpm);
	if (!hide_down_pm->load (hide_down_xf)) hide_down_pm->create (hide_down_xpm);
	if (!hide_hover_pm->load (hide_hover_xf)) hide_hover_pm->create (hide_hover_xpm);
	if (!winops_free_pm->load (winops_free_xf)) winops_free_pm->create (winops_free_xpm);
	if (!winops_down_pm->load (winops_down_xf)) winops_down_pm->create (winops_down_xpm);
	if (!winops_hover_pm->load (winops_hover_xf)) winops_hover_pm->create (winops_hover_xpm);
}

//********************************************************************

struct CloseButton: Button
{
	CloseButton ();
	int exec ();
};

CloseButton::CloseButton ()
{
	pm_free = close_free_pm;
	pm_hover = close_hover_pm;
	pm_down = close_down_pm;
}

int
CloseButton::exec ()
{
	cq.add (new WinCmd (event->sw, CW_CLOSE));
}

//********************************************************************

struct MaxButton: Button
{
	MaxButton ();
	int exec ();
};

MaxButton::MaxButton ()
{
	pm_free = mx_free_pm;
	pm_hover = mx_hover_pm;
	pm_down = mx_down_pm;
}

int
MaxButton::exec () {
	cq.add (new WinCmd (event->sw, CW_MAX));
}

// ??? RestoreButton with other pixmap, decors_maximize would add it
// instead of MaxButton
//********************************************************************

struct HideButton: Button {
	HideButton ();
	int exec ();
};

HideButton::HideButton () {
	pm_free = hide_free_pm;
	pm_hover = hide_hover_pm;
	pm_down = hide_down_pm;
}

int
HideButton::exec () {
	cq.add (new WinCmd (event->sw, CW_HIDE));
}

//********************************************************************
struct WinOpsButton: Button {
	WinOpsButton ();
	int exec ();
};

WinOpsButton::WinOpsButton () {
	pm_free = winops_free_pm;
	pm_hover = winops_hover_pm;
	pm_down = winops_down_pm;
}

int
WinOpsButton::exec () {
	if (winops_menu) winops_menu->exec ();
}

//********************************************************************

DWin::DWin ()
{
	XSetWindowAttributes at;
	at.cursor = cursor;
	at.event_mask =
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask
		| ExposureMask | EnterWindowMask | LeaveWindowMask | StructureNotifyMask;
	at.border_pixel = menu_style.rule.pixel;
	vm = CWCursor | CWEventMask | CWBorderPixel;

	if (dwin_style.bkg->pm) {
		at.background_pixmap = dwin_style.bkg->pm;
		vm |= CWBackPixmap;
	}
	else {
		at.background_pixel = dwin_style.frame.pixel;
		vm |= CWBackPixel;
	};

	w = XCreateWindow
		(dpy, root_window, 0, 0, 1, 1, 0,
		 CopyFromParent, InputOutput, CopyFromParent, vm, &at);

	hbox = new HBox; // will be deleted in SWindow::setup
	last_clicked = 0;
}

int
DWin::cl ()
{
	XClearWindow (dpy, w);
}

int
DWin::handle_event ()
{
	boxw = w;

	Win::handle_event ();

	switch (event->x.type)
	{
	case ButtonPress:
		if (!event->handled) {
			if (event->x.xbutton.time - last_clicked < double_click_ms) {
				last_clicked = 0;
				event->context = C_DBL_DECOR;
			}
			else {
				last_clicked = event->x.xbutton.time;
				event->context = C_DECOR;
			}
			bind_list.handle_event ();
		};
		break;
	case EnterNotify:
		event->sw->focus ();
		break;
	}
}

SWindow::SWindow (Window iawin, int place_and_map)
{
	// setup structure for window to be managed
	awin = iawin;

	// get some info -- name, class, protocols
	name = icon_name = NoName;
	has_net_wm_name = 0;
	has_net_wm_icon_name = 0;
	get_name ();
	get_icon_name ();

	/*
	get_command ();
	if (start_cmd)
		log_window.pr ("%s", start_cmd);
	*/
	
	//  printf ("swindow::swindow begin %s\n", name);

	class_hint = NoClass;
	XGetClassHint (dpy, awin, &class_hint);
	if (!class_hint.res_name)
		class_hint.res_name = NoName;
	if (!class_hint.res_class)
		class_hint.res_class = NoName;

	flags = 0;
	get_wm_protocols ();

	wmhints = XGetWMHints (dpy, awin);

	if (XGetTransientForHint (dpy, awin, &transient_for)) {
		flags |= TRANSIENT;
	}
	else {
		transient_for = None;
		flags &= ~TRANSIENT;
	};
		
	get_size_hints ();
	mwm_hints_get ();
	
	pmode = WPM_free;

	// get SWM hints from server
	Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after;
	SWM_Hints *xs_swm_hints;
	int retval = XGetWindowProperty (dpy, awin, _XA_SWM_HINTS, 0L, sizeof (SWM_Hints), 0, AnyPropertyType,
		  &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **) &xs_swm_hints);

	if (retval == Success  &&  actual_type != None  &&  xs_swm_hints != NULL) {

		pmode = xs_swm_hints->pmode_saved;
/*		log_window.pr("---%s", name);
		log_window.pr("actual_type=%d", actual_type); 
		log_window.pr("actual_format=%d", actual_format); 
		log_window.pr("nitems=%d", nitems); 
		log_window.pr("bytes_after=%d", bytes_after); 
		log_window.pr("saved: %dx%d%+d%+d", xs_swm_hints->wd_saved, xs_swm_hints->ht_saved, xs_swm_hints->x_saved, xs_swm_hints->y_saved);
		log_window.pr("pmode_saved: %d", xs_swm_hints->pmode_saved);*/
		if (pmode != WPM_free) {
			// starting in special mode
			a_saved = new AWin;
			a_saved->x = xs_swm_hints->x_saved;
			a_saved->y = xs_swm_hints->y_saved;
			a_saved->wd = xs_swm_hints->wd_saved;
			a_saved->ht = xs_swm_hints->ht_saved;
		};
		XFree (xs_swm_hints);
	}

	// where to get ICCCM: see the X source files, /doc directory

	// force not save_under (because it slows down everything)
	// ICCCM (section 4.1.6) says this is OK
	XSetWindowAttributes at;
	at.save_under = FALSE;
	//  at.do_not_propagate_mask = wat.do_not_propagate_mask | ButtonPressMask;
	unsigned long vm = CWSaveUnder; // | CWDontPropagate;
	XChangeWindowAttributes (dpy, awin, vm, &at);

	if (awin == pager.w) {
		// special pager stuff
		spager = this;
		spager->flags |= STICKY; // pager remains sticky - for the movement of windows on it
	}
	else if (awin == log_window.w) {
		// special log_windowin stuff
		log_swindow = this;
	}
	else {
		// catch events (e. g. we will catch DestroyNotify -- see remark in Event::handle_MapRequest)
		XSelectInput (dpy, awin, StructureNotifyMask | PropertyChangeMask | EnterWindowMask | LeaveWindowMask);
	}

	bind_list.grab (awin);

	// create pager_view
	at.cursor = cursor;
	at.border_pixel = pager.win_border.pixel;
	at.event_mask = ExposureMask | PointerMotionMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask;
	vm = CWCursor | CWBorderPixel | CWEventMask;

	if (pager.win_bkg->pm) {
		at.background_pixmap = pager.win_bkg->pm;
		vm |= CWBackPixmap;
	}
	else {
		at.background_pixel = dwin_style.frame.pixel;
		vm |= CWBackPixel;
	};

	pager_view = XCreateWindow
		(dpy, pager.w, 0, 0, 1, 1, 1,
		CopyFromParent, InputOutput, CopyFromParent, vm, &at);
	
	pager_view_skip = 0;

	// create dwin - window for decorations ("frame")
	a_get ();
	dwin = new DWin;
	dwin->hbox->aadd(a); // so that the 'a' allocated in a_get gets deleted properly with decor_build

	style_setup ();
	decor_build (WPM_current, true);

	Window wins[2];
	wins[0] = awin;
	wins[1] = dwin->w;
	XRestackWindows (dpy, wins, 2);

	// ??? bl.grab (dwin->w, context)
	// ??? e.g. bl.grab (dwin->w, C_DECOR)
	// ??? see bindings.cc
	bind_list.grab (dwin->w);
		
	if (place_and_map) {
		int manual_placement = 0;

		//    log_window.pr ("%s %x", name, hints.flags);

		// place it
		if (flags & TRANSIENT  || hints.flags & (USPosition | PPosition)  || starts_hidden ()) {
			// if the USPosition/PPosition was specified, or the window is a
			// transient, or it starts hidden -- place it where requested
			// (don't do anything)

			// except try to keep it on the screen

			if (dwin->hbox->wd < root.wd) {
				if (dwin->wr.x < 0)
					dwin->wr.x = 0;
				else if (dwin->wr.x + dwin->hbox->wd > root.wd) 
					dwin->wr.x = root.wd - dwin->hbox->wd;
			};

			if (dwin->hbox->ht < root.ht) {
				if (dwin->wr.y < 0)
					dwin->wr.y = 0;
				else if (dwin->wr.y + dwin->hbox->ht > root.ht)
					dwin->wr.y = root.ht - dwin->hbox->ht;
			};

			sync_to_xserver ();
		}
		else if (swm_flags & SWM_CENTER_PLACEMENT) {
			// center placement;
			dwin->wr.x = (root.wd - dwin->wr.wd) / 2;
			dwin->wr.y = (root.ht - dwin->wr.ht) / 2;

			sync_to_xserver ();
		}
		// else CascadePlacement?
		else {
			// manual placement
			ppos.query ();
			dwin->wr.x = ppos.rx;
			dwin->wr.y = ppos.ry;
			dwin->align ();

			sync_to_xserver ();

			manual_placement = 1;
		};

		// appearing animation
		/*
		if (flags & TRANSIENT && wl.findw (&swindow, transient_for))
			anim.from.set (swindow->dwin->wr);
		else
		{
		*/
			//      anim.from.start ();
			anim.from.ppos ();
			//    };

		// map it
		anim.to.set (dwin->wr);
		anim.type = ANIM_AD;
		anim.run ();

		if (manual_placement) do_move (1);
		
		map_update ();
	}
	else {
		sync_to_xserver ();
		map_update ();
	}

	// finish
	unmap_events = 0;
	// keep active & ontops back above newly created windows
	win_list.raise ();
	
	//	if (flags & DoesWmTakeFocus) send_message (_XA_WM_TAKE_FOCUS, CurrentTime);
	//	if (wmhints) log_window.dbg ("%s %d", name, wmhints->initial_state);

	if (starts_hidden()) hide (); else set_map_state (NormalState);

	XSync (dpy, 0);
}

int
SWindow::starts_hidden ()
{
	return (wmhints  &&  wmhints->flags & StateHint  &&  wmhints->initial_state == IconicState);
}

int
SWindow::onto_top ()
{
	// make the window the first in the wl's list. Remark: the window
	// must be already in the list! (this can't be used e.g. in
	// SWindow::SWindow, which is called from events.cc with wl.add (new
	// SWindow (...))
	
	if (win_list.first != this) {
		SWindow *i;
		for (i = win_list.first; i->next != this; i=i->next);
			
		i->next = next;
		next = win_list.first;
		win_list.first = this;
	}

	return 1;
}

//********************************************************************

int outline_wd = 4;

struct Outline {
	SWindow *parent;
	Rect r;
	GC gc;
	
	Outline (Rect &);
	~Outline ();
	int draw ();
	int update (Rect &);
};

Outline::Outline (Rect &t)
{
	//  XGrabServer (dpy);

	// create GC
	gcm = GCFunction | GCForeground | GCFillStyle | GCLineWidth | GCSubwindowMode;
	gcv.function = GXxor;
	gcv.foreground = dwin_style.outline.pixel;
	gcv.fill_style = FillSolid;
	gcv.line_width = outline_wd;
	gcv.subwindow_mode = IncludeInferiors;
	gc = XCreateGC (dpy, root_window, gcm, &gcv);

	r = t;
	//  draw ();
}

Outline::~Outline () {
	//  draw ();

	XFreeGC (dpy, gc);
	//  XUngrabServer (dpy);
}

int
Outline::update (Rect &nr)
{
	if (nr != r) {
		draw ();
		r = nr;
		draw ();
	}
}

int
Outline::draw ()
{
	// gc.function is GXxor - first (odd) call draws, second (even) clear
	if (r.wd || r.ht) XDrawRectangle (dpy, root_window, gc,
		r.x + outline_wd/2, r.y + outline_wd/2, r.wd - outline_wd, r.ht - outline_wd);
}

//********************************************************************

int
SWindow::focus ()
{
	// *** FIXME ***
	/*
		XSetInputFocus (dpy, awin, RevertToParent, CurrentTime);
		wl.focus = this;
	*/

	// make the window focused (active) from swm point of view
	win_list.focus = this;
	onto_top ();

	// set the input focus, but only if we can
	if (!wmhints || wmhints->input) {
		XSetInputFocus (dpy, awin, RevertToPointerRoot, CurrentTime);
		return 1;
	}
	else {
		return 0;
	}
}

int
SWindow::get_name ()
{
	if (name != NoName) delete name;

	name = get_net_wm_name();
	if (!name) name = get_wm_name ();
	if (!name) name = NoName;
}

char *
SWindow::get_net_wm_name ()
{
	Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after;
	char *string;
	char *ret=NULL;

	int retval=XGetWindowProperty (dpy, awin, _XA_NET_WM_NAME, 0L, 0xffffff, False, UTF8_STRING,
		  &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **) &string);
	
	if (retval==Success && string) {
		ret = strndup(string, nitems);
		has_net_wm_name = 1;
		XFree(string);
	};

	return ret;
}

char *
SWindow::get_wm_name ()
{
	// XFetchName wrapper
	// copied from WindowMaker
	XTextProperty text_prop;
	char **list;
	char *ret=NULL;
	int num;

	if (XGetWMName (dpy, awin, &text_prop)) {
		if (text_prop.value && text_prop.nitems > 0) {
			if (text_prop.encoding == XA_STRING) {
				ret = strdup ((char *) text_prop.value);
				XFree (text_prop.value);
			}
			else {
				text_prop.nitems = strlen ((char *) text_prop.value);
				if (XmbTextPropertyToTextList (dpy, &text_prop, &list, &num) >= Success && num > 0 && *list) {
					XFree (text_prop.value);
					ret = strdup (*list);
					XFreeStringList (list);
				}
				else {
					ret = strdup ((char *) text_prop.value);
					XFree (text_prop.value);
				}
			}
		}
		else
			// the title was set to none
			ret = strdup ("");
	}

	return ret;
}

int
SWindow::get_icon_name ()
{
	if (icon_name != NoName) delete icon_name;

	icon_name = get_net_wm_icon_name();
	if (!icon_name) icon_name = get_wm_icon_name ();
	if (!icon_name) icon_name = NoName;
}

char *
SWindow::get_net_wm_icon_name ()
{
	Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after;
	char *string;
	char *ret=NULL;

	int retval=XGetWindowProperty (dpy, awin, _XA_NET_WM_ICON_NAME, 0L, 0xffffff, False, UTF8_STRING,
		  &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **) &string);
	
	if (retval==Success && string) {
		ret = strndup(string, nitems);
		has_net_wm_icon_name = 1;
		XFree(string);
	};

	return ret;
}

char *
SWindow::get_wm_icon_name ()
{
	// XGetIconName Wrapper
	// copied from WindowMaker
	XTextProperty text_prop;
	char **list;
	char *ret=NULL;
	int num;

	if (XGetWMIconName (dpy, awin, &text_prop)  && text_prop.value  && text_prop.nitems > 0) {
		if (text_prop.encoding == XA_STRING) {
			ret = strdup((char *) text_prop.value);
			XFree (text_prop.value);      
		}
		else {
			text_prop.nitems = strlen ((char *) text_prop.value);
			if (XmbTextPropertyToTextList(dpy, &text_prop, &list, &num) >=Success && num>0 && *list) {
				XFree (text_prop.value);
				ret = strdup (*list);
				XFreeStringList (list);
			}
			else {
				ret = strdup((char *) text_prop.value);
				XFree (text_prop.value);
			};
		};
	}
	return ret;
}

/*
int
SWindow::get_command ()
{
	XTextProperty text_prop;
	char **list;
	int num;

	if (XGetTextProperty (dpy, awin, &text_prop, _XA_WM_COMMAND))
	{
		if (text_prop.value && text_prop.nitems > 0)
		{
			if (text_prop.encoding == XA_STRING)
			{
	start_cmd = strdup ((char *) text_prop.value);
	XFree (text_prop.value);
			}
			else
			{
	text_prop.nitems = strlen ((char *) text_prop.value);
	if (XmbTextPropertyToTextList (dpy, &text_prop, &list, &num) >= Success
			&& num > 0 && *list)
	{
		XFree (text_prop.value);
		start_cmd = strdup (*list);
		XFreeStringList (list);
	}
	else
	{
		start_cmd = strdup ((char *) text_prop.value);
		XFree (text_prop.value);
	};
			};
		}
		else
		{
			// the title was set to none
			start_cmd = strdup ("");
		};
	}
	else
	{
		// the hint is probably not set
		start_cmd = NULL;
	};
};
*/

int
SWindow::get_wm_protocols ()
{
	// finds out which protocols the window supports
	Atom *protocols = NULL, *ap;
	int i, n;

	// First, try the Xlib function to read the protocols.
	// This is what Twm uses.
	if (XGetWMProtocols (dpy, awin, &protocols, &n)) {
		for (i = 0, ap = protocols; i < n; i++, ap++) {
			if (*ap == _XA_WM_TAKE_FOCUS) flags |= DoesWmTakeFocus;
			else if (*ap == _XA_WM_DELETE_WINDOW) flags |= CLOSEABLE;
		};

		if (protocols)
			XFree ((char *) protocols);
	}
	else {
		Atom atype;
		int aformat;
		unsigned long bytes_remain, nitems;
		// next, read it the hard way. mosaic from Coreldraw needs to 
		// be read in this way.
		if (Success == XGetWindowProperty (dpy, awin, _XA_WM_PROTOCOLS, 0L, 10L, FALSE,
			 _XA_WM_PROTOCOLS, &atype, &aformat, &nitems, &bytes_remain, (unsigned char **) &protocols))
		{
			for (i = 0, ap = protocols; i < nitems; i++, ap++) {
				if (*ap == _XA_WM_TAKE_FOCUS) flags |= DoesWmTakeFocus;
				else if (*ap == _XA_WM_DELETE_WINDOW) flags |= CLOSEABLE;
			};
			
			if (protocols) XFree ((char *) protocols);
		};
	};
}

int
SWindow::get_size_hints ()
{
	// gets application supplied size info
	long supplied = 0;

	if (!XGetWMNormalHints (dpy, awin, &hints, &supplied))
		hints.flags = 0;

	// Beat up our copy of the hints, so that all important field are
	// filled in!
	if (hints.flags & PResizeInc) {
		if (hints.width_inc == 0) hints.width_inc = 1;
		if (hints.height_inc == 0) hints.height_inc = 1;
	}
	else {
		hints.width_inc = 1;
		hints.height_inc = 1;
	};

	// ICCCM says that PMinSize is the default if no PBaseSize is given,
	// and vice-versa

	if (!(hints.flags & PBaseSize)) {
		if (hints.flags & PMinSize) {
			hints.base_width = hints.min_width;
			hints.base_height = hints.min_height;
		}
		else {
			hints.base_width = 0;
			hints.base_height = 0;
		};
	};

	if (!(hints.flags & PMinSize)) {
		hints.min_width = hints.base_width;
		hints.min_height = hints.base_height;
	};

#define MAX_WINDOW_WIDTH 32767
#define MAX_WINDOW_HEIGHT 32767

	if (!(hints.flags & PMaxSize)) {
		hints.max_width = MAX_WINDOW_WIDTH;
		hints.max_height = MAX_WINDOW_HEIGHT;
	};

	if(hints.max_width < hints.min_width) hints.max_width = MAX_WINDOW_WIDTH;    
	if(hints.max_height < hints.min_height) hints.max_height = MAX_WINDOW_HEIGHT;    

	// zero width/height windows are bad news!
	if(hints.min_height <= 0) hints.min_height = 1;
	if(hints.min_width <= 0) hints.min_width = 1;
	
	if (!(hints.flags & PWinGravity)) {
		hints.win_gravity = NorthWestGravity;
		hints.flags |= PWinGravity;
	};
}

/*
struct DTitle: HText
{
	DTitle (char *&, MyFont *);
	handle_event ();
};

int
DTitle::DTitle (char *&itext, MyFont *ifont)
{
	HText (itext, ifont);
	//  text = &itext; // strcpy?
	//  font = ifont;
};

int
DTitle::handle_event ()
{
	if (event->x.type == ButtonPress)
	{
		event->context = C_DECOR;
		bl.handle_ButtonPress ();
	};
};
*/

AWin::AWin (Box *copy_from)
{
	x = copy_from->x;
	y = copy_from->y;
	wd = copy_from->wd;
	ht = copy_from->ht;
}

int
SWindow::a_get ()
{
	// stores the actual geometry of a window to a newly created a
	XWindowAttributes wat;
	XGetWindowAttributes (dpy, awin, &wat);
	
	abw = wat.border_width;

	a = new AWin;
	a->x = wat.x;
	a->y = wat.y;
	a->wd = wat.width + 2*abw;
	a->ht = wat.height + 2*abw;
}

int
SWindow::query_max_flag ()
{
	// checks whether we can maximize
	if (flags & TRANSIENT) {
		flags &= ~MAXIMIZABLE;
		return 0;
	}/*
	else if (decor_build (WPM_max)) {
		flags &= ~MAXIMIZABLE;
	}*/
	else
		flags |= MAXIMIZABLE;
}

int
SWindow::decor_build (int des_pmode, int respect_desk)
{
// des_pmode = desired placement mode  this->pmode = current placement mode

// "build decorations around this->a and align this->a"

// this procedure changes:
// this->a - 'bare' [a]pp window coordinates (a.wd, a.ht are changed in box->ia() and box->fa())
// this->dwin->hbox - decorations (that contains this->a - both visually and in memory (as a box))
// pmode

// this->dwin->wr - decoration window coordinates
// this->dwin->wr.x,.y is used as input in free mode; they are modifided in bounded modes

// this procedure does not affect this->w (server-side) properties

// couple of (intermediary) VBoxes/HBoxes are used in the process

// box->aadd - adds single box; box->add - add a NULL terminated list of boxes

	int build_mode = (des_pmode == WPM_current ? pmode : des_pmode);
	
	HBox *hbox;
	AWin *a_new;

	VBox *vbox = new VBox;
	vbox->xs = 10;

	// titlebar
	if (flags & DECORATIONS && flags & TITLE) {
		HBox *tit = new HCenter;
		// window name
		if (build_mode == WPM_max) tit->aadd (new HSkip (pad_wd));
		tit->aadd (new HText (name));
		// buttons
		if (flags & HIDE_BUTTON)	tit->add (new HSkip (pad_wd), new HideButton, NULL);
		// ??? should be shrinkable, but not stretchable
		if (flags & MAXIMIZABLE)	tit->add (new HSkip (pad_wd), new MaxButton, NULL);
		if (flags & CLOSEABLE)		tit->add (new HSkip (pad_wd), new CloseButton, NULL);
		
		vbox->aadd (tit);
		if (pad_wd) vbox->aadd (new VSkip (pad_wd));
		//      vbox->aadd (new HRule (1, ws.titleGC));
	};
		
	//xxx
	// WPM = windows placement mode
	if (build_mode == WPM_free) {
		// free mode

		if (pmode != WPM_free) {
			// transition to WPM_free from bounded mode ("unmaximization")
			pmode = WPM_free;
			a_new = a_saved;
		}
		else {
			// staying in WPM_free
			a_new = new AWin(a); // copy a to a_new
			a_new->x += dwin->wr.x;
			a_new->y += dwin->wr.y; // a_new->x, y now relative to root
		}

		vbox->aadd (a_new);
		if (flags & DECORATIONS && pad_wd) vbox->add_padding (pad_wd);
		hbox = new HBox; hbox->ys = 10;
		hbox->aadd (vbox);
		if (flags & DECORATIONS && border_wd) hbox->add_border (border_wd, dwin_style.borderGC);

		delete dwin->hbox; // deletes also original a
		dwin->hbox = hbox; a = a_new;
		// [1] "want 'a' to determine the position" ("building decors around it")
		dwin->wr.x = a->x; dwin->wr.y = a->y;
		dwin->align ();
		// align() changes a->x, a->y, discard old ones
		// "'dwin->wr.x,y' determining, 'a.x,y' determined"
		// a->wd, a->ht causes dwin to grow 
		// after align(): a->x, a->y are related to the upper left corner of dwin
		// to fulfil [1], the following is needed - typically it can be read as
		// "minus padding and border" for x, "minus title, padding and border" for y
		dwin->wr.x -= a->x; dwin->wr.y -= a->y;
	}
	else {
		// bounded placement modes
		a_new = new AWin(a);

		vbox->aadd (a_new);
		hbox = new HBox; hbox->aadd (vbox);
	
		if (des_pmode == WPM_current) {
			// center a_new, retaining its size - why VFil? consider xterm or other
			// 'granular' type of window (constrain_size())

			// these fills don't work right
			a_new->xs = a_new->ys = 0;
			vbox = new VBox; vbox->add (new VFil, hbox, new VFil, NULL);
			hbox = new HBox; hbox->add (new HFil, vbox, new HFil, NULL);
		}
		else {
			// grow a_new to maximum
			hbox->ys = 10;
			a_new->xs = a_new->ys = 10;
			if (des_pmode != WPM_max) hbox->add_padding (pad_wd);
		}

	//	hbox->add_padding (pad_wd);
		hbox->add_border (border_wd, dwin_style.borderGC);
// \hbox{\vrule\vbox{\hrule\hbox{\hfil\vbox{\vfil\hbox{\vbox{[title] a_new}}\vfil}\hfil}\hrule}\vrule}
// \hbox{\hskip\vbox{\vskip\hbox{\vbox{[title] a_new}}\vskip}\hskip}

		hbox->ia ();
		// now impose size on hbox and position on dwin->wr
		int nx, ny;
		switch (build_mode) {
		case WPM_max:     nx = 0; ny = 0; hbox->wd = root.wd; hbox->ht = root.ht; break;
		case WPM_half_l:  nx = 0; ny = 0; hbox->wd = root.wd/2; hbox->ht = root.ht; break;
		case WPM_half_r:  nx = root.wd/2; ny = 0; hbox->wd = root.wd - root.wd/2; hbox->ht = root.ht; break;
		case WPM_quad_tl: nx = 0; ny = 0; hbox->wd = root.wd/2; hbox->ht = root.ht/2; break;
		case WPM_quad_tr: nx = root.wd/2; ny = 0; hbox->wd = root.wd/2; hbox->ht = root.ht/2; break;
		case WPM_quad_bl: nx = 0; ny = root.ht/2; hbox->wd = root.wd - root.wd/2; hbox->ht = root.ht - root.ht/2; break;
		case WPM_quad_br: nx = root.wd/2; ny = root.ht/2; hbox->wd = root.wd - root.wd/2; hbox->ht = root.ht - root.ht/2; break;
		}
		// why root.wd - root.wd/2 instead of root.wd/2? consider non-even root.wd (i.e. when it is odd)
		// ok it is a rather odd occurence :)
		hbox->fa ();

		if (des_pmode == WPM_current) {
			if (respect_desk == true) {
				// transition to special on startup
				nx += (a->x / root.wd) * root.wd;
				ny += (a->y / root.ht) * root.ht;
			};

			delete dwin->hbox; // deletes also old a
			dwin->hbox = hbox; a = a_new;
			dwin->wr.x = nx; dwin->wr.y = ny;
			dwin->wr.wd = dwin->hbox->wd; dwin->wr.ht = dwin->hbox->ht;
		}
		else {
			int new_wd = a_new->wd - 2*abw;
			int new_ht = a_new->ht - 2*abw;
				
			// checks whether the imposed size is in accordance to hints
			if (	new_wd < hints.min_width || new_wd > hints.max_width ||
				new_ht < hints.min_height || new_ht > hints.max_height	) {
				// unsuccessful transition to special mode impossible, cancel it
				delete hbox; // a_new gets deleted too
				return 0;
			}
			else {
				// transition to special mode possible
				if (pmode == WPM_free) {
					a_saved = new AWin(a);
					a_saved->x += dwin->wr.x;
					a_saved->y += dwin->wr.y;
					// a_saved contains coordinates relative to root
				}
			
				delete dwin->hbox; // deletes also old a
				dwin->hbox = hbox; a = a_new;
				constrain_size (a);
				pmode = des_pmode;

				decor_build (WPM_current); // = center 'a' (within surely constructable 'dwin')
				return 1;
			};
		}
	}
}

int
SWindow::max_toggle ()
{
	if (awin == pager.w)
		return 0;
		
	if (pmode != WPM_free) {
		// unspecialize
		decor_build (WPM_free);
		if (!(flags & DECORATIONS)) XUnmapWindow (dpy, dwin->w);
		sync_to_xserver ();
		save_hints ();
	}
	else if (decor_build (WPM_max)) {
		// maximization was succesful
		sync_to_xserver ();
		save_hints ();
		XMapWindow (dpy, dwin->w);
		// necessary for windows that doesn't have decorations in unmaximized state
	}
}

int
SWindow::style_setup (char *t)
{
	Style *style;
	for (style = style_list.first; style; style = style->next)
		if (style->match (t)) {
			flags = (flags & ~style->mask) | style->flags;
			if (style->flags & PADDING_WD) pad_wd = style->pad_wd;
			if (style->flags & BORDER_WD) border_wd = style->border;
		};
}

//********************************************************************

// Motif WM hints
#define MWM_HINTS_FUNCTIONS           (1L << 0)
#define MWM_HINTS_DECORATIONS         (1L << 1)

// bit definitions for MwmHints.functions
#define MWM_FUNC_ALL            (1L << 0)
#define MWM_FUNC_RESIZE         (1L << 1)
#define MWM_FUNC_MOVE           (1L << 2)
#define MWM_FUNC_MINIMIZE       (1L << 3)
#define MWM_FUNC_MAXIMIZE       (1L << 4)
#define MWM_FUNC_CLOSE          (1L << 5)

// bit definitions for MwmHints.decorations
#define MWM_DECOR_ALL                 (1L << 0)
#define MWM_DECOR_BORDER              (1L << 1)
#define MWM_DECOR_RESIZEH             (1L << 2)
#define MWM_DECOR_TITLE               (1L << 3)
#define MWM_DECOR_MENU                (1L << 4)
#define MWM_DECOR_MINIMIZE            (1L << 5)
#define MWM_DECOR_MAXIMIZE            (1L << 6)

int
SWindow::mwm_hints_get ()
{
	Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after;

	// Motif WM hints
	XGetWindowProperty (dpy, awin, _XA_MOTIF_WM_HINTS, 0L, sizeof (MWM_Hints), 0, AnyPropertyType,
		  &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **) &mwm_hints);

	if (actual_type == None) mwm_hints = NULL;
}

int
SWindow::mwm_hints_interpret ()
{
	// MWM hints
	if (mwm_hints) {
		//    printf ("MWM hints: %s\t%d\n", name, mwm_hints->flags);
		if (mwm_hints->flags & MWM_HINTS_DECORATIONS) {
			flags &= ~DECORATIONS;

			if (mwm_hints->decorations & MWM_DECOR_ALL)	flags |= DECORATIONS;
			if (mwm_hints->decorations & MWM_DECOR_BORDER)	flags |= DECORATIONS;
			if (mwm_hints->decorations & MWM_DECOR_TITLE)	flags |= TITLE;
		};
		
		if (mwm_hints->flags & MWM_HINTS_FUNCTIONS) {
			if (mwm_hints->functions & MWM_FUNC_MAXIMIZE)	flags |= MAXIMIZABLE; else flags &= ~MAXIMIZABLE;
			if (mwm_hints->functions & MWM_FUNC_MINIMIZE)	flags |= HIDE_BUTTON; else flags &= ~HIDE_BUTTON;

			/*
			if (mwm_hints->functions & MWM_FUNC_CLOSE) {
				// don't enable it, since it can be disabled from get_wm_protocols
				//	flags |= CLOSEABLE;
			}
			else
				flags &= ~CLOSEABLE;
			*/
		}
	}
}

int
SWindow::style_setup ()
{
	border_wd = pad_wd = 0;

	// priority: name > res_name > res_class
	style_setup (class_hint.res_class);
	style_setup (class_hint.res_name);
	style_setup (name);

	if (!(flags & DECORATIONS)) {
		flags &= ~TITLE;
		border_wd = pad_wd = 0;
	};

	query_max_flag ();
	flags |= HIDE_BUTTON;

	mwm_hints_interpret ();
}

int
SWindow::send_message (Atom a, Time timestamp)
{
	//  ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
	//  client messages will have the following form:
	//
	//     event type	ClientMessage
	//     message type	_XA_WM_PROTOCOLS
	//     window		w
	//     format		32
	//     data[0]	message atom
	//     data[1]	time stamp
	XClientMessageEvent ev;

	ev.type = ClientMessage;
	ev.window = awin;
	ev.message_type = _XA_WM_PROTOCOLS;
	ev.format = 32;
	ev.data.l[0] = a;
	ev.data.l[1] = timestamp;
	XSendEvent (dpy, awin, FALSE, 0L, (XEvent*) &ev);
}

int
SWindow::done ()
{
	// things to do when swm ends
	if (flags & HIDDEN)
		show (0);
/*	else if (flags & SPECIALLY_PLACED)
		max_toggle ();*/
}

int
SWindow::withdraw ()
{
	// aargh, the Withdrawn state ...
	//  Window w = event->x.xunmap.window;

	// transition to WithdrawnState
	XEvent xev;
	XSelectInput (dpy, awin, NoEventMask);
	set_map_state (WithdrawnState);
	bind_list.ungrab (awin);
	//  XDeleteProperty (dpy, awin, _XA_WM_STATE);

	// compatibility with obsolete clients
	// (they must not access the WM_STATE property)
	// see the footline at ICCCM
	//  XReparentWindow (dpy, awin, dwin->w, a->x, a->y);
	//  XReparentWindow (dpy, awin, root, a->x, a->y);
	XSync (dpy, 0);
}

SWindow::~SWindow ()
{
	// prepare message and anim (have to be here, name and dwin will be destroyed)
	char *msg; int msgr;
	if (swm_mode == MODE_RUN)
		msgr = asprintf (&msg, "closed %s", name);

	Rect rd = dwin->wr;

	// release space used to store window/icon names
	if (name != NoName) delete name;
	if (icon_name != NoName) delete icon_name;
	name = icon_name = NULL;

	// release hints
	if (wmhints)
		XFree ((char *) wmhints);

	if (mwm_hints)
		XFree ((char *) mwm_hints);

	if (class_hint.res_name && class_hint.res_name != NoName)
		XFree ((char *) class_hint.res_name);

	if (class_hint.res_class && class_hint.res_class != NoName)
		XFree ((char *) class_hint.res_class);

	// destroy window on pager
	XDestroyWindow (dpy, pager_view);

	// put off decorations
	delete dwin;

	//XSync (dpy, 0);

	if (swm_mode == MODE_RUN) {
		// disappearing animation
		if (flags & HIDDEN) {
			anim.from.start ();
			//      anim.to.end ();
			anim.to.set (rd);
			anim.type = ANIM_AD;
			anim.run ();

			anim.to.ppos ();
		}
		else {
			anim.from.set (rd);

			/*
			if (flags & TRANSIENT && wl.findw (&swindow, transient_for)) {
				anim.to.set (swindow->dwin->wr);
			}
			else
			{	
			*/
			//	anim.to.end ();
			anim.to.ppos ();
			//      };
		};
 
		anim.type = ANIM_AD;
		anim.run ();

		// window disappearing to right:
		// anim.to = f;
		// anim.to.x = root.wd + rd.wd;

		if (msgr>0) log_window.pr ("%s", msg);
		delete msg;
	}
}

int
SWindow::save_hints ()
{
	Atom atype;
	SWM_Hints swm_hints;
	swm_hints.pmode_saved = pmode;
	if (pmode != WPM_free) {
		// a_saved does not point to allocated AWin when in WPM_free
		swm_hints.x_saved = a_saved->x;
		swm_hints.y_saved = a_saved->y;
		swm_hints.wd_saved = a_saved->wd;
		swm_hints.ht_saved = a_saved->ht;
	};
	XChangeProperty (dpy, awin, _XA_SWM_HINTS, _XA_SWM_HINTS, 8, PropModeReplace, ((unsigned char *) &swm_hints), sizeof(SWM_Hints));
}

int
SWindow::sync_to_xserver ()
{
	// sync to Xserver
	// change the position and size of awin according to
	// dwin->wr.x,.y and a.wd,.ht
	XMoveResizeWindow (dpy, awin,
		dwin->wr.x + a->x, dwin->wr.y + a->y, a->wd - 2*abw, a->ht - 2*abw);
	//  printf ("update %9d %9d %9d %9d %5s\n",
	//	  dwin->x + a->x, dwin->y + a->y,
	//	  a->wd - 2*abw, a->ht - 2*abw, name);

	// we should send an event if the window was moved but not resized
	// ??? but all apps seems to work right without this

	XEvent xev;

	xev.type = ConfigureNotify;
	xev.xconfigure.display = dpy;
	xev.xconfigure.event = awin;
	xev.xconfigure.window = awin;
	xev.xconfigure.x = dwin->wr.x + a->x;
	xev.xconfigure.y = dwin->wr.y + a->y;
	xev.xconfigure.width = a->wd - 2*abw;
	xev.xconfigure.height = a->ht - 2*abw;
	xev.xconfigure.border_width = abw;
	xev.xconfigure.above = None; // root; // dwin;
	xev.xconfigure.override_redirect = FALSE;
	
	XSendEvent (dpy, awin, FALSE, StructureNotifyMask, &xev);

	XSync (dpy, 0);
	
	dwin->update ();
	//  dwin->pr ();
	pager_view_update ();
//	save_hints();
}

// xmms does a test = it detects how the WM treats the geometry of the
// windows (on startup, the small window which appears only for short
// time, one usually don't notice it); it then behaves (in one run)
// still the same (even if you change the window manager)

// try it: run swm first (on console, without WM), xmms will work
// fine, then kill swm (keep xmms running), run any bad WM (like fvwm,
// wmaker < 0.61, kwm from KDE <2.0) - it still works fine - now kill
// xmms, start it again (when bad WM is running) - it will work wrong
// - kill WM - it will still work wrong

int
SWindow::pager_view_update ()
{
	int x, y, wd, ht;

	x = ifrac (dwin->wr.x + dsk.x, dsk.wd, pager.wd);
	y = ifrac (dwin->wr.y + dsk.y, dsk.ht, pager.ht);

	// why -2 ? since pager_view has border_width = 1
	wd = ifrac (dwin->hbox->wd, dsk.wd, pager.wd) - 2;
	ht = ifrac (dwin->hbox->ht, dsk.ht, pager.ht) - 2;

	if (wd < 1) wd = 1;
	if (ht < 1) ht = 1;

	XMoveResizeWindow (dpy, pager_view, x, y, wd, ht);
}

int
word_delim (char c)
{
	const char *delims = " @/.,_{}();:-";
	for (int i=0; delims[i]; i++)
		if (c == delims[i])
			return 1;
	
	return 0;
}

#define PAGER_VIEW_OFFSET 1

int
SWindow::pager_view_one_line (int &line_end, int &line_begin, int wd)
{
	// get one line
	line_begin = line_end;

#ifdef FONTS_XFT
	do {
		// needs fixing - utf8
		if (!name[line_end]) return 0;
		line_end++;
	} while (XftTextWidthUtf8 (pager.font.font, name + line_begin, line_end - line_begin) <= wd - PAGER_VIEW_OFFSET);
	
#elif defined FONTS_XCORE
	do {
		if (!name[line_end]) return 0;
		line_end++;

		// go forward one word
		//do p++; while (name[p] && !word_delim (name[p-1]));
	
		// skip possible remaining delimiters
		//while (word_delim (name[p])) p++;
	} while (XTextWidth (pager.font.font, name + line_begin, line_end - line_begin) <= wd - PAGER_VIEW_OFFSET);
#endif
	return 1;
}

int
SWindow::pager_view_draw (int diff)
{
	int wd, ht;
	wd = ifrac (dwin->hbox->wd, dsk.wd, pager.wd) - 2*PAGER_VIEW_OFFSET;
	ht = ifrac (dwin->hbox->ht, dsk.ht, pager.ht) - 2*PAGER_VIEW_OFFSET;

	// draw the name of the window
	XClearWindow (dpy, pager_view);
	int line_begin;
	int line_end;

	// try to use as much space as possible - cut down unnecessarily
	// big pager_view_skip
	line_end=0;
	line_begin=0;
	int num_lines;
	for (num_lines=0; name[line_end]; num_lines++) pager_view_one_line (line_end, line_begin, wd);

	num_lines -= (ht / pager.font.ht);
	if (num_lines < 0) num_lines = 0;

	if (diff > 0) {
		pager_view_skip += diff;
		if (pager_view_skip > num_lines) pager_view_skip -= diff;
	}
	else if (diff < 0) {
		pager_view_skip += diff;
		if (pager_view_skip > num_lines) pager_view_skip = num_lines - diff;
		if (pager_view_skip < 0) pager_view_skip = 0;
	};

	int real_skip = pager_view_skip;
	if (real_skip > num_lines) real_skip = num_lines;

	// now draw it
	line_end = 0;
	line_begin = 0;

	for (int i=0; i<real_skip; i++) pager_view_one_line (line_end, line_begin, wd);
	
	int yt = 0;
	while (name[line_end]) {
		pager_view_one_line (line_end, line_begin, wd);
#ifdef FONTS_XFT
		XftDrawChange (xft_draw, pager_view);
		XftDrawStringUtf8 (xft_draw, &dwin_style.title.xftcolor, pager.font.font,
			PAGER_VIEW_OFFSET, yt+pager.font.y, (XftChar8*) name+line_begin, line_end-line_begin);
#elif defined FONTS_XCORE
		XDrawString (dpy, pager_view, pager.textGC,
			PAGER_VIEW_OFFSET, yt+pager.font.y, name+line_begin, line_end-line_begin);
#endif
		yt += pager.font.ht;
	}
}

int
SWindow::move (int dx, int dy)
{
	// move window by [dx, dy]
	// this is called from dsk.move

	if ((dx==0 && dy==0) || (flags & STICKY || flags & HIDDEN)) {
		// dsk.x or dsk.y updated, need to update small win in pager,
		// it will get mapped without updating in unstick
		pager_view_update ();
	}
	else {
		dwin->wr.x += dx;
		dwin->wr.y += dy;
		sync_to_xserver ();
	}
}

int
SWindow::move_to (int nx, int ny)
{
	if (nx == dwin->wr.x && ny == dwin->wr.y) return 0;

	dwin->wr.x = nx;
	dwin->wr.y = ny;

	sync_to_xserver ();
}

int
SWindow::close ()
{
	if (flags & CLOSEABLE)
		send_message (_XA_WM_DELETE_WINDOW, CurrentTime);
	else
		log_window.pr ("can't close %s", name);
}

int
SWindow::destroy ()
{
	if (awin == pager.w)
		return 0;

	XKillClient (dpy, awin);
	XSync (dpy, 0);
}

int
SWindow::center ()
{
	int x = dwin->wr.x + dwin->wr.wd/2;
	int y = dwin->wr.y + dwin->wr.ht/2;

	restrict (x, -dsk.x, -dsk.x + dsk.wd-1);
	restrict (y, -dsk.y, -dsk.y + dsk.ht-1);
	//  printf ("%d\t%d\n", ifrac (x, root.wd, 1), ifrac (y, root.ht, 1));

	int nx, ny;
	nx = (root.wd - dwin->wr.wd) / 2;
	ny = (root.ht - dwin->wr.ht) / 2;

	//  printf ("%s\n", name);
	//  return 0;
	if (dwin->wr.x < root.wd && dwin->wr.x + dwin->wr.wd >= 0
		&& dwin->wr.y < root.ht && dwin->wr.y + dwin->wr.ht >= 0)
	{
		// the window is in the selected screen (cell), so center to
		// the cell, in which it's center lies (if it wouldn't bring it
		// outside of the desktop)
		dwin->wr.x = nx;
		dwin->wr.y = ny;
	}
	else {
		dwin->wr.x = nx + root.wd * iifrac (x, root.wd);
		dwin->wr.y = ny + root.ht * iifrac (y, root.ht);
	}
	//    dwin->align ();
}

int
SWindow::constrain_size (Box *a)
{
	// Adjust width and height in [a] to account for the constraints
	// imposed by size hints. The general algorithm, especially the
	// aspect ratio stuff, is borrowed from uwm's CheckConsistency
	// routine.
#define makemult(a,b) ((b==1) ? (a) : (((int)((a)/(b))) * (b)) )
#define _min(a,b) (((a) < (b)) ? (a) : (b))
	int minwd, minht, maxwd, maxht;
	int xinc, yinc, delta;
	int basewd, baseht;
	int dwd = a->wd - 2*abw;
	int dht = a->ht - 2*abw;
	// dwd, dht - desired (demanded) wd, ht

	minwd = hints.min_width;
	minht = hints.min_height;

	maxwd = hints.max_width;
	maxht = hints.max_height;

	basewd = hints.base_width;
	baseht = hints.base_height;

	xinc = hints.width_inc;
	yinc = hints.height_inc;

	// first, clamp to min and max values
	if (dwd < minwd) dwd = minwd;
	if (dht < minht) dht = minht;
	if (dwd > maxwd) dwd = maxwd;
	if (dht > maxht) dht = maxht;

	// second, fit to base + N * inc
	dwd = ((dwd - basewd) / xinc * xinc) + basewd;
	dht = ((dht - baseht) / yinc * yinc) + baseht;

	// third, adjust for aspect ratio
#define maxAspectX (hints.max_aspect.x)
#define maxAspectY (hints.max_aspect.y)
#define minAspectX (hints.min_aspect.x)
#define minAspectY (hints.min_aspect.y)
	// the math looks like this:
	//
	// minAspectX    dwd     maxAspectX
	// ---------- <= ------- <= ----------
	// minAspectY    dht    maxAspectY
	//
	// If that is multiplied out, then the width and height are
	// invalid in the following situations:
	//
	// minAspectX * dht > minAspectY * dwd
	// maxAspectX * dht < maxAspectY * dwd

	if (hints.flags & PAspect) {
		if (minAspectX * dht > minAspectY * dwd) {
			delta = makemult (minAspectX * dht / minAspectY - dwd, xinc);
			
			if (dwd + delta <= maxwd)
				dwd += delta;
			else {
				delta = makemult (dht - dwd * minAspectY / minAspectX, yinc);
				if (dht - delta >= minht)
				dht -= delta;
			}
		}

		if (maxAspectX * dht < maxAspectY * dwd) {
			delta = makemult (dwd * maxAspectY / maxAspectX - dht, yinc);
			if (dht + delta <= maxht)
				dht += delta;
			else {
				delta = makemult (dwd - maxAspectX * dht / maxAspectY, xinc);
				if (dwd - delta >= minwd)
					dwd -= delta;
			}
		}
	}

	a->wd = dwd + 2*abw;
	a->ht = dht + 2*abw;
}

int
SWindow::raise ()
{
	win_list.raise (this);

	// transients too
	SWindow *i;
	for (i= win_list.first; i; i= i->next)
		if (i->flags & TRANSIENT && i->transient_for == awin)
			i->raise ();
}

int
SWindow::lower ()
{
	if (!(flags & KEEPONTOP)) {
		// transients too
		SWindow *i;
		for (i= win_list.first; i; i= i->next)
			if (i->flags & TRANSIENT && i->transient_for == awin)
				i->lower ();

		XLowerWindow (dpy, fake_win);
		XLowerWindow (dpy, dwin->w);
		Window wins[2];
		wins[0]= fake_win;
		wins[1]= awin;
		XRestackWindows (dpy, wins, 2);

		XLowerWindow (dpy, pager_view);
	}

	// message
	if (awin != pager.w) log_window.pr ("lowered %s", name);
}

int
SWindow::stick_on ()
{
	/*
		// print atom list
	{
		Atom *a;
		int n;
		a=XListProperties (dpy, awin, &n);
		printf ("\n\n");
		printf ("%s:\n", name);

		for (int i=1; i<n; i++)
			printf ("%s\n", XGetAtomName (dpy, a[i]));

		printf ("\n\n");
		
		return 1;
	};
	*/

	// stick on transients too
	SWindow *i;
	for (i=win_list.first; i; i= i->next)
		if (i->flags & TRANSIENT && i->transient_for == awin)
			i->stick_on ();

	XUnmapWindow (dpy, pager_view);
	flags |= STICKY;
}

int
SWindow::stick_off ()
{
	if (this == spager) return 0; // disable unsticking of pager

	// stick off transients too
	SWindow *i;
	for (i=win_list.first; i; i= i->next)
		if (i->flags & TRANSIENT && i->transient_for == awin)
			i->stick_off ();

	if (!(flags & HIDDEN))
		XMapWindow (dpy, pager_view);

	flags &= ~STICKY;
}

int
SWindow::stick_toggle ()
{
	if (flags & STICKY)
		stick_off ();
	else
		stick_on ();
}

int
SWindow::map_update ()
{
	if (!(flags & HIDDEN)) {
		XMapWindow (dpy, awin);
		if (flags & DECORATIONS) XMapWindow (dpy, dwin->w); else XUnmapWindow (dpy, dwin->w);
		if (!(flags & STICKY)) XMapWindow (dpy, pager_view);
	}
}

int
SWindow::show (int setstate)
{
	if (flags & HIDDEN) {
		// animation
		anim.from.start ();
		anim.to.set (dwin->wr);
		anim.type = ANIM_AD;
		anim.run ();

		flags &= ~(HIDDEN | HIDDEN_INDEPENDENTLY);

		map_update ();

		// not independently (=automatically) hidden transients too
		SWindow *i;
		for (i=win_list.first; i; i= i->next)
			if (i->flags & TRANSIENT  && i->transient_for == awin  && !(i->flags & HIDDEN_INDEPENDENTLY))
				i->show ();

		if (setstate)
			set_map_state (NormalState);

		if (flags & DoesWmTakeFocus)
			send_message (_XA_WM_TAKE_FOCUS, CurrentTime);
		
		XSync (dpy, 0);
		
		log_window.pr ("shown %s", name);
	}
}

int
SWindow::hide ()
{
	if (flags & HIDDEN) return 0;

	flags |= HIDDEN;

	// transients first
	SWindow *i;
	for (i=win_list.first; i; i= i->next)
		if (i->flags & TRANSIENT && i->transient_for == awin) i->hide ();

	if (flags & TRANSIENT && win_list.findw (&swindow, transient_for)) {
		// if "transient parent" (stored in swindow now) is not hidden,
		// mark window as independently hidden
		if (!(swindow->flags & HIDDEN))
			flags |= HIDDEN_INDEPENDENTLY;
	}

	XUnmapWindow (dpy, dwin->w);
	XUnmapWindow (dpy, pager_view);

	set_map_state (IconicState);

	XUnmapWindow (dpy, awin);
	unmap_events++;

	// animation
	anim.from.set (dwin->wr);
	/*
	if (flags & TRANSIENT && wl.findw (&swindow, transient_for))
		anim.to.set (swindow->dwin->wr);
	else	     
		anim.to.start ();
	*/
	anim.to.start ();
	
	//  anim.type = ANIM_A;
	anim.type = ANIM_AD;
	anim.run ();

	// message
	log_window.pr ("hidden %s", name);
	return 1;
}

int
SWindow::do_resize ()
{
	if (awin == pager.w) {
		log_window.pr ("can't resize %s %dx%d", name, wd_c (), ht_c ());
		return 0;
	};

	if (!grab_setup (root_window)) return 0;

	AWin *a_orig = new AWin (a);
	Rect orig = dwin->wr;
	a_orig->x += orig.x;
	a_orig->y += orig.y;

	int xmotion = 0, ymotion = 0;

	info_window.map ();
	show_size ();
	
	Outline *ol;
	if (!(swm_flags & SWM_OPAQUE_RESIZE)) {
		ol = new Outline (dwin->wr);
		ol->draw ();
	};

	int done = 0;
	while (!done)
	{
		// block until there is an interesting event
		event->mask (KeyPressMask | KeyReleaseMask | ButtonPressMask
		| ButtonReleaseMask | PointerMotionMask | EnterWindowMask | ExposureMask);

		switch (event->x.type)
		{
		case Expose:
			if (!(swm_flags & SWM_OPAQUE_RESIZE)) {
				ol->draw ();
				event->handle_Expose ();
				ol->draw ();
			}
			else {
				event->handle_Expose ();
			};
			break;
		
		case KeyPress:
			pointer_warping();
			switch (event->keysym)
			{
			case XK_Return:
			case XK_space:
				done = 1;
				break;
			case XK_Escape:
				if (~swm_flags & SWM_OPAQUE_RESIZE) {
					ol->draw ();
					delete ol;
				}

				dwin->wr = orig;
				a = a_orig;
				decor_build (); // deletes old "a"
				sync_to_xserver ();

				done = 1;
				break;
			};
			event->handled = 1;
			break;

		case ButtonPress:
			event->handled = 1;
			break;
		
		case ButtonRelease:
			delete a_orig;
			
			if (~swm_flags & SWM_OPAQUE_RESIZE) {
				ol->draw ();
				delete ol;
			};

			if (dwin->wr != orig) {
				sync_to_xserver ();

/*				if (flags & SPECIALLY_PLACED) {	  
					flags &= ~SPECIALLY_PLACED;
				}
*/
			}
			done = 1;
			event->handled = 1;
			break;

		case EnterNotify:
			//      printf ("yes!\n");
		case MotionNotify:
			int x, y;
			if (event->x.type == EnterNotify) {
				x = event->x.xcrossing.x_root;
				y = event->x.xcrossing.y_root;
			}
			else {
				x = event->x.xmotion.x_root;
				y = event->x.xmotion.y_root;
			};
			
			//      if (!x || !y) printf ("%d\t%d\n", x, y);

			if ((y <= orig.y) || (ymotion == 1 && y < orig.y + orig.ht - 1)) {
				// up
				a->ys = 10;
				dwin->ia ();
				dwin->hbox->ht = orig.y + orig.ht - y + hints.height_inc - 1;
				dwin->fa ();
				constrain_size (a);	
				a->ys = 0;
				dwin->ia (); dwin->fa ();
				dwin->wr.y = orig.y + orig.ht - dwin->hbox->ht;
				ymotion = 1;
			}
			else if ((y >= orig.y + orig.ht - 1) || (ymotion == -1 && y > orig.y)) {
				// down
				a->ys = 10;
				dwin->ia ();
				dwin->hbox->ht = y - orig.y + hints.height_inc;
				dwin->fa ();
				constrain_size (a);
				a->ys = 0;
				dwin->ia (); dwin->fa ();
				dwin->wr.y = orig.y;
				ymotion = -1;
			};

			if ((x <= orig.x) || (xmotion == 1 && x < orig.x + orig.wd - 1)) {
				// left
				a->xs = 10;
				dwin->ia ();
				dwin->hbox->wd = orig.wd + orig.x - x + hints.width_inc - 1;
				dwin->fa ();
				constrain_size (a);
				a->xs = 0;
				dwin->ia (); dwin->fa ();
				dwin->wr.x = orig.x + orig.wd - dwin->hbox->wd;
				xmotion = 1;
			}
			else if ((x >= orig.x + orig.wd - 1) || (xmotion == -1 && x > orig.x)) {
				// right
				a->xs = 10;
				dwin->ia ();
				dwin->hbox->wd = x - orig.x + hints.width_inc;
				dwin->fa ();
				constrain_size (a);
				a->xs = 0;
				dwin->ia (); dwin->fa ();
				dwin->wr.x = orig.x;
				xmotion = -1;
			};


			if (swm_flags & SWM_OPAQUE_RESIZE) {
				sync_to_xserver ();
				//	dwin->draw ();
			}
			else {
				ol->update (dwin->wr);
				//	dwin->draw ();
				//	pager_view_update ();
			};

			show_size ();
			event->handled = 1;
			break;
		default:
			break;
		};

		if (!event->handled)
			event->handle ();
	};

	info_window.unmap ();
	grab_cancel ();
	log_window.pr ("%dx%d %s", wd_c (), ht_c (), name);
}

int
SWindow::show_size ()
{
	info_window.pr ("%d x %d", wd_c (), ht_c ());
}

int
SWindow::wd_c ()
{
	int wd;

	wd = a->wd - 2*abw;

	//wd -= hints.base_width;
	wd /= hints.width_inc;
	
	return wd;
}

int
SWindow::ht_c ()
{
	int ht;

	ht = a->ht - 2*abw;

	//ht -= hints.base_height;
	ht /= hints.height_inc;
	
	return ht;
}

int
SWindow::stick (int &nx, int &ny, int ox, int oy, int m=1)
{
	//#define L_ZERO(x, r) ((x<0) && (x >= -r))
	//#define R_ZERO(x, r) ((x>0) && (x <= r))
#define L_ZERO(x, r) (absi(x) <= r)
#define R_ZERO(x, r) (absi(x) <= r)
	// assistant routine for movement; adjust nx, y so that the
	// movement is "sticky"
	int corr_x=0, corr_y=0;

	if (origin_stick > 0)
	{
		int inx, iny;
		int idx, idy; // difference from integer*root.wd,_ht displaced
		// original position
		inx = ox + root.wd * iifrac (nx - ox + root.wd/2, root.wd);
		iny = oy + root.ht * iifrac (ny - oy + root.ht/2, root.ht);
		/*
		inx = root.wd * iifrac (nx, root.wd);
		iny = root.ht * iifrac (ny, root.ht);
		*/

		idx = nx - inx;
		idy = ny - iny;

		//    log_window.pr ("%d %d", idx, idy);

		if (absi(idx) <= m*origin_stick) {
			//      log_window.pr ("dx=%d", idx);
			nx -= idx;
			corr_x = 1;
		}

		if (absi(idy) <= m*origin_stick) {
			//      log_window.pr ("dy=%d", idy);
			ny -= idy;
			corr_y = 1;
		}
	}

	if (border_stick > 0) {
		if (!corr_x) {
			if (L_ZERO (nx, m*border_stick)) nx=0; // left
			else if (R_ZERO(nx + dwin->hbox->wd - root.wd, m*border_stick)) // right
				nx = root.wd - dwin->hbox->wd;
		}
		
		if (!corr_y) {
			if (L_ZERO (ny, m*border_stick)) ny=0;// top
			else if (R_ZERO(ny + dwin->hbox->ht - root.ht, m*border_stick)) // bottom
				ny = root.ht - dwin->hbox->ht;
		}
	}
}

int
SWindow::do_move (int ol_drawn)
{
	if (!grab_setup (root_window)) return 0;

	ppos.save ();
	
	int orig_pmode = pmode;
	Rect orig_dwr; int has_free_orig_dwr=0;
	AWin orig_a(a);
	AWin orig_a_saved;

	if (orig_pmode == WPM_free) {
		orig_dwr = dwin->wr;
		has_free_orig_dwr = 1;
	}
	else {
		orig_a_saved.set (a_saved);
	};

	Rect synced_dwr = dwin->wr;

	// border position for gestures (maximization by dragging to top, halving by dragging to
	// left/right sides etc.)
	int left, right, top, bottom, redge, bedge;
	left = gest_stick; right = root.wd - left;
	top = gest_stick; bottom = root.ht - top;
	redge = root.wd-1; bedge = root.ht-1;

	//  info.map (); show_pos ();

	//log_window.dbg ("%dx%d %+d %+d", dwin->wr.wd, dwin->wr.ht, dwin->wr.x, dwin->wr.y);
 
	Outline *ol;
	if (!(swm_flags & SWM_OPAQUE_MOVE)) {
		ol = new Outline (dwin->wr);
		if (ol_drawn) ol->draw ();
	}

	int done = 0;
	while (!done)
	{
		// block until there is an interesting event
		event->mask (KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | ExposureMask);

		switch (event->x.type) {
		case Expose:
			if (!(swm_flags & SWM_OPAQUE_MOVE)) {
				if (ol_drawn) ol->draw ();
				event->handle_Expose ();
				if (ol_drawn) ol->draw ();
			}
			else 
				event->handle_Expose ();
			break;
		case KeyPress:
			pointer_warping();
			if (event->keysym == XK_Return || event->keysym == XK_space) {
				done = 1;
				event->handled = 1;
			}
			else if (event->keysym == XK_Escape) {
				if (orig_pmode == WPM_free) {
					if (pmode != WPM_free) {
						a_saved->x = orig_dwr.x + orig_a.x;
						a_saved->y = orig_dwr.y + orig_a.y;
						a_saved->wd = orig_a.wd; a_saved->ht = orig_a.ht;
						decor_build (WPM_free);
					}
					else {
						dwin->wr.x = orig_dwr.x;
						dwin->wr.y = orig_dwr.y;
						a->x = orig_a.x; a->y = orig_a.y;
						a->wd = orig_a.wd; a->ht = orig_a.ht;

						decor_build (WPM_current); // ==WPM_free
					}
				}
				else {
					decor_build (orig_pmode);
				}

				if (swm_flags & SWM_OPAQUE_MOVE) sync_to_xserver ();
				done = 1;
			};
			break;

		case ButtonPress: event->handled = 1; break;

		case ButtonRelease: done = 1; event->handled = 1; break;

		case EnterNotify:
		case MotionNotify:
			// get pointer coordinates (rel. to root)
			int x, y;
			
			if (event->x.type == EnterNotify) {
				x = event->x.xcrossing.x_root;
				y = event->x.xcrossing.y_root;
			}
			else {	
				x = event->x.xmotion.x_root;
				y = event->x.xmotion.y_root;
			};
			
			int pulled_x, pulled_y;
			pulled_x = x-ppos.sx;
			pulled_y = y-ppos.sy;

			int new_pmode;
			new_pmode = WPM_free;

			/**/ if (gest_stick < 1); // switch off gestures
			else if (x==0 && y<top || y==0 && x<left) new_pmode = WPM_quad_tl;
			else if (x==redge && y<top || y==0 && x>right) new_pmode = WPM_quad_tr; 
			else if (x==0 && y>bottom || y==bedge && x<left) new_pmode = WPM_quad_bl;
			else if (x==redge && y>bottom || y==bedge && x>right) new_pmode = WPM_quad_br;
			else if (y==0) new_pmode = WPM_max;
			else if (x==0) new_pmode = WPM_half_l;
			else if (x==root.wd-1) new_pmode = WPM_half_r;
			
			if (pmode != WPM_free) {
				if (new_pmode != WPM_free && new_pmode != pmode) {
					decor_build (new_pmode);
				}
				//xxx
				else {
					// transition to WPM_free
					Rect maxwr = dwin->wr;
					
					if (has_free_orig_dwr) {
						if (pmode == WPM_max && y>top
							|| pmode == WPM_half_l	&& x>left
							|| pmode == WPM_half_r	&& x<right
							|| pmode == WPM_quad_tl	&& (x>left || y>top)
							|| pmode == WPM_quad_tr	&& (x<right || y>top)
							|| pmode == WPM_quad_bl	&& (x>left || y<bottom)
							|| pmode == WPM_quad_br	&& (x<right || y<bottom))
						{
							decor_build (WPM_free);
							// dwin now unmaximized

							dwin->wr.x = x - (((double) (x-maxwr.x) / maxwr.wd) * dwin->wr.wd);
							dwin->wr.y = y - (((double) (y-maxwr.y) / maxwr.ht) * dwin->wr.ht);
						}
					}
					else {
						if (pulled_x > gest_stick || pulled_x < -gest_stick
							|| pulled_y > gest_stick || pulled_y < -gest_stick)
						{
							// initial "tearing off" of a window

							// hm hm "přehmátnutí" na okně zjevně - na
							// titulku - relativní polohu vzhledem k
							// this->a by to chtělo zvážit a nepočítat
							// to přes reál. číslo
							decor_build (WPM_free);
							
							orig_dwr.x = x - (((double) (x-maxwr.x) / maxwr.wd) * dwin->wr.wd);
							orig_dwr.y = y - (((double) (y-maxwr.y) / maxwr.ht) * dwin->wr.ht);
							ppos.sx = x;
							ppos.sy = y;
							has_free_orig_dwr = 1;

							dwin->wr.x = x - (((double) (x-maxwr.x) / maxwr.wd) * dwin->wr.wd);
							dwin->wr.y = y - (((double) (y-maxwr.y) / maxwr.ht) * dwin->wr.ht);
						}
					}

				}
			}
			else { // pmode == WPM_free
				if (new_pmode != WPM_free) {
					decor_build (new_pmode);
				}

				if (pmode == WPM_free) {
					int nx, ny;

					nx = orig_dwr.x + pulled_x;
					ny = orig_dwr.y + pulled_y;

					if (orig_pmode == WPM_free) stick (nx, ny, orig_dwr.x, orig_dwr.y, 1);

					dwin->wr.x = nx;
					dwin->wr.y = ny;

					//dwin->ia ();
					//dwin->fa ();
				}
			}
			
			// displaying above counted dwin, a
			if (swm_flags & SWM_OPAQUE_MOVE) {
				if (dwin->wr != synced_dwr) {
					sync_to_xserver ();
					//dwin->update;
					dwin->draw (); //??
					synced_dwr = dwin->wr;
				}
			}
			else {
				if (!ol_drawn && dwin->wr != orig_dwr) {
					// first (not explicit, pointer motion induced) drawing of outline
					ol_drawn = 1;
					if (!(swm_flags & SWM_OPAQUE_MOVE)) ol->draw ();
				}

				ol->update (dwin->wr);
			}

			//      show_pos ();

			event->handled = 1;
			break;

		default:
			break;
		} // <-- event.type switch

		//    if (!event->handled)
		//      event->handle ();
	} // <-- while (!done)
	
	if (!(swm_flags & SWM_OPAQUE_MOVE)) {
		// hide outline
		if (ol_drawn) ol->draw ();
		delete ol;
		sync_to_xserver ();
	}

	if (pmode != orig_pmode) {
		if (orig_pmode == WPM_free) {
			// saving for max_toggle
			a_saved->x = orig_dwr.x + orig_a.x;
			a_saved->y = orig_dwr.y + orig_a.y;
		}
		save_hints();
	}

	//  info.unmap ();
	show_pos ();
	grab_cancel ();
}

int
SWindow::show_pos ()
{
	log_window.pr ("%+-d%+-d %s", dwin->wr.x+a->x, dwin->wr.y+a->y, name);
}

int
SWindow::switch_to_desk ()
{
	int tx, ty;

	// switch to the screen, where is the center of the window
	tx = ((dsk.x + dwin->wr.x + dwin->hbox->wd/2) / root.wd) * root.wd;
	ty = ((dsk.y + dwin->wr.y + dwin->hbox->ht/2) / root.ht) * root.ht;

	// animation could be there - acc/decc ...

	dsk.viewport_to (tx, ty);
}

int
SWindow::switch_to ()
{
	if (flags & HIDDEN) {
		raise ();
		show ();
	}
	else {
		switch_to_desk ();

		// bring the windows moved out completely off the desktop back
		// this occurs after/ due to crash e.g.
/*
#define MIN_VISIBLE 20
		if (dwin->wr.x + dwin->wr.wd < MIN_VISIBLE)
			dwin->wr.x = MIN_VISIBLE - dwin->wr.wd;
		else if (dwin->wr.x > root.wd - MIN_VISIBLE)
			dwin->wr.x = root.wd - MIN_VISIBLE;

		if (dwin->wr.y + dwin->wr.ht < MIN_VISIBLE)
			dwin->wr.y = MIN_VISIBLE - dwin->wr.ht;
		else if (dwin->wr.y > root.ht - MIN_VISIBLE)
			dwin->wr.y = root.ht - MIN_VISIBLE;


*/
		if (!dwin->wr.overlap(0, 0, root.wd, root.ht)) cq.add (new WinCmd (this, CW_CENTER));
		sync_to_xserver ();
	}

	cq.add (new WinCmd (this, CW_FOCUS));
	//  ppos.warp_to (a->x + a->wd / 2, a->y + a->ht / 2);
}

int
SWindow::handle_ConfigureRequest ()
{
#define cre event->x.xconfigurerequest
	if (cre.value_mask & CWStackMode)
	{
		unsigned long mask;
		XWindowChanges xwc;

		mask = CWStackMode;
		xwc.stack_mode = cre.detail;
		if (cre.value_mask & CWSibling) {
			mask |= CWSibling;
			xwc.sibling = cre.above;
		}

		XConfigureWindow (dpy, awin, mask, &xwc);
		//    XSync (dpy, 0);

		// keep dwin below the window
		Window wins[2];
		wins[0] = awin;
		wins[1] = dwin->w;
		XRestackWindows (dpy, wins, 2);

		// keep active & ontops on top of all
		win_list.raise ();

		// restack pager_view
		mask = CWStackMode;
		xwc.stack_mode = cre.detail;
		
		if (cre.value_mask & CWSibling) {
			mask |= CWSibling;
			SWindow *swindow;
			win_list.findw (&swindow, cre.above);
			xwc.sibling = swindow->pager_view;
		}
		
		XConfigureWindow (dpy, pager_view, mask, &xwc);
	}

	if (cre.value_mask & CWBorderWidth)
	{
		int dbw = cre.border_width - abw;
		a->wd += 2*dbw;
		a->ht += 2*dbw;
		abw = cre.border_width;

		//      XWindowChanges xwc;
		//      xwc.border_width = cre.border_width;
		//      XConfigureWindow (dpy, cre.window, CWBorderWidth, &xwc);
	}

	if (pmode != WPM_free  &&  cre.value_mask & (CWBorderWidth | CWX | CWY | CWWidth | CWHeight)) {
		if (!decor_build())
			decor_build (WPM_free);
	}

	if (cre.value_mask & CWX) dwin->wr.x = cre.x - a->x;
	if (cre.value_mask & CWY) dwin->wr.y = cre.y - a->y;
	if (cre.value_mask & CWWidth) a->wd = cre.width + 2*abw;
	if (cre.value_mask & CWHeight) a->ht = cre.height + 2*abw;

	dwin->align ();
	sync_to_xserver ();
#undef cre
}

int
SWindow::decors_toggle ()
{
	if (flags & SPECIALLY_PLACED) return 0;
	if (flags & DECORATIONS) flags &= ~DECORATIONS; else flags |= DECORATIONS;

	decor_build ();
	sync_to_xserver ();
	
	// ??? if maximized with decorations, this should remove
	// decorations, change the background (of dwin->w) to black and redo
	// maximization (without any decorations)
}

int
SWindow::handle_PropertyNotify ()
{
//	log_window.dbg ("prop notify %s %s", name, XGetAtomName (dpy, event->x.xproperty.atom));

	// fix(?): handle XDeleteProperty (_XA_NET_WM_NAME) set has_net_wm_name to 0 hm
	// won't probably occur for real apps - once they support extended window manager hints (<=set NET_WM_NAME)
	// why would they want to delete it
	Atom atom = event->x.xproperty.atom;

	/**/ if (atom == _XA_NET_WM_NAME  ||  (!has_net_wm_name && atom == XA_WM_NAME)) {
		get_name ();
		// the window may now belong to another class than before
		style_setup (); 
		decor_build ();
		map_update ();
		sync_to_xserver ();
		dwin->cl ();
		dwin->draw (); // buttons need to be redrawn
		// at least title needs to be redrawn, but in fact the decorations
		// may have totally changed
		pager_view_draw ();
	}
	else if (atom == _XA_NET_WM_ICON_NAME  ||  (!has_net_wm_icon_name && atom == XA_WM_ICON_NAME)) {
		get_icon_name ();
	}
	else if (atom == XA_WM_HINTS) {
		if (wmhints) XFree ((char *) wmhints);
		wmhints = XGetWMHints (dpy, event->x.xany.window);
	}
	else if (atom == XA_WM_NORMAL_HINTS) {
		get_size_hints ();
		style_setup ();
		decor_build ();    
	}
	else if (atom == _XA_WM_STATE) {
		// ??? the clients should not change WM_STATE property ???
		// of course, but we receive our own changes also ;)
		//      log_window.pr ("wm_state");
	}
	else if (atom == _XA_WM_PROTOCOLS) {
		get_wm_protocols ();

		// the buttons may have changed, re-create decorations; run
		// e.g. xfontsel to try this (it changes WM_PROTOCOLS shortly
		// after startup
			
		//      if (flags & TITLE)
		//	dwin->title->cl ();
		dwin->cl ();

		decor_build ();

		sync_to_xserver ();
		dwin->draw (); // buttons need to be redrawn
	}
	else if (atom == _XA_WM_TAKE_FOCUS) {
		//      log_window.pr ("wm_take_focus");
		if (flags & DoesWmTakeFocus) send_message (_XA_WM_TAKE_FOCUS, event->x.xproperty.time);
	}
	else if (atom == _XA_MOTIF_WM_HINTS) {
		if (mwm_hints) XFree ((char *) mwm_hints);
		/*
		get_wm_protocols ();
		// for the "CLOSEABLE" flag, mwm_hints may have disabled it
		// before and enable it now, e.g. AcroRead transition to/from
		// fullscreen
		*/
		mwm_hints_get ();
		style_setup (); 

		decor_build ();
		map_update ();
		sync_to_xserver ();
		dwin->draw (); // buttons need to be redrawn
		// at least title needs to be redrawn, but in fact the decorations
		// may have totally changed
		pager_view_draw ();
	}
}

int
SWindow::set_map_state (int nstate)
{
	//  return 0;
	// tell applications which windows on the screen are top level
	// application windows, and which windows are the icon windows that
	// go with them
	unsigned long data[3]; // "suggested" by ICCCM version 1

	map_state = nstate;

	data[0] = (unsigned long) nstate;
	data[1] = (unsigned long) None;
	data[2] = (unsigned long) None;

	XChangeProperty (dpy, awin, _XA_WM_STATE, _XA_WM_STATE, 32,
			 PropModeReplace, (unsigned char *) data, 3);
}

//********************************************************************

WinList::WinList ()
{
	first = NULL;
}

long isIconicState;

struct MSwitch: MSelectable
{
	SWindow *to_win;

	MSwitch (char *, SWindow *);

	int fa ();
	int draw ();
	int action ();
};

int
MSwitch::fa ()
{
/*
	int twd = XTextWidth (menu_style.font.font, text, len);

	if (twd > wd) {
		// "eat middle of the text"
		len = strlen(text);
		int ll = len/2;
		int s = 0; // which side to eat - have to begin on right!!!
		// the left part can be one char shorter than the right (len/2 is rounded down)
		int rl = len - ll;
#define MIDDLE_TEXT "~"
		//#define MIDDLE_TEXT "..."
		int mlen = strlen (MIDDLE_TEXT);

		char *t = new char[len+mlen+1];

		memcpy (t, text, ll);
		memcpy (t+ll, &(MIDDLE_TEXT), mlen);
		memcpy (t+ll+mlen, text+ll, rl);
		*(t+len+mlen) = 0;
		delete text;
		text = t;
		len += mlen;

		char *c = text + ll;
		char *i;

		while (twd > wd && len > mlen) {
			// eat the middle
			if (s==1) {
			// eat left side
				c--;
				i = c;
				s = 0;
			}
			else {
			// eat right side
				i = c+mlen;
				s = 1;
			}

			while (*i) {
				*i = *(i+1);
				i++;
			}

			len = strlen (text);
			twd = 2*menu_text_padding + XTextWidth (menu_style.font.font, text, len);
		}
	}
*/
	MText::fa ();
}

MSwitch::MSwitch (char *itext, SWindow *ito_win)
{
	text = itext;
	parse_hotkeys ();
	len = strlen (text);

	to_win = ito_win;
}

int
MSwitch::draw ()
{
	MSelectable::draw ();

	// highlighting of windows on pager
	if (state) {
		// *** IMPROVE ME ***
		// XFillRectangle (dpy, t->pager_view, gc)
		// gc.function = GX_AND; gc.pixel = menu_style.hiback.pixel;
		XSetWindowBackground (dpy, to_win->pager_view, menu_style.hiback.pixel);
	}
	else {
		if (pager.win_bkg->pm)
			XSetWindowBackgroundPixmap (dpy, to_win->pager_view, pager.win_bkg->pm);
		else
			XSetWindowBackground (dpy, to_win->pager_view, dwin_style.frame.pixel);
	}

	to_win->pager_view_draw ();
}

int
MSwitch::action ()
{
	cq.add (new WinCmd (to_win, CW_SWITCH_TO));
	swm_mode = MODE_RUN;
}

int
HiddenWindowsMenu::build ()
{
	vbox = new VBox;
	hbox = new HBox;
	hbox->aadd (vbox);

	char *label;
	char scut = '0'; // current shortcut

	//  vbox->aadd (new MTitle ("Switch to"));
	//  vbox->aadd (new MHRule);

	SWindow *i;
	int n = 0;
	for (i = win_list.first; i; i = i->next) {
#define hidden (i->flags & HIDDEN)
#define shown !hidden
#define trans ((i->flags & TRANSIENT) && wl.findw (&swindow, i->transient_for))
#define skip (i->flags & WINLISTSKIP)
	
		if (hidden && 
			(i->flags & HIDDEN_INDEPENDENTLY  ||  i->flags & WINLISTSKIP  ||
			! (i->flags & TRANSIENT  &&  win_list.findw(&swindow, i->transient_for)  &&  swindow->flags & HIDDEN)))
		{
			// so it is possible to show/switch to 1. shown windows without
			// WINLISTSKIP flag 2. hidden (=iconified) windows, even with
			// the WINLISTSKIP flag 3. transients, which has been hid, but
			// their parent wasn't 4. transients, which parent has not been
			// found
			
			// Shortcuts: 1..9, then A..Z, then nothing (space)
			
			if (scut != ' ') scut++;
			
			if (scut == ('9' + 1)) scut = 'A';
			else if (scut == ('Z' + 1)) scut = ' ';

			char *mt; int r;
			r=asprintf (&mt, "&%c  %s", scut, i->icon_name);
			if (r>0) it_add (new MSwitch (mt, i));
			n++;
		}
#undef hidden
#undef shown
#undef trans
#undef skip
	}

	if (n==0) vbox->aadd (new MTitle ((char *) "<no hidden windows>"));

	vbox->ia ();
	if (vbox->wd > root.wd/2) vbox->wd = root.wd/2;
	vbox->fa ();

	hbox->add_border (menu_style.bw, menu_style.ruleGC);
}

int
SwitchToMenu::build ()
{
	vbox = new VBox;
	hbox = new HBox;
	hbox->aadd (vbox);

	char *label;
	char scut = '0'; // current shortcut

	//  vbox->aadd (new MTitle ("Switch to"));
	//  vbox->aadd (new MHRule);

	SWindow *i;
	int n = 0;
	for (i = win_list.first; i; i = i->next) {
#define fl (i->flags)
#define hidden (fl & HIDDEN)
#define shown !hidden
#define trans ((fl & TRANSIENT) && win_list.findw (&swindow, i->transient_for))
#define skip (fl & WINLISTSKIP)
	
		if ((hidden && (fl & HIDDEN_INDEPENDENTLY || fl & WINLISTSKIP))
			||
			(!(shown && (trans || skip)) &&
			 !(hidden && (fl & TRANSIENT 
			 	&&  win_list.findw (&swindow, i->transient_for)
			 	&&  swindow->flags & HIDDEN))))
		{
			// so it is possible to show/switch to 1. shown windows without
			// WINLISTSKIP flag 2. hidden (=iconified) windows, even with
			// the WINLISTSKIP flag 3. transients, which has been hid, but
			// their parent wasn't 4. transients, which parent has not been
			// found
			
			// Shortcuts: 1..9, then A..Z, then nothing (space)
			
			if (scut != ' ') scut++;
	
			if (scut == ('9' + 1)) scut = 'A';
			else if (scut == ('Z' + 1)) scut = ' ';

			char *mt; int r;

			if (i->flags & HIDDEN)
				r=asprintf (&mt, "&%c  (%s)", scut, i->icon_name);
			else if (i == win_list.focus)
				r=asprintf (&mt, "&%c* %s", scut, i->name);
			else
				r=asprintf (&mt, "&%c  %s", scut, i->name);

			if (r>0) it_add (new MSwitch (mt, i));
			n++;
		}

#undef fl
#undef hidden
#undef shown
#undef trans
#undef skip
	}

	if (n==0)
		vbox->aadd (new MTitle ((char *) "<empty>"));

	vbox->ia ();
	if (vbox->wd > root.wd/2) vbox->wd = root.wd/2;
	vbox->fa ();

	hbox->add_border (menu_style.bw, menu_style.ruleGC);
}

int
WinList::init ()
{
#ifdef DEBUG
	printf ("winlist.init start\n");
#endif
	NoClass.res_name = NoName;
	NoClass.res_class = NoName;

	// decorates all windows at start-up
#ifndef DEBUG
	XGrabServer (dpy);
#endif
	unsigned int nchildren;
	Window parent, *children;
	if (!XQueryTree (dpy, root_window, &junk.root, &parent, &children, &nchildren))
		return 0;

	// weed out icon windows
	int i, j;
	for (i=0; i<nchildren; i++) if (children[i]) {
		XWMHints *wmhintsp = XGetWMHints (dpy, children[i]);
		if (wmhintsp) {
			if (wmhintsp->flags & IconWindowHint)
				for (j = 0; j < nchildren; j++)
					if (children[j] == wmhintsp->icon_window) {
						children[j] = None;
						break;
					};

			XFree ((char *) wmhintsp);
		};
	};

	SWindow *nw;
	Window w;

	// map all of the non-override windows
	for (i=0; i<nchildren; i++) {
		w = children[i];
		if (w && should_manage (w) && !active_list.find (w)) {
			nw = new SWindow (w, 0);
			add (nw);
			if (isIconicState == IconicState) nw->hide ();
		}
	}

	if (nchildren > 0)
		XFree ((char *) children);

#ifndef DEBUG
	XUngrabServer (dpy);
#endif

	focus_find ();
#ifdef DEBUG
	printf ("winlist.init done\n");
#endif
}

int
WinList::seq_switch ()
{
	// ALT-tab switching as known from MS-Windows
	if (swm_mode != MODE_RUN) return 0;
	if (!grab_setup (root_window)) return 0;

	// pop up tasklist in the center of the screen
	menu->pop_up (MPOP_CENTER);
	if (spager) spager->raise ();
	
	// now select second in the list (or first, if there's only one
	// item, or none, if the list is empty)
	if (menu->it_last) {
		boxw = menu->w;
		menu->select (menu->it_last->it_next->it_next);
	}

	swm_mode = MODE_MENU;

	while (swm_mode == MODE_MENU) {
		event->mask (MENU_EVENT_MASK);

		menu->handle_event ();

		if (!event->handled) {
			switch (event->x.type)
			{
			case KeyPress:
				boxw = menu->w;
				switch (event->keysym)
				{
				case XK_Tab:
					if (menu->focus)
						menu->select (menu->focus->it_next);
					event->handled = 1;
					break;
				default:
					menu->handle_KeyPress ();
				};
			//      event->handled = 1;
				break;
			case KeyRelease:
				boxw = menu->w;
				switch (event->keysym) {
				case XK_Meta_L: case XK_Meta_R:
				case XK_Alt_L: case XK_Alt_R:		
				case XK_Super_L: case XK_Super_R:	// Super = winlogo key
					if (menu->focus) menu->focus->action ();
					swm_mode = MODE_RUN;
					break;
				}
				event->handled = 1;
				break;
			}
		}

		if (!event->handled) event->handle ();
	}

	menu->pop_down ();
	grab_cancel ();

	if (spager) spager->lower ();
}

int
WinList::focus_root ()
{
	focus = NULL;
	XSetInputFocus (dpy, root_window, RevertToPointerRoot, CurrentTime);
}

/*
int
WinList::handle_FocusIn ()
{
	//  log_window.pr ("focus_in");
	// *** FIXME ***
	;
};
*/

int
WinList::focus_find ()
{
	ppos.query ();
	findw (&focus, ppos.w) || find_dwin (&focus, ppos.w);

	if (focus == NULL) {
		// find the closest window on desk
		int cldsq;
		for (SWindow *i= first; i; i= i->next) {
			if (i != spager && i != log_swindow && i->dwin->wr.overlap(root)) {
				int dsq=i->dwin->wr.dsq(ppos.rx, ppos.ry);
				if (focus == NULL || dsq < cldsq) { // evaluation from left to right - order important!
					focus = i;
					cldsq = dsq;
				}
			}
		}
	}

	#ifdef DEBUG
	//log_window.dbg("ffind %s", (focus!=NULL ? focus->name : "<root>"));
	#endif
	// no windows on desk => focus root
	if (focus) focus->focus (); else focus_root ();
}

int
WinList::should_manage (Window w)
{
	// checks to see if we should add decorations to w
	XWindowAttributes at;
	if (!XGetWindowAttributes (dpy, w, &at))
		return 0;
			
	Atom atype;
	int aformat;
	unsigned long nitems, bytes_remain;
	unsigned char *prop;

	isIconicState = DontCareState;

	if (Success == 
		XGetWindowProperty (dpy, w, _XA_WM_STATE, 0L, 3L, FALSE, _XA_WM_STATE,
				&atype, &aformat, &nitems, &bytes_remain, &prop))
		if (prop) {
			isIconicState= *(long *) prop;
			XFree (prop);
		}

	// "no to override_redirects, yes to hidden (=iconified) or mapped windows"
	if (!at.override_redirect && (isIconicState == IconicState || at.map_state != IsUnmapped))
	// in other words "|| at.map_state == mapped"
		return 1;
	else
		return 0;
}

int
WinList::add (SWindow *tmp)
{
	//  pager->add (tmp); ?
	tmp->next = first;
	first = tmp;
}

int
WinList::rm (SWindow *tmp)
{
	if (tmp == first)
		first = tmp->next;
	else {
		SWindow *i;
		for (i=first; i->next; i=i->next)
			if (i->next == tmp) {
				i->next = tmp->next;
				break;
			}
	}

	delete tmp;

}

int
WinList::done ()
{
#ifdef DEBUG
	printf("win_list.done start\n");
#endif
	focus = NULL;

//	XGrabServer (dpy);

	while (first) {
		first->done ();
		rm (first);
	}

//	XUngrabServer (dpy);
#ifdef DEBUG
	printf("win_list.done done\n");
#endif
}

int
WinList::raise (SWindow *tmp)
{
	// "tmp == NULL" = just raise active and ontops
	int n = active_list.n;

	// add KEEPONTOPs
	for (SWindow *i = first; i; i=i->next) if (i->flags & KEEPONTOP) n += 2;
	
	// add tmp (if any and not KEEPONTOP)
	if (tmp && !(tmp->flags & KEEPONTOP)) n += 2;

	// fill in the array
	// order: active, ontops, tmp
	if (n) {
		Window *wins = new Window[n];
	
		int j = 0;

		for (ActiveArea *i = active_list.first; i; i=i->next) {
			wins[j] = i->w;
			j++;
		}

		for (SWindow *i = first; i; i=i->next) if (i->flags & KEEPONTOP) {
			wins[j] = i->awin;
			wins[j+1] = i->dwin->w;
			j += 2;
		}

		if (tmp && !(tmp->flags & KEEPONTOP)) {
			wins[j] = tmp->awin;
			wins[j+1] = tmp->dwin->w;
		}

		XRaiseWindow (dpy, wins[0]);
		XRestackWindows (dpy, wins, n);

		delete wins;
	}

	// note: not implemented yet: two ontops overlaping, you want
	// raise one - there should be an internal "ontop-stack"?

	// raising pager view
	n = 0;

	for (SWindow *i = first; i; i=i->next) if (i->flags & KEEPONTOP) n++;
	if (tmp && !(tmp->flags & KEEPONTOP)) n++;

	// order: active, ontops, tmp
	if (n) {
		Window *wins = new Window[n];
	
		int j = 0;

		for (SWindow *i = first; i; i=i->next)
			if (i->flags & KEEPONTOP) {
				wins[j] = i->pager_view;
				j++;
			}

		if (tmp && !(tmp->flags & KEEPONTOP))
			wins[j] = tmp->pager_view;
		
		XRaiseWindow (dpy, wins[0]);
		XRestackWindows (dpy, wins, n);

		delete wins;
	}

	XSync (dpy, 0);
}

int
WinList::handle_Expose (Window w)
{
	SWindow *swindow;

	if (find_dwin (&swindow, w))
		swindow->dwin->draw ();
	else if (find_pager_view (&swindow, w))
		swindow->pager_view_draw ();
}

int
WinList::findw (SWindow **i, Window w)
{
	for (*i = first; *i; *i = (*i)->next)
		if ((*i)->awin == w)
			return 1;

	//  i = NULL;
	return 0;
}

int
WinList::find_dwin (SWindow **i, Window w)
{
	for (*i = first; *i; *i = (*i)->next)
		if ((*i)->dwin->w == w)
			return 1;
		
	// i == NULL here
	return 0;
}

int
WinList::find_pager_view (SWindow **i, Window w)
{
	for (*i = first; *i; *i = (*i)->next)
		if ((*i)->pager_view == w)
			return 1;

	return 0;
}

// focus policy: * the focus is assigned to window if ((it is entered)
// or (it is mapped)) * if the focused window is destroyed, and has
// been a transient, than the focus is passed to his parent (but if
// the closing of the window causes the pointer to enter another
// window, than the focus is immediately passed to it); if it hasn't
// been a transient, the focuse is passed to the window the pointer is
// currently in

//********************************************************************

int
InfoWin::init ()
{
	char *lorem_ipsum=(char *) " +88888 x +88888 ";
#ifdef FONTS_XFT
	wd = XftTextWidthUtf8 (swm_font.font, lorem_ipsum, strlen(lorem_ipsum)) + 2*swm_font.ht;
#elif defined FONTS_XCORE
	wd = XTextWidth (swm_font.font, lorem_ipsum, strlen(lorem_ipsum)) + 2*swm_font.ht;
#endif

	ht = swm_font.ht + swm_font.ht/2;
	
	XSetWindowAttributes at;
	at.border_pixel = menu_style.rule.pixel;
	at.event_mask = EnterWindowMask;
	at.save_under = FALSE; //TRUE;
	vm = CWBorderPixel | CWEventMask | CWSaveUnder;
	if (dwin_style.bkg->pm) {
		vm |= CWBackPixmap;
		at.background_pixmap = dwin_style.bkg->pm;
	}
	else {
		vm |= CWBackPixel;
		at.background_pixel = dwin_style.frame.pixel;
	}
	
	w = XCreateWindow
		(dpy, root_window, (root.wd - wd)/2, (root.ht - ht)/2, wd, ht, 2,
		 CopyFromParent, InputOutput, CopyFromParent, vm, &at);
}

int
InfoWin::done ()
{
	XDestroyWindow (dpy, w);
}

int
InfoWin::map ()
{
	XMapRaised (dpy, w);
}

int
InfoWin::unmap ()
{
	XUnmapWindow (dpy, w);
}

int
InfoWin::pr (const char *tpl, ...)
{
	char *s;
	va_list ap;
	
	va_start (ap, tpl);
	if (vasprintf (&s, tpl, ap)) {
		#ifdef FONTS_XFT
		int xo = (wd - XftTextWidthUtf8 (swm_font.font, s, strlen (s))) / 2;
		int yo = (ht - swm_font.ht) / 2 + swm_font.y;
		XClearWindow (dpy, w);
		XftDrawChange (xft_draw, w);
		XftDrawStringUtf8 (xft_draw, &dwin_style.title.xftcolor, swm_font.font, xo, yo, (XftChar8*) s, strlen (s));
		#elif defined FONTS_XCORE
		int xo = (wd - XTextWidth (swm_font.font, s, strlen (s))) / 2;
		int yo = (ht - swm_font.ht) / 2 + swm_font.y;

		XClearWindow (dpy, w);
		XDrawString (dpy, w, ws.titleGC, xo, yo, s, strlen (s));
		#endif
	};
	va_end (ap);
}
