/* window.c - client window managing class
 * 
 *  WindowMaker window manager
 * 
 *  Copyright (c) 1997 Alfredo K. Kojima
 * 
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "wconfig.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "generic/wwmlib.h"
#include "WindowMaker.h"
#include "GNUstep.h"
#include "wcore.h"
#include "window.h"
#include "icon.h"
#include "properties.h"
#include "actions.h"
#include "client.h"
#include "resource.h"
#include "funcs.h"
#include "keybind.h"
#include "stacking.h"

/****** Global Variables ******/

extern WShortKey *wKeyBindings[WKBD_LAST];

/* modules */
extern int ModuleNo;

/* contexts */
extern XContext wWinContext;

/* cursors */
extern Cursor wCursor[WCUR_LAST];

/* protocol atoms */
extern Atom _XA_WM_DELETE_WINDOW;
extern Atom _XA_GNUSTEP_WM_MINIATURIZE_WINDOW;

extern Atom _XA_WINDOWMAKER_STATE;

extern WPreferences wPreferences;

extern WResource wResourceDefault;

extern Time LastTimestamp;


/***** Local Stuff *****/

/* local functions */
static FocusMode getFocusMode(WWindow *wwin);
static void putWindowBorders(WWindow *wwin, int width, int height, 
			     int *child_x, int *child_y);
static void grabKeys(WWindow *wwin);
static void setupGrabs(WWindow *wwin);
static void removeGrabs(WWindow *wwin);
static void setupResources(WWindow *wwin, WResource *resource);
static int getSavedState(Window window, WSavedState **state);


/* event handlers */

/* titlebar */
static void titlebarMouseDown(WObjDescriptor *desc, XEvent *event);
static void titlebarDblClick(WObjDescriptor *desc, XEvent *event);


static WMethodTable wWindowTitlebarMethods = {
    (WEventHandler)titlebarMouseDown,
    NULL,
    (WEventHandler)titlebarDblClick,
    NULL
};

/* resize bar */
static void resizebarMouseDown(WObjDescriptor *desc, XEvent *event);

static WMethodTable wWindowResizebarMethods = {
    (WEventHandler)resizebarMouseDown,
    NULL,
    NULL,
    NULL
};



/* frame window (during window grabs) */
static void frameMouseDown(WObjDescriptor *desc, XEvent *event);

static WMethodTable wWindowFrameMethods = {
    (WEventHandler)frameMouseDown,
    NULL,
    NULL,
    NULL
};


/* close button */
static void windowCloseClick(WObjDescriptor *desc, XEvent *event);
static void windowCloseDblClick(WObjDescriptor *desc, XEvent *event);

static WMethodTable wWindowCloseMethods;

/* iconify button */
static void windowIconifyClick(WObjDescriptor *desc, XEvent *event);

static WMethodTable wWindowIconifyMethods;


/*
 *----------------------------------------------------------------------
 * wWindowInitialize--
 * 	Initialize window "class".
 * 
 * Notes:
 * 	Method table initialization to user defined bindings is done 
 * here.
 *---------------------------------------------------------------------- 
 */
void
wWindowInitialize()
{
    memcpy(&wWindowCloseMethods, &wButtonMethods, sizeof(WMethodTable));
    wWindowCloseMethods.click = windowCloseClick;
    wWindowCloseMethods.dbl_click = windowCloseDblClick;    
    
    memcpy(&wWindowIconifyMethods, &wButtonMethods, sizeof(WMethodTable));
    wWindowIconifyMethods.click = windowIconifyClick;
}



WWindow *
wWindowFor(Window window)
{
    WObjDescriptor *desc;

    if (window==None)
      return NULL;

    if (XFindContext(dpy, window, wWinContext, (XPointer*)&desc)==XCNOENT)
      return NULL;

    if (desc->parent_type==WCLASS_WINDOW)
      return desc->parent;
    else
      return NULL;
}




WWindow *
wWindowCreate()
{
    WWindow *wwin;
    
    wwin = wmalloc(sizeof(WWindow));

    memset(wwin, 0, sizeof(WWindow));

    /* setup global decoration defaults */
    setupResources(wwin, &wResourceDefault);

    wwin->client_descriptor.methods = &wWindowFrameMethods;
    wwin->client_descriptor.parent = wwin;
    wwin->client_descriptor.self = wwin;
    wwin->client_descriptor.parent_type = WCLASS_WINDOW;
    wwin->client_descriptor.click_timestamp = 0;
    return wwin;
}


void
wWindowDestroy(WWindow *wwin)
{
    int i;

    if (wwin->title!=NULL)
      XFree(wwin->title);

    if (wwin->normal_hints)
      wfree(wwin->normal_hints);
    
    if (wwin->wm_hints)
      XFree(wwin->wm_hints);
    
    if (wwin->wm_instance)
      XFree(wwin->wm_instance);

    if (wwin->wm_class)
      XFree(wwin->wm_class);

    if (wwin->wm_gnustep_attr)
      XFree(wwin->wm_gnustep_attr);
    
    if (wwin->cmap_windows)
      XFree(wwin->cmap_windows);

    XDeleteContext(dpy, wwin->client_win, wWinContext);
    
    for (i=0; i<wwin->l_button_no; i++) {
	if (wwin->l_buttons[i]!=NULL) {	    
	    wButtonDestroy(wwin->l_buttons[i]);
	}	
    }    
    for (i=0; i<wwin->r_button_no; i++) {
	if (wwin->r_buttons[i]!=NULL) {
	    wButtonDestroy(wwin->r_buttons[i]);
	}
    }

    wTextureFreeData(wwin->screen_ptr->focused_texture,
		     &wwin->focused_texture_data);
    wTextureFreeData(wwin->screen_ptr->pfocused_texture,
		     &wwin->pfocused_texture_data);
    wTextureFreeData(wwin->screen_ptr->unfocused_texture,
		     &wwin->unfocused_texture_data);
    wTextureFreeData(wwin->screen_ptr->resizebar_texture,
		     &wwin->resizebar_texture_data);
    if (wwin->titlebar)
	wCoreDestroy(wwin->titlebar);

    if (wwin->resizebar)
   	wCoreDestroy(wwin->resizebar);    

    if (wwin->frame)
      wCoreDestroy(wwin->frame);

    if (wwin->icon) {
	RemoveFromStackList(wwin->icon->core);
	wIconDestroy(wwin->icon);
    }

    wfree(wwin);
}


/*
 *----------------------------------------------------------------
 * wManageWindow--
 * 	reparents the window and allocates a descriptor for it.
 * Window manager hints and other hints are fetched to configure
 * the window decoration attributes and others. User preferences
 * for the window are used if available, to configure window 
 * decorations and some behaviour.
 * 
 * Returns:
 * 	the new window descriptor
 * 
 * Side effects:
 * 	The window is reparented and appropriate notification
 * is done to the client. Input mask for the window is setup.
 * 	The window descriptor is also associated with various window 
 * contexts and inserted in the head of the window list.
 * Event handler contexts are associated for some objects 
 * (buttons, titlebar and resizebar)
 *---------------------------------------------------------------- 
 */
WWindow *
wManageWindow(WScreen *scr, Window window)
{
    WWindow *wwin;
    int px, py, width, height;
    XWindowAttributes wattribs;    
    XSetWindowAttributes attribs;
    WResource *resource;
    XEvent junk;
    int iconic = 0;
    WSavedState *wstate;
    extern WWindow *colormap_win;

    /* mutex. */
    XGrabServer(dpy);
    XSync(dpy, 0);
    /* make the window is still there */
    if (XCheckTypedWindowEvent(dpy, window, DestroyNotify, &junk)) {
	XUngrabServer(dpy);
	return NULL;
    }
    wwin = wWindowCreate();
#ifdef DEBUG
    printf("managing window %x\n", (unsigned)window);
#endif

#ifdef SHAPE
    {
	int junk;
	unsigned int ujunk;
	int b_shaped;
	
	XShapeSelectInput(dpy, window, ShapeNotifyMask);
	XShapeQueryExtents(dpy, window, &b_shaped, &junk, &junk, &ujunk, 
			   &ujunk, &junk, &junk, &junk, &ujunk, &ujunk);
	wwin->flags.shaped = b_shaped;
    }
#endif
    
    /* if we're restarting, restore saved state */
    if (!getSavedState(window, &wstate)) {
	wstate = NULL;
    } else {
	wwin->flags.shaded = wstate->shaded;
	wwin->flags.hidden = wstate->hidden;
    }

    /*
     *--------------------------------------------------
     * 
     * Get hints and other information in properties
     *
     *-------------------------------------------------- 
     */
    wwin->wm_hints = XGetWMHints(dpy, window);
    PropGetWMClass(window, &wwin->wm_class, &wwin->wm_instance);

    if (wwin->wm_class && strcmp(wwin->wm_class, "WindowMaker")==0) {
	wwin->flags.internal_window = 1;
    }
    
    /* setup descriptor */
    wwin->client_win = window;
    wwin->screen_ptr = scr;

    XGetWindowAttributes(dpy, window, &wattribs);
    wwin->old_border_width = wattribs.border_width;

    if (wwin->flags.internal_window)
      attribs.event_mask = CLIENT_EVENTS|ExposureMask;
    else
      attribs.event_mask = CLIENT_EVENTS;
    attribs.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask;
    attribs.save_under = False;
    XChangeWindowAttributes(dpy, window, CWEventMask|CWDontPropagate
			    |CWSaveUnder, &attribs);    
    XSetWindowBorderWidth(dpy, window, 0);

    /* fill in property/hint data */
    if (!MyXFetchName(dpy, window, &(wwin->title))) {
	wwin->title = NULL;
    }
    /* get hints from GNUstep app */
    if (!PropGetGNUstepWMAttr(window, &wwin->wm_gnustep_attr)) {
	wwin->wm_gnustep_attr=NULL;
    }

    if (!PropGetClientLeader(window, &wwin->client_leader)) {
	wwin->client_leader = None;
    } else {
	wwin->main_window = wwin->client_leader;
    }

    wwin->wm_hints = XGetWMHints(dpy, window);

    if (wwin->wm_hints)  {
	if ((wwin->wm_hints->flags&StateHint) 
	    && (wwin->wm_hints->initial_state == IconicState)) {
	    iconic = 1;
	    /* don't do iconify animation */
	    wwin->flags.skip_next_animation = 1;
	}
      
	if (wwin->wm_hints->flags & WindowGroupHint) {
	    wwin->group_id = wwin->wm_hints->window_group;
	    /* window_group has priority over CLIENT_LEADER */
	    wwin->main_window = wwin->group_id;
	} else {
	    wwin->group_id = None;
	}

	if (wwin->wm_hints->flags & UrgencyHint)
	  wwin->flags.urgent = 1;
    } else {
	wwin->group_id = None;
    }

    PropGetProtocols(window, &wwin->protocols);
    
    if (!XGetTransientForHint(dpy, window, &wwin->transient_for)) {
	wwin->transient_for = None;
    } else {
	if (wwin->transient_for==None || wwin->transient_for==window) {
	    wwin->transient_for = scr->root_win;
	} else {
	    WWindow *owner;
	    owner = wWindowFor(wwin->transient_for);
	    if (owner) {
		wwin->main_window = owner->main_window;
	    } else {
		wwin->main_window = None;
	    }
	}
    }

    /* guess the focus mode */
    wwin->focus_mode = getFocusMode(wwin);

    /* get geometry stuff */
    GetNormalHints(wwin, &wattribs, True);

    /* get colormap windows */
    GetColormapWindows(wwin);
    
    /*
     *--------------------------------------------------
     * 
     * Setup the decoration/window attributes and
     * geometry
     * 
     *--------------------------------------------------
     */

    /*
     * Decoration setting is done in this precedence (lower to higher)
     * - use global defaults (done in wWindowCreate())
     * - try to guess some settings
     * - use default in the resource database
     * - use GNUstep window attributes
     */

    if (wwin->protocols.DELETE_WINDOW)
      wwin->window_flags.broken_close = 0;
    else
      wwin->window_flags.broken_close = 1;

    /* transients can't be iconified or maximized */
    if (wwin->transient_for) {
	wwin->window_flags.miniaturizable=0;
#ifdef DEBUG
	printf("%x is transient for %x\n", (unsigned)window, 
	       (unsigned)wwin->transient_for);
#endif
    }

    /* if the window can't be resized, remove the resizebar */
    if (wwin->normal_hints->flags & (PMinSize|PMaxSize)
	&& (wwin->normal_hints->min_width==wwin->normal_hints->max_width)
	&& (wwin->normal_hints->min_height==wwin->normal_hints->max_height)) {
	wwin->window_flags.resizable=0;
    }

    /* get window attribute stuff from resource database */
    if ((resource=wResourceGet(wwin->title, wwin->wm_instance,
			       wwin->wm_class))) {
	setupResources(wwin, resource);	
    }
    
    /* set workspace on which the window starts */
    
    if (wstate) {
	wwin->workspace = wstate->workspace;
	if (wwin->workspace > scr->last_workspace) {
	    if (scr->last_workspace < MAX_WORKSPACES)
	      scr->last_workspace = wwin->workspace;
	    else
	      wwin->workspace = scr->last_workspace;
	}
    } else {
	wwin->workspace = scr->workspace;
    }

    /* set GNUstep window attributes */
    if (wwin->wm_gnustep_attr) {
	if (wwin->wm_gnustep_attr->flags & GSWindowStyleAttr) {
	    wwin->window_flags.titlebar =
	      ((wwin->wm_gnustep_attr->window_style & NSTitledWindowMask)?1:0);

	    wwin->window_flags.closable = 
	      ((wwin->wm_gnustep_attr->window_style & NSClosableWindowMask)?1:0);
	    
	    wwin->window_flags.miniaturizable = 
	      ((wwin->wm_gnustep_attr->window_style
		& NSMiniaturizableWindowMask)?1:0);

	    wwin->window_flags.resizable = 
	      ((wwin->wm_gnustep_attr->window_style 
		& NSResizableWindowMask)?1:0);
	} else {
	    /* setup the defaults */
	    wwin->window_flags.titlebar = 1;
	    wwin->window_flags.closable = 1;
	    wwin->window_flags.miniaturizable = 1;
	    wwin->window_flags.resizable = 1;
	}
	if (wwin->wm_gnustep_attr->flags & GSWindowLevelAttr) {
	    wwin->window_level = wwin->wm_gnustep_attr->window_level;
	} else {
	    /* setup defaults */
	    wwin->window_level = NSNormalWindowLevel;
	}
	
	if (wwin->wm_gnustep_attr->flags & GSExtraFlagsAttr) {
	    if (wwin->wm_gnustep_attr->extra_flags & GSClientResizeFlag) {
		wwin->window_flags.client_resizebar = 1;
	    }
	    if (wwin->wm_gnustep_attr->extra_flags & GSMenuWindowFlag) {
		wwin->window_flags.menu_window = 1;
	    }
	    if (wwin->wm_gnustep_attr->extra_flags & GSIconWindowFlag) {
		wwin->window_flags.icon_window = 1;
	    }
	    if (wwin->wm_gnustep_attr->extra_flags&GSFullKeyboardEventsFlag) {
		wwin->window_flags.ignore_keys = 1;
	    }
	    if (wwin->wm_gnustep_attr->extra_flags&GSSkipWindowListFlag) {
		wwin->window_flags.skip_window_list = 1;
	    }
	    if (wwin->window_flags.menu_window 
		|| wwin->window_flags.icon_window) {
		wwin->window_flags.focusable = 0;
	    }
	}
    } 

    /* setup window geometry */
    width = wwin->client.width;
    height = wwin->client.height;

    wWindowConstrainSize(wwin, &width, &height);
    
    wwin->client.width = width;
    wwin->client.height = height;
#ifdef DEBUG
    printf("Geometry %i,%i %ix%i\n",wwin->client.x, wwin->client.y,
	   width,height);
#endif
    /* do not ask for window placement if the window is
     * transient, during startup or if the window wants to
     * start iconic */
    if (!scr->flags.startup && wwin->transient_for==None 
	&& !(wwin->normal_hints->flags & (USPosition|PPosition))
	&& !wwin->flags.internal_window && !iconic) {
	PlaceWindow(wwin, &wwin->frame_x, &wwin->frame_y);
    } else {
	wwin->frame_x = wwin->client.x;
	wwin->frame_y = wwin->client.y;
    }
    
    /* 
     *--------------------------------------------------
     * 
     * Create frame, borders and do reparenting
     *
     *--------------------------------------------------
     */
    if (wwin->wm_gnustep_attr && 
	(wwin->wm_gnustep_attr->flags & GSWindowStyleAttr)
	&& wwin->wm_gnustep_attr->window_style==NSBorderlessWindowMask) {
	wwin->frame = wCoreCreateTopLevel(scr, wwin->frame_x, wwin->frame_y,
					width, height, 0, True);
    } else {
	wwin->frame = wCoreCreateTopLevel(scr, wwin->frame_x, wwin->frame_y,
					  width, height, FRAME_BORDER_WIDTH,
					  True);
    }

    /* create borders */
    putWindowBorders(wwin, width, height, &px, &py);
    wwin->client.x += px;
    wwin->client.y += py;
    XReparentWindow(dpy, wwin->client_win, wwin->frame->window, px, py);
    XResizeWindow(dpy, wwin->client_win, wwin->client.width, 
		  wwin->client.height);

    /*
     *--------------------------------------------------
     * 
     * Setup descriptors and save window to internal
     * lists
     *
     *-------------------------------------------------- 
     */

    if (wwin->main_window!=None) {
	wApplicationCreate(scr, wwin->main_window);
    }

    /* setup the frame descriptor */
    wwin->frame->descriptor.methods = &wWindowFrameMethods;
    wwin->frame->descriptor.parent = wwin;
    wwin->frame->descriptor.parent_type = WCLASS_WINDOW;

    /* do not save context for our windows, as they may have their own
     * contexts saved */
    if (!wwin->flags.internal_window) {
	XSaveContext(dpy, window, wWinContext, 
		     (XPointer)&wwin->client_descriptor);
	/* don't let windows go away if we die */
	XAddToSaveSet(dpy, window);
    } else {
	/* don't let our windows be shaded */
	wwin->window_flags.shadeable = 0;
    }

    XLowerWindow(dpy, window);
    XMapSubwindows(dpy, wwin->frame->window);
    if (!iconic && wwin->workspace == scr->workspace
/*	&& !wwin->window_flags.start_withdrawn */
	&& !(wwin->wm_hints && (wwin->wm_hints->flags & StateHint)
	     && wwin->wm_hints->initial_state == WithdrawnState)) {
	XMapWindow(dpy, wwin->frame->window);
	wwin->flags.mapped=1;
    }
    
    /* setup stacking descriptor */
    wwin->frame->stacking = wmalloc(sizeof(WStacking));
    wwin->frame->stacking->above = NULL;
    wwin->frame->stacking->under = NULL;
    if (wwin->window_level == NSNormalWindowLevel 
	&& wwin->window_flags.floating) {
	wwin->frame->stacking->window_level = NSFloatingWindowLevel;
    } else {
	wwin->frame->stacking->window_level = wwin->window_level;
    }

    if (wPreferences.on_top_transients && wwin->transient_for!=None
	&& wwin->transient_for!=scr->root_win) {
	WWindow *tmp;
	tmp = wWindowFor(wwin->transient_for);
	if (tmp)
	  wwin->frame->stacking->child_of = tmp->frame;
    } else {
	wwin->frame->stacking->child_of = NULL;
    }
    /* insert window in window stacking list on top of all other windows,
     * taking care of stacking precedence */
    AddToStackList(wwin->frame);

    if (!scr->focused_window) {
	/* first window on the list */
	wwin->next = NULL;
	wwin->prev = NULL;
	scr->focused_window = wwin;
    } else {
	WWindow *tmp;

	/* add window at beginning of focus window list */
	tmp = scr->focused_window;
	while (tmp->prev)
	  tmp = tmp->prev;
	tmp->prev = wwin;
	wwin->next = tmp;
	wwin->prev = NULL;
    }

    /*
     *--------------------------------------------------
     * 
     * Final preparations before window is ready to go
     * 
     *-------------------------------------------------- 
     */
    
    if (!iconic && wwin->workspace == scr->workspace) {
	if (wPreferences.focus_mode==WKF_CLICK)
	  wSetFocusTo(scr, wwin);
    }
    if (!wwin->flags.internal_window && !wwin->window_flags.ignore_mouse 
	&& !wwin->window_flags.focusable) {

	XGrabButton(dpy, Button1, Mod1Mask, wwin->client_win, True,
		    ButtonPressMask, GrabModeSync, GrabModeAsync, None,
		    wCursor[WCUR_ARROW]);
    }
    if (!wwin->window_flags.ignore_keys) {
	grabKeys(wwin);
    }

    /*
     * Prevent window withdrawal when getting the 
     * unmap notifies generated during reparenting
     */
    wwin->flags.mapped=0;
    XUngrabServer(dpy);
    XSync(dpy, 0);
    InstallWindowColormaps(colormap_win);
    

    UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_ADD);    
    /*
     *--------------------------------------------------
     *  Cleanup temporary stuff
     *--------------------------------------------------
     */
    if (wstate)
      XFree(wstate);
    
    return wwin;
}


/*
 *---------------------------------------------------------------------- 
 * wUnmanageWindow--
 * 	Removes the frame window from a window and destroys all data
 * related to it. The window will be reparented back to the root window
 * if restore is True.
 * 
 * Side effects:
 * 	Everything related to the window is destroyed and the window
 * is removed from the window lists. Focus is set to the previous on the
 * window list.
 *---------------------------------------------------------------------- 
 */
void
wUnmanageWindow(WWindow *wwin, int restore)
{
    WCoreWindow *frame = wwin->frame;
    WWindow *owner;

    XUnmapWindow(dpy, frame->window);
    XSync(dpy, 0);

    UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_REMOVE);

    /* reparent the window back to the root */
    if (restore)
      wClientRestore(wwin);

    if (wwin->transient_for!=wwin->screen_ptr->root_win) {
	owner = wWindowFor(wwin->transient_for);
	if (owner) {
	    owner->flags.semi_focused = 0;
	}
    }
#ifdef BUGGYSTACKING
    /* remove from the window stack list */
    if (frame->stacking->under)
      frame->stacking->under->stacking->above = frame->stacking->above;
    if (frame->stacking->above)
      frame->stacking->above->stacking->under = frame->stacking->under;
    else /* this was the first window on the list */
      wwin->screen_ptr->stacking_list = frame->stacking->under;
#else /* BUGGYSTACKING */
    RemoveFromStackList(frame);
#endif /* !BUGGYSTACKING */

    /* remove from window focus list */
    if (!wwin->prev && !wwin->next) {
	/* was the only window */
	wwin->screen_ptr->focused_window = NULL;
	wSetFocusTo(wwin->screen_ptr, NULL);
    } else {
	if (wwin->prev)
	  wwin->prev->next = wwin->next;
	if (wwin->next)
	  wwin->next->prev = wwin->prev;
	else {
	    wwin->screen_ptr->focused_window = wwin->prev;
	    wwin->screen_ptr->focused_window->next = NULL;

	    if (wPreferences.focus_mode==WKF_CLICK) {
		WWindow *tmp;

		tmp = wWindowFor(wwin->transient_for);
		if (!tmp || !tmp->flags.mapped) {
		    tmp = wwin->prev;
		    while (tmp) {
			if (tmp->window_flags.focusable
			    && (tmp->flags.mapped || tmp->flags.shaded))
			  break;
			tmp = tmp->prev;
		    }
		}
		wSetFocusTo(wwin->screen_ptr, tmp);
	    }
	}
    }
#ifdef DEBUG
    printf("destroying window %x frame %x\n", (unsigned)wwin->client_win,
	   (unsigned)frame->window);
#endif 
    wWindowDestroy(wwin);
    XSync(dpy, 0);
}


/*
 *---------------------------------------------------------------------- 
 * wWindowPaint--
 * 	Paints the requested parts of the window.
 * If repaint is true, the background will be remade from scratch.
 * 
 * Returns:
 * 	None
 * 
 * Side effects:
 * 	Texture may be regenerated.
 *---------------------------------------------------------------------- 
 */
void
wWindowPaint(WWindow *wwin, int sides, int repaint)
{
    WScreen *scr = wwin->screen_ptr;
    int x, i, title_width, titlelen;
    GC gc, light_gc, dim_gc;
    int just;
    char *title;
#ifndef NEWSTYLE
    int extra_width;
#endif

    if (wwin->flags.needs_full_repaint) {
	sides = BORDER_ALL;
	wwin->flags.needs_full_repaint=0;
	repaint = False;
    }
    if (wwin->window_flags.titlebar && (sides & BORDER_TOP)) {
	/* titlebar and buttons */
	if (wwin->flags.focused || wwin->window_flags.menu_window) {
	    wTexturePaint(scr->focused_texture, &wwin->focused_texture_data,
			  wwin->titlebar, repaint);
	    gc = scr->focused_text_gc; 
	} else if (wwin->flags.semi_focused) {
	    wTexturePaint(scr->pfocused_texture, &wwin->pfocused_texture_data,
			  wwin->titlebar, repaint);
	    gc = scr->pfocused_text_gc;
	} else {
	    wTexturePaint(scr->unfocused_texture, &wwin->unfocused_texture_data,
			  wwin->titlebar, repaint);
	    gc = scr->unfocused_text_gc;
	}

	just = wPreferences.title_justification;
	if (wwin->window_flags.menu_window) {
	    just = WTJ_LEFT;
	}

#ifdef NEWSTYLE
	if (!wwin->title)
	  title = ShrinkString(&scr->title_font, DEF_WINDOW_TITLE, 
			       wwin->titlebar->width-4);
	else
	  title = ShrinkString(&scr->title_font, wwin->title,
			       wwin->titlebar->width-4);
	titlelen = strlen(title);
	title_width = MyTextWidth(scr->title_font.font, title, titlelen);
	switch (just) {
	 case WTJ_LEFT:
	    x = 4;
	    break;

	 case WTJ_RIGHT:
	    x = wwin->titlebar->width - title_width - 4;
	    break;

	 case WTJ_CENTER:
	 default:
	    x = (wwin->titlebar->width - title_width)/2;
	    break;
	}
#else /* !NEWSTYLE */
	if (wwin->flags.buttons_dont_fit) {
	    extra_width = 4;
	} else {
	    extra_width = (wwin->r_button_no+wwin->l_button_no)
	      *(wwin->titlebar->height -7+3);
	}
	if (!wwin->title)
	  title = ShrinkString(&scr->title_font, DEF_WINDOW_TITLE, 
			       wwin->titlebar->width - extra_width);
	else
	  title = ShrinkString(&scr->title_font, wwin->title,
			       wwin->titlebar->width - extra_width);
	titlelen = strlen(title);
	title_width = MyTextWidth(scr->title_font.font, title, titlelen);
	switch (just) {
	 case WTJ_LEFT:
	    if (wwin->flags.buttons_dont_fit) /* titlebar is too narrow */
	      x = 2;
	    else
	      x = (wwin->l_button_no*(wwin->titlebar->height - 7+3)) + 10;
	    break;

	 case WTJ_RIGHT:
	    if (wwin->flags.buttons_dont_fit) /* titlebar is too narrow */
	      x = wwin->titlebar->width - title_width - 2;
	    else
	      x = wwin->titlebar->width - title_width
	      - (wwin->r_button_no*(wwin->titlebar->height - 7+3)) - 10;
	    break;

	 case WTJ_CENTER:
	 default:
	    x = (wwin->titlebar->width - title_width)/2;
	    break;
	}
#endif /* !NEWSTYLE */
	/* title text */
#ifndef I18N_MB
	XDrawString(dpy, wwin->titlebar->window, gc, x,
		    scr->title_font.y + 4, title, titlelen);
#else /* I18N_MB */
	XmbDrawString(dpy, wwin->titlebar->window, scr->title_font.font, 
		      gc, x, scr->title_font.y + 4, title, titlelen);
#endif /* I18N_MB */
	free(title);	
	for (i=0; i<wwin->l_button_no; i++) {
	    wButtonPaint(wwin->l_buttons[i], repaint);
	}
	for (i=0; i<wwin->r_button_no; i++) {
	    wButtonPaint(wwin->r_buttons[i], repaint);
	}	
    }
    
    /* resizing handle */
    if (wwin->window_flags.resizable && (sides & BORDER_BOTTOM)) {
	Window win;
	int w, h;
	int cw;

	w = wwin->resizebar->width;
	h = wwin->resizebar->height;
	cw = wwin->resizebar_corner_width;
	light_gc = scr->resizebar_texture->solid.light_gc;
	dim_gc = scr->resizebar_texture->solid.dim_gc;
	win = wwin->resizebar->window;

	wTexturePaint(scr->resizebar_texture, &wwin->resizebar_texture_data,
		      wwin->resizebar, repaint);

	XDrawLine(dpy, win, dim_gc, 0, 0, w, 0);
	XDrawLine(dpy, win, light_gc, 0, 1, w, 1);

	XDrawLine(dpy, win, dim_gc, cw, 2, cw, h);
	XDrawLine(dpy, win, light_gc, cw+1, 2, cw+1, h);

	XDrawLine(dpy, win, dim_gc, w-cw-2, 2, w-cw-2, h);
	XDrawLine(dpy, win, light_gc, w-cw-1, 2, w-cw-1, h);
    }
}

     
void
wWindowFocus(WWindow *wwin)
{
#ifdef NEWSTYLE    
    int i;
#endif

#ifdef NEWSTYLE
    if (wwin->titlebar) {	
	if (!wwin->flags.focused) {
	    for (i=0; i<wwin->l_button_no; i++) {
		wwin->l_buttons[i]->texture=wwin->screen_ptr->focused_texture;
		wwin->l_buttons[i]->image = wwin->l_buttons[i]->image1;
		wTextureFreeData(wwin->l_buttons[i]->texture,
				 &wwin->l_buttons[i]->texture_data);
	    }
	    for (i=0; i<wwin->r_button_no; i++) {
		wwin->r_buttons[i]->texture=wwin->screen_ptr->focused_texture;
		wwin->r_buttons[i]->image = wwin->r_buttons[i]->image1;
		wTextureFreeData(wwin->r_buttons[i]->texture,
				 &wwin->r_buttons[i]->texture_data);
	    }
	}
    }
#endif
    wwin->flags.focused=1;
    if (wPreferences.focus_mode==WKF_CLICK)
      removeGrabs(wwin);
    /* keep meta-click grabs */
    if (!wwin->window_flags.ignore_mouse) {
	XGrabButton(dpy, Button1, Mod1Mask, wwin->client_win, True,
		    ButtonPressMask, GrabModeSync, GrabModeAsync, None,
		    wCursor[WCUR_ARROW]);
    }
}

void
wWindowUnfocus(WWindow *wwin)
{
#ifdef NEWSTYLE    
    int i;
#endif

    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);
    
#ifdef NEWSTYLE	
    if (wwin->titlebar) {
	WTexture *texture;
	if (wwin->flags.semi_focused)
	  texture=wwin->screen_ptr->pfocused_texture;
	else
	  texture=wwin->screen_ptr->unfocused_texture;
	for (i=0; i<wwin->l_button_no; i++) {
	    wwin->l_buttons[i]->texture=texture;
	    if (wwin->flags.semi_focused)
	      wwin->l_buttons[i]->image = wwin->l_buttons[i]->image1;
	    else
	      wwin->l_buttons[i]->image = wwin->l_buttons[i]->image2;
	    wTextureFreeData(wwin->l_buttons[i]->texture,
			     &wwin->l_buttons[i]->texture_data);
	}
	for (i=0; i<wwin->r_button_no; i++) {
	    wwin->r_buttons[i]->texture=texture;
	    if (wwin->flags.semi_focused)
	      wwin->r_buttons[i]->image = wwin->r_buttons[i]->image1;
	    else
	      wwin->r_buttons[i]->image = wwin->r_buttons[i]->image2;
	    wTextureFreeData(wwin->r_buttons[i]->texture,
			     &wwin->r_buttons[i]->texture_data);
	}
    }
#endif
    if (wwin->transient_for!=None 
	&& wwin->transient_for!=wwin->screen_ptr->root_win) {
	WWindow *owner;
	owner = wWindowFor(wwin->transient_for);
	if (owner && owner->flags.semi_focused) {
	    owner->flags.semi_focused = 0;
	    if (owner->flags.mapped || owner->flags.shaded) {
#ifdef NEWSTYLE
		wWindowUnfocus(owner);
#endif
		wWindowPaint(owner, BORDER_ALL, False);
	    }
	}
    }
    wwin->flags.focused=0;
    if (!wwin->window_flags.ignore_mouse) {
	XUngrabButton(dpy, Button1, Mod1Mask, wwin->client_win);
    }
    if (wPreferences.focus_mode==WKF_CLICK)
      setupGrabs(wwin);
}


/*
 *----------------------------------------------------------------------
 * 
 * wWindowConstrainSize--
 * 	Constrains size for the client window, taking the maximal size,
 * window resize increments and other size hints into account.
 * 
 * Returns:
 * 	The closest size to what was given that the client window can 
 * have.
 * 
 *---------------------------------------------------------------------- 
 */
void
wWindowConstrainSize(WWindow *wwin, int *nwidth, int *nheight)
{
    XSizeHints *sizeh=wwin->normal_hints;
    int width = *nwidth;
    int height = *nheight;
    int winc = sizeh->width_inc;
    int hinc = sizeh->height_inc;
    int aspw = (sizeh->min_aspect.x + sizeh->max_aspect.x)/2;
    int asph = (sizeh->min_aspect.y + sizeh->max_aspect.y)/2;

    if (width < sizeh->min_width)
      width = sizeh->min_width;
    if (height < sizeh->min_height)
      height = sizeh->min_height;

    if (width > sizeh->max_width) 
      width = sizeh->max_width;
    if (height > sizeh->max_height) 
      height = sizeh->max_height;
    
    if (sizeh->flags & PAspect) {
	/* adjust min aspect ratio */
	if (width * sizeh->min_aspect.y > height * sizeh->min_aspect.x) {
	    if (aspw > asph) {
		height = (aspw/asph) * width;
		width = (asph/aspw) * height;
	    } else {
		width = (asph/aspw) * height;
		height = (aspw/asph) * width;
	    }
	}
	/* adjust max aspect ratio */
	if (width * sizeh->max_aspect.y < height * sizeh->max_aspect.x) {
	    if (aspw > asph) {
		height = (aspw/asph) * width;
		width = (asph/aspw) * height;
	    } else {
		width = (asph/aspw) * height;
		height = (aspw/asph) * width;
	    }
	}	
    }
    width = (((width - sizeh->base_width) / winc) * winc) + sizeh->base_width; 
    height = (((height - sizeh->base_height) / hinc) * hinc)
      + sizeh->base_height;
    
    *nwidth = width;
    *nheight = height;
}

#ifdef NEWSTYLE
/*
 * adjust titlebar size and buttons, if they fit.
 */
static void
adjustButtons(WWindow *wwin)
{
    int i;
    int x, w, bsize;

    w = wwin->frame->width;
    bsize = wwin->titlebar->height;

    if ((bsize * (wwin->l_button_no + wwin->r_button_no) > w/2)) {
	wwin->flags.buttons_dont_fit=1;
	wCoreConfigure(wwin->titlebar, 0, 0, wwin->frame->width, 
		       wwin->titlebar->height);
	/* hide buttons */
	XRaiseWindow(dpy, wwin->titlebar->window);
	return;
    }
    wwin->flags.buttons_dont_fit=0;
    x = w - bsize + 1;
    for (i=0; i<wwin->r_button_no; i++) {
	XMoveWindow(dpy, wwin->r_buttons[i]->core->window, x, 0);
	x -= bsize;
    }
    wCoreConfigure(wwin->titlebar, wwin->l_button_no*(bsize-1), 0,
		   w - (wwin->l_button_no+wwin->r_button_no)*(bsize-1), 
		   wwin->titlebar->height);
}
#else /* !NEWSTYLE */
static void
adjustButtons(WWindow *wwin)
{
    int i;
    int x, y, w, bsize;

    w = wwin->frame->width;
    wCoreConfigure(wwin->titlebar, 0, 0, w, wwin->titlebar->height);
    bsize = wwin->titlebar->height - 7;

    if ((bsize+3) * (wwin->l_button_no + wwin->r_button_no) > w/2) {
	if (!wwin->flags.buttons_dont_fit) {
	    wwin->flags.buttons_dont_fit=1;
	    XUnmapSubwindows(dpy, wwin->titlebar->window);
	}
	return;
    } else if (wwin->flags.buttons_dont_fit) {
	XMapSubwindows(dpy, wwin->titlebar->window);
    }
    wwin->flags.buttons_dont_fit=0;
    x = w - 3 - bsize;
    y = (wwin->titlebar->height - bsize)/2;
    for (i=0; i<wwin->r_button_no; i++) {
	XMoveWindow(dpy, wwin->r_buttons[i]->core->window, x, y);
	x -= (bsize + 3);
    }
}
#endif /* !NEWSTYLE */


void
wWindowChangeWorkspace(WWindow *wwin, int workspace)
{
    WScreen *scr = wwin->screen_ptr;
    
    if (workspace>scr->last_workspace || workspace < 0 
	|| workspace==wwin->workspace)
      return;

    if (workspace != scr->workspace) {
	/* Sent to other workspace. Unmap window */
	if ((wwin->flags.mapped||wwin->flags.shaded)
	    && !wwin->window_flags.omnipresent) {
	    XUnmapWindow(dpy, wwin->frame->window);
	    wwin->flags.mapped = 0;
	}
    } else {
	/* brought to current workspace. Map window */
	if (!wwin->flags.mapped && 
	    !(wwin->flags.miniaturized || wwin->flags.hidden)) {
	    XMapWindow(dpy, wwin->frame->window);
	    wwin->flags.mapped = 1;
	}
    }
    wwin->workspace = workspace;
    if (!wwin->window_flags.omnipresent)
      UpdateSwitchMenu(scr, wwin, ACTION_CHANGE_WORKSPACE);
}


/*
 *----------------------------------------------------------------------
 * wWindowConfigure--
 * 	Configures the frame, decorations and client window to the 
 * specified geometry. The geometry is not checked for validity,
 * wWindowConstrainSize() must be used for that. 
 * 	Geometry parameters are for the frame, not the client window.
 * 	The client window receives a ConfigureNotify event, according
 * to what ICCCM says.
 * 
 * Returns:
 * 	None
 * 
 * Side effects:
 * 	Window size and position are changed and client window receives
 * a ConfigureNotify event.
 *---------------------------------------------------------------------- 
 */
void 
wWindowConfigure(wwin, req_x, req_y, req_width, req_height)
WWindow *wwin;
int req_x, req_y;		       /* new position of the frame */
int req_width, req_height;	       /* new size of the frame */
{
    int synth_notify = False;
    int resize;

    resize = (req_width!=wwin->frame->width 
	      || req_height!=wwin->frame->height);
    /* 
     * if the window is being moved but not resized then
     * send a synthetic ConfigureNotify
     */
    if ((req_x!=wwin->frame_x || req_y!=wwin->frame_y) && !resize) {
	synth_notify = True;
    }
    
    /* do not let the window get unreachable 
    if (wPreferences.dont_move_off) { 
	
	if (req_y<WINDOW_PIXELS_INSIDE)
	  req_y = WINDOW_PIXELS_INSIDE;
	if (req_x<WINDOW_PIXELS_INSIDE)
	  req_x = WINDOW_PIXELS_INSIDE;
	if (wwin->screen_ptr->scr_height - req_y < WINDOW_PIXELS_INSIDE)
	  req_y = WINDOW_PIXELS_INSIDE;
	if (wwin->screen_ptr->scr_width - req_x < WINDOW_PIXELS_INSIDE)
	  req_x = WINDOW_PIXELS_INSIDE;
	  
    }
     */

    if (resize) {
	int cx, cy, cw, ch;

	if (req_width <= 0)
	  req_width = 1;
	if (req_height <= 0)
	  req_height = 1;

	cx = req_x;
	cy = req_y;
	cw = req_width;
	ch = req_height;
	/* resize the titlebar horizontally */
	if (wwin->window_flags.titlebar) {
	    ch -= wwin->top_width;
	    cy += wwin->top_width;
	    
	    /* force the texture to be regenerated */
	    wTextureFreeData(wwin->screen_ptr->focused_texture,
			     &wwin->focused_texture_data);
	    wTextureFreeData(wwin->screen_ptr->pfocused_texture,
			     &wwin->pfocused_texture_data);
	    wTextureFreeData(wwin->screen_ptr->unfocused_texture,
			     &wwin->unfocused_texture_data);
	}
	/* resize the resizebar horizontally and move it */
	if (wwin->window_flags.resizable) {
	    ch -= wwin->resizebar->height;
	    wCoreConfigure(wwin->resizebar, 0, wwin->top_width+ch, req_width, 0);
	    wTextureFreeData(wwin->screen_ptr->resizebar_texture,
			     &wwin->resizebar_texture_data);
	}
	wwin->client.x = cx;
	wwin->client.y = cy;
	wwin->client.width = cw;
	wwin->client.height = ch;
	XMoveResizeWindow(dpy, wwin->client_win, 0, wwin->top_width, cw, ch);
	wCoreConfigure(wwin->frame, req_x, req_y, req_width, req_height);
	if (wwin->window_flags.titlebar)
	  adjustButtons(wwin);	
#ifdef SHAPE
	if (wwin->flags.shaped)
	  wWindowSetShape(wwin);
#endif
	XSync(dpy, 0);
    } else {
	int cx, cy;
	
	cx = req_x;
	cy = req_y;
	if (wwin->window_flags.titlebar) {
	    cy += wwin->top_width;
	}
	wCoreConfigure(wwin->frame, req_x, req_y, 0, 0);
	wwin->client.x = cx;
	wwin->client.y = cy;
    }
    wwin->frame_x = req_x;
    wwin->frame_y = req_y;
    if (synth_notify) {
	XEvent sevent;
	
	sevent.type = ConfigureNotify;
	sevent.xconfigure.display = dpy;
	sevent.xconfigure.event = wwin->client_win;
	sevent.xconfigure.window = wwin->client_win;

	sevent.xconfigure.x = wwin->client.x;
	sevent.xconfigure.y = wwin->client.y;
	sevent.xconfigure.width = wwin->client.width;
	sevent.xconfigure.height = wwin->client.height;
 
	sevent.xconfigure.border_width = wwin->old_border_width;
	if (wwin->titlebar)
	  sevent.xconfigure.above = wwin->titlebar->window;
	else
	  sevent.xconfigure.above = None;
	sevent.xconfigure.override_redirect = False;
	XSendEvent(dpy, wwin->client_win, False, StructureNotifyMask, &sevent);
    }
    if (req_width < (RESIZEBAR_CORNER_WIDTH*2 + RESIZEBAR_MIN_WIDTH)) {
	wwin->resizebar_corner_width = (req_width - RESIZEBAR_MIN_WIDTH)/2;
	if (wwin->resizebar_corner_width < RESIZEBAR_MIN_WIDTH)
	  wwin->resizebar_corner_width = RESIZEBAR_MIN_WIDTH/2;
    } else {
	wwin->resizebar_corner_width = RESIZEBAR_CORNER_WIDTH;
    }
}


static void
setButtonImage(WButton *wbut, Pixmap pixmap, Pixmap mask, int focused)
{
    WScreen *scr = wbut->core->screen_ptr;
#ifdef NEWSTYLE /* TODO: check if this is right */
    if (wbut->image) {
	if (!wbut->image->shared 
	    && (wbut->image->image!=pixmap || wbut->image->mask!=mask)) {
	    wPixmapDestroy(wbut->image);
	    wbut->image = wPixmapCreate(scr, pixmap, mask);
	    wbut->image->client_owned = 1;
	    wbut->image->client_owned_mask = 1;
	}
    } else {
	wbut->image1 = wPixmapCreateFromBitmap(scr, pixmap, mask,
					       scr->ftitle_pixel, 
					       scr->white_pixel);
	wbut->image1->client_owned_mask = 1;
	wbut->image2 = wPixmapCreateFromBitmap(scr, pixmap, mask,
					       scr->utitle_pixel, 
					       scr->white_pixel);
	wbut->image2->client_owned_mask = 1;
	if (focused)
	  wbut->image = wbut->image1;
	else
	  wbut->image = wbut->image2;
    }
#else /* !NEWSTYLE */
    if (wbut->image) {
	if (!wbut->image->shared 
	    && (wbut->image->image!=pixmap || wbut->image->mask!=mask)) {
	    wPixmapDestroy(wbut->image);
	    wbut->image = wPixmapCreateFromBitmap(scr, pixmap, mask,
						  scr->black_pixel, 
						  scr->white_pixel);
	    wbut->image->client_owned_mask = 1;
	}
    } else {
	wbut->image = wPixmapCreateFromBitmap(scr, pixmap, mask,
					      scr->black_pixel, 
					      scr->white_pixel);
	wbut->image->client_owned_mask = 1;
    }
#endif /* !NEWSTYLE */
}


void 
wWindowUpdateButtonImages(WWindow *wwin)
{
    WButton *wbut=NULL;
    WScreen *scr = wwin->screen_ptr;
    Pixmap pixmap, mask;
    
    if (!wwin->titlebar)
      return;

    /* miniaturize button */
    
    if (wwin->window_flags.miniaturizable && (wbut = wwin->l_buttons[0])) {
	if (wwin->wm_gnustep_attr
	    && wwin->wm_gnustep_attr->flags & GSMiniaturizePixmapAttr) {
	    
	    pixmap = wwin->wm_gnustep_attr->miniaturize_pixmap;
	      
	    if (wwin->wm_gnustep_attr->flags&GSMiniaturizeMaskAttr) {
		mask = wwin->wm_gnustep_attr->miniaturize_mask;
	    } else {
		mask = None;
	    }
	    
	    setButtonImage(wbut, pixmap, mask, wwin->flags.focused);

	} else {
	    if (wbut->image && !wbut->image->shared) {
		wPixmapDestroy(wbut->image);
	    }
#ifdef NEWSTYLE
	    wbut->image1 = scr->b_pixmaps[WBUT_ICONIFY];
	    wbut->image2 = scr->b_pixmaps[WBUT_LAST+WBUT_ICONIFY];
	    if (wwin->flags.focused)
	      wbut->image = wbut->image1;
	    else
	      wbut->image = wbut->image2;
#else
	    wbut->image = scr->b_pixmaps[WBUT_ICONIFY];
#endif
	}
	wwin->l_buttons[0] = wbut;
    }
    
    /* close button */

    if (wwin->window_flags.closable && (wbut = wwin->r_buttons[0])) {
	if (wwin->wm_gnustep_attr
	    && wwin->wm_gnustep_attr->flags&GSClosePixmapAttr) {
	    
	    pixmap = wwin->wm_gnustep_attr->close_pixmap;
	    
	    if (wwin->wm_gnustep_attr->flags&GSCloseMaskAttr) {
		mask = wwin->wm_gnustep_attr->close_mask;
	    } else {
		mask = None;
	    }

	    setButtonImage(wbut, pixmap, mask, wwin->flags.focused);
	    
	} else if (wwin->window_flags.broken_close) {
	    if (wbut->image && !wbut->image->shared) {
		wPixmapDestroy(wbut->image);
	    }
#ifdef NEWSTYLE
	    wbut->image1 = scr->b_pixmaps[WBUT_BROKENCLOSE];
	    wbut->image2 = scr->b_pixmaps[WBUT_LAST+WBUT_BROKENCLOSE];
	    if (wwin->flags.focused)
	      wbut->image = wbut->image1;
	    else
	      wbut->image = wbut->image2;
#else
	    wbut->image = scr->b_pixmaps[WBUT_BROKENCLOSE];
#endif
	} else {
	    if (wbut->image && !wbut->image->shared) {
		wPixmapDestroy(wbut->image);
	    }
#ifdef NEWSTYLE
	    wbut->image1 = scr->b_pixmaps[WBUT_CLOSE];
	    wbut->image2 = scr->b_pixmaps[WBUT_LAST+WBUT_CLOSE];
	    if (wwin->flags.focused)
	      wbut->image = wbut->image1;
	    else
	      wbut->image = wbut->image2;
#else
	    wbut->image = scr->b_pixmaps[WBUT_CLOSE];
#endif
	}
	wwin->r_buttons[0] = wbut;
    }
}

/*
 *----------------------------------------------------------------------
 * wWindowConfigureBorders--
 * 	Configure window borders, titlebar, resize bar and titlebar 
 * buttons according to the current window attributes.
 * 
 * Returns:
 * 	None
 * 
 * Side effects:
 * 	Window titlebar, resize bar and buttons may be created,
 * modified or destroyed. Texture data is freed so that next time
 * window is exposed, texture will be regenerated.
 * 
 *---------------------------------------------------------------------- 
 */
void
wWindowConfigureBorders(WWindow *wwin)
{
    int resized=0;
    WScreen *scr = wwin->screen_ptr;
    int bsize;
    int new_height = wwin->client.height;
    int width=wwin->client.width, height=wwin->client.height;
    int i;
    
    /* titlebar and buttons */
    wwin->top_width = 0;
      
    if (wwin->window_flags.titlebar) {
	WButton *wbut=NULL;
	int x, w, h;
	
	h = scr->title_font.height + 8;
	w = width;
	resized = 1;	
	if (!wwin->titlebar) {
	    wwin->titlebar = wCoreCreate(wwin->frame, 0, 0, w, h);
	} else
	  wCoreConfigure(wwin->titlebar, 0, 0, w, h);

	/* setup object descriptor */
	wwin->titlebar->descriptor.parent = wwin;
	wwin->titlebar->descriptor.parent_type = WCLASS_WINDOW; 
	wwin->titlebar->descriptor.methods = &wWindowTitlebarMethods;

	new_height += h;
	wwin->top_width += h;

	for (i=0; i<wwin->l_button_no; i++) {
	    if (wwin->l_buttons[i]!=NULL) {	    
		wButtonDestroy(wwin->l_buttons[i]);
	    }
	}
	wwin->l_button_no=0;
	bsize = h - 7;
	/* put default titlebar buttons on left */
	if (wwin->window_flags.miniaturizable) {
#ifdef NEWSTYLE
	    x = 0;
	    wbut = wButtonCreate(wwin->frame, x, 0, h-1, h);
#else
	    x = 3;
	    wbut = wButtonCreate(wwin->titlebar, x, (h-bsize)/2, bsize, bsize);
#endif
	    /* setup object descriptor */
	    wbut->core->descriptor.parent = wwin;
	    wbut->core->descriptor.parent_type = WCLASS_WINDOW;
	    wbut->core->descriptor.methods = &wWindowIconifyMethods;
	    wwin->l_buttons[wwin->l_button_no++] = wbut;
	}
	for (i=0; i<wwin->r_button_no; i++) {
	    if (wwin->r_buttons[i]!=NULL) {
		wButtonDestroy(wwin->r_buttons[i]);
	    }
	}	
	wwin->r_button_no=0;
	/* put default titlebar buttons on right */
	if (wwin->window_flags.closable) {
#ifdef NEWSTYLE
	    x = width - h + 1;
	    wbut = wButtonCreate(wwin->frame, x, 0, h-1, h);
#else
	    x = width - 3 - bsize;
	    wbut = wButtonCreate(wwin->titlebar, x, (h-bsize)/2, bsize, bsize);
#endif
	    /* setup object descriptor */
	    wbut->core->descriptor.parent = wwin;
	    wbut->core->descriptor.parent_type = WCLASS_WINDOW;
	    wbut->core->descriptor.methods = &wWindowCloseMethods;
	    wwin->r_buttons[wwin->r_button_no++] = wbut;
	}
	wWindowUpdateButtonImages(wwin);
	XMapSubwindows(dpy, wwin->titlebar->window);
	adjustButtons(wwin);
    } else {
	/* destroy titlebar */
	if (wwin->titlebar) {
	    for (i=0; i<wwin->l_button_no; i++) {
		if (wwin->l_buttons[i]!=NULL) {	    
		    wButtonDestroy(wwin->l_buttons[i]);
		}
	    }
	    for (i=0; i<wwin->r_button_no; i++) {
		if (wwin->r_buttons[i]!=NULL) {
		    wButtonDestroy(wwin->r_buttons[i]);
		}
	    }
	    resized = 1;
	    wCoreDestroy(wwin->titlebar);
	    wwin->titlebar=NULL;
	}
    }

    /* resize resizebar */
    
    wwin->bottom_width = 0;    
    if (wwin->window_flags.resizable) {
	resized = 1;
	if (!wwin->resizebar) {
	    wwin->resizebar = wCoreCreate(wwin->frame, 0, 
					  wwin->top_width+height, 
					  width, RESIZEBAR_HEIGHT);
	} else
	  wCoreConfigure(wwin->resizebar, 0, wwin->top_width+height, width, 
			 RESIZEBAR_HEIGHT);
	/* setup object descriptor */
	wwin->resizebar->descriptor.parent = wwin;
	wwin->resizebar->descriptor.parent_type = WCLASS_WINDOW;
	wwin->resizebar->descriptor.methods = &wWindowResizebarMethods;
	wwin->bottom_width += RESIZEBAR_HEIGHT;
	new_height += RESIZEBAR_HEIGHT;
	
	wwin->resizebar_corner_width = RESIZEBAR_CORNER_WIDTH;
	if (width < RESIZEBAR_CORNER_WIDTH*2 + RESIZEBAR_MIN_WIDTH) {
	    wwin->resizebar_corner_width = (width - RESIZEBAR_MIN_WIDTH)/2;
	    if (wwin->resizebar_corner_width < 0)
	      wwin->resizebar_corner_width = 0;
	}
    } else {
	/* remove resizebar */
	if (wwin->resizebar) {
	    resized = 1;
	    wCoreDestroy(wwin->resizebar);
	    wwin->resizebar=NULL;
	}
    }

    wTextureFreeData(wwin->screen_ptr->focused_texture,
		     &wwin->focused_texture_data);
    wTextureFreeData(wwin->screen_ptr->unfocused_texture,
		     &wwin->unfocused_texture_data);
    wTextureFreeData(wwin->screen_ptr->pfocused_texture,
			     &wwin->pfocused_texture_data);
    wTextureFreeData(wwin->screen_ptr->resizebar_texture,
		     &wwin->resizebar_texture_data);

    if (resized) {
	wWindowConfigure(wwin, wwin->frame_x, wwin->frame_y,
			 wwin->frame->width, new_height);
    }
    if (wwin->flags.focused)
      wWindowFocus(wwin);
    else
      wWindowUnfocus(wwin);
    wwin->flags.needs_full_repaint=1;
    wWindowPaint(wwin, BORDER_ALL, False);
}



void
wWindowSaveState(WWindow *wwin)
{
    WSavedState state;

    if (wwin->flags.miniaturized || wwin->flags.hidden) {
	state.workspace = 0;
    } else {
	state.workspace = wwin->workspace;
    }
    state.shaded = wwin->flags.shaded;
    state.hidden = wwin->flags.hidden;

    XChangeProperty(dpy, wwin->client_win, _XA_WINDOWMAKER_STATE,
		    _XA_WINDOWMAKER_STATE, 32, PropModeReplace,
		    (unsigned char *) &state, sizeof(WSavedState)/sizeof(int));
}


static int
getSavedState(Window window, WSavedState **state)
{
    Atom type_ret;
    int fmt_ret;
    unsigned long nitems_ret;
    unsigned long bytes_after_ret;
    
    if (XGetWindowProperty(dpy, window, _XA_WINDOWMAKER_STATE, 0, 
			   sizeof(WSavedState), 
			   True, _XA_WINDOWMAKER_STATE,
			   &type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret,
			   (unsigned char **)state)!=Success)
      return 0;
    if (type_ret==_XA_WINDOWMAKER_STATE)
      return 1;
    else 
      return 0;
}


#ifdef SHAPE
void wWindowSetShape(WWindow *wwin)
{
    XRectangle rect[2];
    int count=0;

    XShapeCombineShape(dpy, wwin->frame->window, ShapeBounding,
		       0, wwin->top_width, wwin->client_win, 
		       ShapeBounding, ShapeSet);
    if (wwin->titlebar) {
	rect[count].x = 0;
	rect[count].y = 0;
	rect[count].width = wwin->frame->width;
	rect[count].height = wwin->top_width;
	count++;
    }
    if (wwin->resizebar) {
	rect[count].x = 0;
	rect[count].y = wwin->frame->height-wwin->bottom_width;
	rect[count].width = wwin->resizebar->width;
	rect[count].height = wwin->bottom_width;
	count++;
    }
    XShapeCombineRectangles(dpy,wwin->frame->window,ShapeBounding,
			    0,0,rect,count,ShapeUnion,Unsorted);
}
#endif

/* ====================================================================== */

static FocusMode
getFocusMode(WWindow *wwin)
{    
    FocusMode mode;
    
    if ((wwin->wm_hints) && (wwin->wm_hints->flags & InputHint)) {
	if (wwin->wm_hints->input == True) {
	    if (wwin->protocols.TAKE_FOCUS)
	      mode = WFM_LOCALLY_ACTIVE;
	    else
	      mode = WFM_PASSIVE;
	} else {
	    if (wwin->protocols.TAKE_FOCUS)
	      mode = WFM_GLOBALLY_ACTIVE;
	    else
	      mode = WFM_NO_INPUT;
	}
    } else {
	mode = WFM_PASSIVE;
    }
    return mode;
}



static void
grabKeys(WWindow *wwin)
{
    int i;
    WShortKey *key;

    for (i=0; i<WKBD_LAST; i++) {
	key = wKeyBindings[i];
	while (key) {
	    if (key->modifier!=AnyModifier) {
		XGrabKey(dpy, key->keycode, key->modifier|LockMask, 
			 wwin->frame->window, True, GrabModeAsync, GrabModeAsync);
	    }
	    XGrabKey(dpy, key->keycode, key->modifier, 
		     wwin->frame->window, True, GrabModeAsync, GrabModeAsync);
	    key = key->alternate;
	}
    }
}


static void
setupGrabs(WWindow *wwin)
{
    if (!wwin->flags.internal_window 
	&& !(wwin->window_flags.menu_window||wwin->window_flags.icon_window)) {
	/* setup passive grabs for the client window */
	XGrabButton(dpy, AnyButton, AnyModifier, wwin->frame->window,
		    True, ButtonPressMask, GrabModeSync, GrabModeAsync,
		    None, None);
	XFlush(dpy);
    }
}

static void
removeGrabs(WWindow *wwin)
{
    if (!wwin->flags.internal_window
	&& !(wwin->window_flags.menu_window||wwin->window_flags.icon_window)) {
	/* remove passive grabs from the client window frame */
	XUngrabButton(dpy, AnyButton, AnyModifier, wwin->frame->window);
	XFlush(dpy);
    }
}



static void
setupResources(WWindow *wwin, WResource *resource)
{
    if (resource!=&wResourceDefault) {
	wwin->icon_file = resource->icon_file;
	wwin->icon_text = resource->icon_text;
    }

    if (resource->valid & WA_TITLEBAR)
      wwin->window_flags.titlebar = (resource->value & WA_TITLEBAR)!=0;
    
    if (resource->valid & WA_RESIZABLE)
      wwin->window_flags.resizable = (resource->value & WA_RESIZABLE)!=0;
    
    if (resource->valid & WA_CLOSABLE)
      wwin->window_flags.closable= (resource->value & WA_CLOSABLE)!=0;

    if (resource->valid & WA_BROKEN_CLOSE)
      wwin->window_flags.broken_close= (resource->value & WA_BROKEN_CLOSE)!=0;

    if (resource->valid & WA_MINIATURIZABLE)
      wwin->window_flags.miniaturizable=(resource->value & WA_MINIATURIZABLE)!=0;
    
    if (resource->valid & WA_SHADEABLE)
      wwin->window_flags.shadeable= (resource->value & WA_SHADEABLE)!=0;
    
    if (resource->valid & WA_FOCUSABLE)
      wwin->window_flags.focusable= (resource->value & WA_FOCUSABLE)!=0;
    
    if (resource->valid & WA_OMNIPRESENT)
      wwin->window_flags.omnipresent = (resource->value & WA_OMNIPRESENT)!=0;
    
    if (resource->valid & WA_SKIP_WINDOW_LIST)
      wwin->window_flags.skip_window_list= (resource->value
					    & WA_SKIP_WINDOW_LIST)!=0;
    if (resource->valid & WA_FLOATING) 
      wwin->window_flags.floating= (resource->value & WA_FLOATING)!=0;
    
    if (resource->valid & WA_IGNORE_KEYS)
      wwin->window_flags.ignore_keys= (resource->value & WA_IGNORE_KEYS)!=0;
    
    if (resource->valid & WA_IGNORE_MOUSE)
      wwin->window_flags.ignore_mouse= (resource->value & WA_IGNORE_MOUSE)!=0;

    if (resource->valid & WA_IGNORE_HIDE_OTHERS)
      wwin->window_flags.ignore_hide_others= (resource->value 
					      & WA_IGNORE_HIDE_OTHERS)!=0;
    
    if (resource->valid & WA_NOT_APPLICATION)
      wwin->window_flags.not_application= (resource->value 
					   & WA_NOT_APPLICATION)!=0;
#if 0
    if (resource->valid & WA_START_WITHDRAWN)
      wwin->window_flags.start_withdrawn = (resource->value 
						& WA_START_WITHDRAWN)!=0;
#endif
}
 

/*
 *---------------------------------------------------------------------- 
 * putWindowBorders--
 * 	Puts the titlebar and resizebar if specified in wwin->window_flags.
 * 
 * Returns:
 * 	The position to place the child window inside the frame (child_x,
 *								 child_y)
 * 
 * Side effects:
 * 	The size of the frame is adjusted to accomodate the titlebar and
 * resizebar. The position of the frame is also adjusted according to the
 * gravity. wwin is associated to the created windows.
 *
 *---------------------------------------------------------------------- 
 */
static void
putWindowBorders(wwin, width, height, child_x, child_y)
WWindow *wwin;
int width, height;		       /* size of the client window */
int *child_x, *child_y;		       /* returns the position of the client
					* window inside the frame */
{
    /*
    int ox, oy;
     */

    *child_x = 0;
    *child_y = 0;
 
    wWindowConfigureBorders(wwin);
/*
    wClientGetGravityOffsets(wwin, &ox, &oy);
    wwin->frame_x += ox * (wwin->old_border_width - FRAME_BORDER_WIDTH);
    wwin->frame_y += oy * (wwin->old_border_width - FRAME_BORDER_WIDTH);
 */
    if (wwin->window_flags.titlebar) {
	int h;
	
	h = wwin->screen_ptr->title_font.height + 8;
	*child_y += h;
    }
    if (wwin->window_flags.resizable && wwin->window_flags.client_resizebar
	&& wwin->resizebar) {
	/* unselect button/kbd events */
	XSelectInput(dpy, wwin->resizebar->window, ExposureMask);
	/* tell the resizebar window to the client */
	wClientSetResizebarWindow(wwin->client_win, wwin->resizebar->window);
    }
}


/* ====================================================================== */

static void 
resizebarMouseDown(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin = desc->parent;

    event->xbutton.state &= ~LockMask;
    
    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    if (event->xbutton.state & Mod1Mask) {
	/* move the window */
	if (XGrabPointer(dpy, wwin->resizebar->window, False, 
			 ButtonMotionMask|ButtonReleaseMask,
			 GrabModeAsync, GrabModeAsync, None, 
			 None, CurrentTime)!=GrabSuccess) {
#ifdef DEBUG0
	    wWarning("pointer grab failed for window move");
#endif
	    return;
	}
	wMouseMoveWindow(wwin, event);
	XUngrabPointer(dpy, CurrentTime);		
    } else {
	if (event->xbutton.button == Button1
	    || event->xbutton.button == Button2) {
	    if (wPreferences.focus_mode==WKF_CLICK)
	      wSetFocusTo(wwin->screen_ptr, wwin);
	}
	if (event->xbutton.button == Button1) {
	    wRaiseFrame(wwin->frame);
	    /* resize the window */
	    if (XGrabPointer(dpy, wwin->resizebar->window, False, 
			     ButtonMotionMask|ButtonReleaseMask,
			     GrabModeAsync, GrabModeAsync, None, 
			     None, CurrentTime)!=GrabSuccess) {
#ifdef DEBUG0
		wWarning("pointer grab failed for window resize");
#endif
		return;
	    }
	    wMouseResizeWindow(wwin, event);
	    XUngrabPointer(dpy, CurrentTime);
	}
    }
}


static void 
titlebarDblClick(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin = desc->parent;

    event->xbutton.state &= ~LockMask;

    if (event->xbutton.button==Button3) {
	if (event->xbutton.state & Mod1Mask) {
	    wHideOtherApplications(wwin);
	} else {
	    if (wwin->wm_instance && wwin->wm_class) {
		printf("%s.%s\n", wwin->wm_instance, wwin->wm_class);
	    } else if (wwin->wm_class) {
		printf("%s\n", wwin->wm_class);
	    } else if (wwin->wm_instance) {
		printf("%s\n", wwin->wm_instance);
	    } else {
		printf("instance/class name not available for window %x\n",
		       (unsigned)wwin->client_win);
	    }
	}
    } else if (event->xbutton.state & ControlMask) {
	/* maximize vertically */
	if (wwin->window_flags.resizable) {
	    if (wwin->flags.maximized) {
		wUnmaximizeWindow(wwin);
	    } else
	      wMaximizeWindow(wwin, event->xbutton.state & ShiftMask);
	}
    } else {
	if (event->xbutton.button==Button1 && wwin->window_flags.shadeable
	    && event->xbutton.state == 0) {
	    /* shade window */
	    if (wwin->flags.shaded)
	      wUnshadeWindow(wwin);
	    else
	      wShadeWindow(wwin);
	} else if (event->xbutton.button==Button2) {
	    wSelectWindow(wwin);	    
	}
    } 
}


static void 
titlebarMouseDown(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin = desc->parent;

    event->xbutton.state &= ~LockMask;

    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    if (wPreferences.focus_mode==WKF_CLICK)
      wSetFocusTo(wwin->screen_ptr, wwin);
    
    if (event->xbutton.button == Button1
	|| event->xbutton.button == Button2) {
	
	if (event->xbutton.button == Button1) {
	    if (event->xbutton.state & Mod1Mask) {
		wLowerFrame(wwin->frame);
	    } else {
		wRaiseFrame(wwin->frame);
	    }
	}
	if (event->xbutton.state & ShiftMask) {
	    wSelectWindow(wwin);
	    return;
	}
	if (XGrabPointer(dpy, wwin->titlebar->window, False, 
			 ButtonMotionMask|ButtonReleaseMask,
			 GrabModeAsync, GrabModeAsync, None, 
			 None, CurrentTime)!=GrabSuccess) {
#ifdef DEBUG0
	    wWarning("pointer grab failed for window move");
#endif
	    return;
	}
	/* move the window */
	wMouseMoveWindow(wwin, event);
	XUngrabPointer(dpy, CurrentTime);
    } else if (event->xbutton.button == Button3 && event->xbutton.state==0) {
	WObjDescriptor *desc;
	
	OpenWindowMenu(wwin, event->xbutton.x_root, 
		       wwin->frame_y+wwin->top_width);

	/* allow drag select */
	if (XFindContext(dpy, wwin->screen_ptr->window_menu->frame->window, 
			 wWinContext, (XPointer *)&desc)!=XCNOENT) {
	    event->xany.send_event = True;
	    (*desc->methods->mouse_down)(desc, event);
	}
    }
}


static void
frameMouseDown(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin = desc->parent;

    event->xbutton.state &= ~LockMask;
    
    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    CloseWindowMenu(wwin->screen_ptr);
    if (wPreferences.focus_mode==WKF_CLICK)
      wSetFocusTo(wwin->screen_ptr, wwin);
    if (event->xbutton.button == Button1
	|| event->xbutton.button == Button2) {
	if (event->xbutton.button == Button1)
	  wRaiseFrame(wwin->frame);
	if (event->xbutton.state & Mod1Mask) {
	    /* move the window */
	    if (XGrabPointer(dpy, wwin->client_win, False, 
			     ButtonMotionMask|ButtonReleaseMask,
			     GrabModeAsync, GrabModeAsync, None,
			     None, CurrentTime)!=GrabSuccess) {
#ifdef DEBUG0
		wWarning("pointer grab failed for window move");
#endif
		return;
	    }
	    wMouseMoveWindow(wwin, event);

	    XUngrabPointer(dpy, CurrentTime);
	}
    }
}


static void
windowCloseClick(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin=desc->parent;

    event->xbutton.state &= ~LockMask;
    
    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    /* if control-click, kill the client */
    if (event->xkey.state & ControlMask) {
      wClientKill(wwin);
    } else if (wwin->protocols.DELETE_WINDOW) {
	/* send delete message */
	wClientSendProtocol(wwin, _XA_WM_DELETE_WINDOW, event->xbutton.time);
    }
}


static void
windowCloseDblClick(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin=desc->parent;

    event->xbutton.state &= ~LockMask;

    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    /* send delete message */
    if (wwin->protocols.DELETE_WINDOW) {
	wClientSendProtocol(wwin, _XA_WM_DELETE_WINDOW, event->xbutton.time);
    } else {
	wClientKill(wwin);
    }
}


static void
windowIconifyClick(WObjDescriptor *desc, XEvent *event)
{
    WWindow *wwin = desc->parent;

    event->xbutton.state &= ~LockMask;
    
    if (wwin->screen_ptr->window_menu)
      wMenuUnmap(wwin->screen_ptr->window_menu);

    if (wwin->protocols.MINIATURIZE_WINDOW) {
	wClientSendProtocol(wwin, _XA_GNUSTEP_WM_MINIATURIZE_WINDOW,
			    event->xbutton.time);
    } else {
	WApplication *wapp;
	if ((event->xbutton.state & Mod1Mask)
	    || event->xbutton.button==Button3) {
	    wapp = wApplicationOf(wwin->main_window);
	    if (wapp && !wwin->window_flags.not_application)
	      wHideApplication(wapp);
	} else {
	    wIconifyWindow(wwin);
	}
    }
    if (wwin->flags.selected) {	
	wSelectWindow(wwin);
    }
}

