/* icon.c - window icon and dock and appicon parent
 * 
 *  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>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <wraster.h>

#include "generic/wwmlib.h"
#include "WindowMaker.h"
#include "wcore.h"
#include "pixmap.h"
#include "texture.h"
#include "window.h"
#include "icon.h"
#include "actions.h"
#include "funcs.h"
#include "stacking.h"
#include "application.h"
#include "resource.h"

/**** Global variables ****/
extern char *PixmapPath;
extern WPreferences wPreferences;
extern WResource wResourceDefault;

extern Cursor wCursor[WCUR_LAST];



static void miniwindowMouseDown(WObjDescriptor *desc, XEvent *event);
static void miniwindowDblClick(WObjDescriptor *desc, XEvent *event);

WMethodTable wIconMethods = {
    (WEventHandler)miniwindowMouseDown,
    NULL,
    (WEventHandler)miniwindowDblClick,
    NULL
};

static void setupIconImage(WIcon *icon, int title_h);


inline static void
getSize(Drawable d, unsigned int *w, unsigned int *h, unsigned int *dep)
{
    Window rjunk;
    int xjunk, yjunk;
    unsigned int bjunk;
    
    XGetGeometry(dpy, d, &rjunk, &xjunk, &yjunk, w, h, &bjunk, dep);
}


WIcon*
wIconCreate(WWindow *wwin)
{
    WScreen *scr=wwin->screen_ptr;
    WIcon *icon;
    
    icon = wmalloc(sizeof(WIcon));
    memset(icon, 0, sizeof(WIcon));
    icon->core = wCoreCreateTopLevel(scr, wwin->icon_x, wwin->icon_y,
				     ICON_WIDTH, ICON_HEIGHT, 0, False);
    /* will be overriden if this is an application icon */
    icon->core->descriptor.methods = &wIconMethods;
    icon->core->descriptor.parent_type = WCLASS_MINIWINDOW;
    icon->core->descriptor.parent = icon;
    
    icon->core->stacking = wmalloc(sizeof(WStacking));
    icon->core->stacking->above = NULL;
    icon->core->stacking->under = NULL;
    icon->core->stacking->window_level = NSNormalWindowLevel;
    icon->core->stacking->child_of = NULL;

    icon->owner = wwin;
    if (wwin->client_win == wwin->main_window && wwin->wm_hints
	&& wwin->wm_hints->flags&IconWindowHint) {
	WApplication *wapp;
	/* do not let miniwindow steal app-icon's icon window */
	wapp = wApplicationOf(wwin->client_win);
	if (wapp && wapp->app_icon!=NULL)
	  icon->use_our_pixmap = 1;
    }
    icon->icon_text = wwin->icon_text;
    icon->icon_file = wwin->icon_file;
    icon->show_title = 1;
    icon->force_paint = 1;
    wIconUpdate(icon);
/*
    XGrabButton(dpy, AnyButton, AnyModifier, icon->core->window,
		True, ButtonPressMask,
		GrabModeAsync, GrabModeAsync, None, None);
 */
    XFlush(dpy);
    
    return icon;
}


WIcon*
wIconCreateWithImage(WScreen *scr, char *icon_file)
{
    WIcon *icon;

    icon = wmalloc(sizeof(WIcon));
    memset(icon, 0, sizeof(WIcon));
    icon->core = wCoreCreateTopLevel(scr, 0, 0, ICON_WIDTH, ICON_HEIGHT, 0, 
				     False);
    /* will be overriden if this is a application icon */
    icon->core->descriptor.methods = &wIconMethods;
    icon->core->descriptor.parent_type = WCLASS_MINIWINDOW;
    icon->core->descriptor.parent = icon;
    
    icon->core->stacking = wmalloc(sizeof(WStacking));
    icon->core->stacking->above = NULL;
    icon->core->stacking->under = NULL;
    icon->core->stacking->window_level = NSNormalWindowLevel;
    icon->core->stacking->child_of = NULL;

    icon->force_paint = 1;
    icon->icon_file = icon_file;
    wIconUpdate(icon);
/*
    XGrabButton(dpy, AnyButton, AnyModifier, icon->core->window,
		True, ButtonPressMask,
		GrabModeAsync, GrabModeAsync, None, None);
 */
    XFlush(dpy);

    return icon;
}



void
wIconDestroy(WIcon *icon)
{
    WCoreWindow *core = icon->core;
    WScreen *scr = core->screen_ptr;

    if (icon->client_window) {
	XReparentWindow(dpy, icon->icon_win, scr->root_win, 0, 0);
	XUnmapWindow(dpy, icon->icon_win);
    }
    if (icon->icon_name)
      XFree(icon->icon_name);
    
#ifdef METROBUG
    if (icon->pix_image)
      XFreePixmap(dpy, icon->pix_image);
#endif
    
    wCoreDestroy(icon->core);
    wfree(icon);
}



/*
 *----------------------------------------------------------------------
 * wIconUpdate--
 * 	Updates or creates the icon contents, including the title and
 * pixmap. If the client does not specify anything to be shown there,
 * the default application icon is used. 
 * 
 * Side effects:
 * 	The icon window background pixmap is set and the title string
 * is reread.
 * 
 *---------------------------------------------------------------------- 
 */
void
wIconUpdate(WIcon *icon)
{
    WWindow *wwin = icon->owner;
    WScreen *scr=icon->core->screen_ptr;
    int th, title_h=scr->icon_title_font.height;
    int had_title, title_changed=0, image_changed=0;
    
    had_title = (icon->icon_name!=NULL);
    if (icon->icon_name) {
	XFree(icon->icon_name);
	icon->icon_name = NULL;
    }
    if (icon->show_title && wwin) {
	if (!MyXGetIconName(dpy, wwin->client_win, &icon->icon_name)) {
	    icon->icon_name = NULL;
	    if (had_title)
	      title_changed = 1;
	} else {
	    if (!had_title)
	      title_changed = 1;
	}
    } else {
	if (had_title)
	  title_changed = 1;
    }

    /* if show_title was changed */
    if (icon->has_titlebar && !icon->show_title) {
	title_changed = 1;
    }
    if (icon->show_title && icon->icon_name!=NULL) {
	th = title_h;
    } else {
	th = 0;
    }
    icon->has_titlebar = icon->show_title;    
    
    /* if icon image was changed */
    if (wwin && wwin->wm_hints) {
	if ((wwin->wm_hints->flags & IconWindowHint)
	    && icon->icon_win!=wwin->wm_hints->icon_window) {
	    image_changed = 1;
	} else if ((wwin->wm_hints->flags & IconPixmapHint)
		   && (icon->our_pixmap
		       || icon->pixmap!=wwin->wm_hints->icon_pixmap
		       || icon->mask!=wwin->wm_hints->icon_mask)) {
	    image_changed = 1;
	}
    }
    if (icon->force_paint || title_changed || image_changed) {
	icon->force_paint = 0;
	setupIconImage(icon, th);
    }
    wIconPaint(icon);
}


static void
drawIconTitle(WScreen *scr, Pixmap pixmap, int height)
{
    XFillRectangle(dpy, pixmap, scr->icon_title_texture->normal_gc,
		   0, 0, ICON_WIDTH, height+1);
    XDrawLine(dpy, pixmap, scr->icon_title_texture->light_gc, 0, 0,
	      ICON_WIDTH, 0);
    XDrawLine(dpy, pixmap, scr->icon_title_texture->light_gc, 0, 0,
	      0, height+1);
    XDrawLine(dpy, pixmap, scr->icon_title_texture->dim_gc, ICON_WIDTH-1, 0, 
	      ICON_WIDTH-1, height+1);
}


static void
setupIconImage(WIcon *icon, int title_h)
{
    WWindow *wwin = icon->owner;
    unsigned int width, height, depth;
    WScreen *scr = icon->core->screen_ptr;
    Pixmap pixmap=None;

    /*
     * TODO: rewrite setupIconImage()
     * 
     * The icon that will be used is selected in this precedence:
     * 
     * 1. application supplied icon window
     * 2. user specified icon for instance name
     * 3. user specified icon for class name
     * 4. application supplied icon pixmap
     * 5. built-in icon
     * 
     * the icon names will also be displayed if it's available
     * 
     */
    if (wwin && wwin->wm_hints && (wwin->wm_hints->flags & IconWindowHint)
	&& !icon->use_our_pixmap) {
	/* check if the client supplies an icon window */
	int resize=0, th;

#ifdef METROBUG
	if (icon->pix_image)
	  XFreePixmap(dpy, icon->pix_image);
#endif
	icon->our_pixmap = 0;
	icon->icon_win = wwin->wm_hints->icon_window;
	icon->client_window = 1;
	getSize(icon->icon_win, &width, &height, &depth);

	if (width > ICON_WIDTH) {
	    resize = 1;
	    width = ICON_WIDTH;
	}
	if (height > ICON_HEIGHT) {
	    resize = 1;
	    height = ICON_HEIGHT;
	}
	if (icon->show_title && icon->icon_name!=NULL 
	    && (height+title_h < ICON_HEIGHT)) {
	    
	    pixmap = XCreatePixmap(dpy, scr->w_win, ICON_WIDTH, ICON_HEIGHT, 
				scr->w_depth);
	    XSetClipMask(dpy, scr->copy_gc, None);
	    XCopyArea(dpy, scr->icon_tile_pixmap, pixmap, scr->copy_gc, 0, 0,
		      ICON_WIDTH, ICON_HEIGHT, 0, 0);
	    th = title_h;
	    drawIconTitle(scr, pixmap, th);
	} else {
	    th = 0;
	    XSetWindowBackgroundPixmap(dpy, icon->core->window,
				       scr->icon_tile_pixmap);
	}
	XSetWindowBorderWidth(dpy, icon->icon_win, 0);
	XReparentWindow(dpy, icon->icon_win, icon->core->window,
			(ICON_WIDTH-width)/2, th+(ICON_HEIGHT-height-th)/2);
	if (resize)
	  XResizeWindow(dpy, icon->icon_win, width, height);
	XMapWindow(dpy, icon->icon_win);
	
	XAddToSaveSet(dpy, icon->icon_win);
	
    } else if (icon->icon_file) {
	/* look for user supplied icon file */
	char *path;
	RImage *img, *tile;
	int x, y;

	icon->our_pixmap = 1;
	path = FindImage(PixmapPath, icon->icon_file);
	if (!path) {
	    wWarning(_("could not find icon \"%s\""), icon->icon_file);
	    goto use_default_icon;
	}

	img = RLoadImage(scr->rcontext, path, 0);
	free(path);
	if (!img)
	  goto use_default_icon;
	x = (ICON_WIDTH - img->width)/2;
	y = title_h + (ICON_HEIGHT - img->height - title_h)/2;
	tile = RCloneImage(scr->icon_tile);
	if (!tile) {
	    wWarning(_("error making icon: %s"), RErrorString);
	    RDestroyImage(img);
	    goto use_default_icon;
	}
	RCombineArea(tile, img, 0, 0, img->width, img->height, x, y);
	RDestroyImage(img);
	if (!RConvertImage(scr->rcontext, tile, &pixmap)) {
	    wWarning(_("error making icon: %s"), RErrorString);
	    pixmap = None;
	}
	if (title_h>0) {
	    drawIconTitle(scr, pixmap, title_h);
	}
	RDestroyImage(tile);
	if (!pixmap) {
	    goto use_default_icon;
	}
#ifdef METROBUG
	if (icon->pix_image)
	  XFreePixmap(dpy, icon->pix_image);
	icon->pix_image = pixmap;
#endif
    } else if (wwin && wwin->wm_hints 
	       && (wwin->wm_hints->flags & IconPixmapHint)) {
	int x, y;
	int w, h, d;
	Pixmap pix;

	icon->our_pixmap = 0;
	/* look for a client supplied pixmap */
	icon->client_pixmap = 1;
	icon->pixmap = wwin->wm_hints->icon_pixmap;
	if (wwin->wm_hints->flags & IconMaskHint)
	  icon->mask = wwin->wm_hints->icon_mask;
	else
	  icon->mask = None;
	/* we accept icon pixmaps of depth > 1, althought it's not 
	 * ICCCM compliant */
	getSize(icon->pixmap, (unsigned*)&w, (unsigned*)&h, (unsigned*)&d);
	
	if (w > MAX_ICON_WIDTH)
	  w = MAX_ICON_WIDTH;

	if (h > MAX_ICON_HEIGHT)
	  h = MAX_ICON_HEIGHT; 

	pix = XCreatePixmap(dpy, scr->w_win, ICON_WIDTH, ICON_HEIGHT,
			    scr->w_depth);
	XSetClipMask(dpy, scr->copy_gc, None);
	XCopyArea(dpy, scr->icon_tile_pixmap, pix, scr->copy_gc, 0, 0,
		  ICON_WIDTH, ICON_HEIGHT, 0, 0);
	x = (ICON_WIDTH - w)/2;
	y = title_h + (ICON_HEIGHT - h - title_h)/2;
	XSetClipOrigin(dpy, scr->copy_gc, x, y);
	XSetClipMask(dpy, scr->copy_gc, icon->mask);
	if (d==1) {
	    XCopyPlane(dpy, icon->pixmap, pix, scr->copy_gc, 0, 0, w, h, 
		       x, y, 1);
	} else {
	    XCopyArea(dpy, icon->pixmap, pix, scr->copy_gc, 0, 0, w, h, x, y);
	}
	if (title_h>0) {
	    drawIconTitle(scr, pix, title_h);
	}
#ifdef METROBUG
	if (icon->pix_image)
	  XFreePixmap(dpy, icon->pix_image);
	icon->pix_image = pix;
#else
	XSetWindowBackgroundPixmap(dpy, icon->core->window, pix);
	XFreePixmap(dpy, pix);
#endif
	if (y <= title_h)
	  icon->show_title = 0;
    } else {
	use_default_icon:
	
	if (scr->def_icon_pixmap==None && wPreferences.icon_back_file) {
	    RImage *img, *tile;
	    char *path;
	    /* generate default icons */
	    
	    path = FindImage(PixmapPath, wPreferences.icon_back_file);
	    if (!path) {
		wWarning(_("could not find icon \"%s\""), 
			 wPreferences.icon_back_file);
		goto fallback;
	    }
	    /* default untitled icon */
	    tile = RCloneImage(scr->icon_tile);
	    if (!tile) {
		wWarning(_("error making icon: %s"), RErrorString);
		goto fallback;
	    }
	    if (!(img = RLoadImage(scr->rcontext,
				   wResourceDefault.icon_file, 0))) {
		wWarning(_("error making icon: %s"), RErrorString);
		RDestroyImage(tile);
		goto fallback;
	    }
	    RCombineArea(tile, img, 0, 0, img->width, img->height,
			 (ICON_WIDTH - img->width)/2, 
			 (ICON_HEIGHT - img->height)/2);
	    if (!RConvertImage(scr->rcontext, tile, &scr->def_icon_pixmap)) {
		wWarning(_("error making icon: %s"), RErrorString);
		RDestroyImage(img);
		RDestroyImage(tile);
		goto fallback;
	    }
	    RDestroyImage(tile);

	    /* default titled icon */
	    tile = RCloneImage(scr->icon_tile);
	    if (!tile) {
		wWarning(_("error making icon: %s"), RErrorString);
		RDestroyImage(img);
		goto fallback;
	    }
#define TH scr->icon_title_font.height
	    RCombineArea(tile, img, 0, 0, img->width, img->height,
			 (ICON_WIDTH - img->width)/2,
			 TH + (ICON_HEIGHT - img->height - TH)/2);
	    if (!RConvertImage(scr->rcontext, tile, 
			       &scr->def_ticon_pixmap)) {
		wWarning(_("error making icon: %s"), RErrorString);
	    }
	    drawIconTitle(scr, scr->def_ticon_pixmap, TH);
#undef TH
	    RDestroyImage(img);
	    RDestroyImage(tile);
	fallback:
	    if (!scr->def_icon_pixmap) {
		scr->def_icon_pixmap = scr->icon_tile_pixmap;
	    }
	    if (!scr->def_ticon_pixmap) {
		scr->def_ticon_pixmap = 
		  XCreatePixmap(dpy, scr->w_win, ICON_WIDTH, ICON_HEIGHT,
				scr->w_depth);
		XSetClipMask(dpy, scr->copy_gc, None);
		XCopyArea(dpy, scr->icon_tile_pixmap, scr->def_ticon_pixmap, 
			  scr->copy_gc, 0, 0, ICON_WIDTH, ICON_HEIGHT, 0, 0);
		drawIconTitle(scr, scr->def_ticon_pixmap, title_h);
	    }
	}
	icon->our_pixmap = 1;
	pixmap = XCreatePixmap(dpy, scr->w_win, ICON_WIDTH, ICON_HEIGHT,
			       scr->w_depth);
	if (scr->def_icon_pixmap!=None) {
	    XSetClipMask(dpy, scr->copy_gc, None);
	    if (icon->show_title && icon->icon_name!=NULL)
	      XCopyArea(dpy, scr->def_ticon_pixmap, pixmap, scr->copy_gc, 
			0, 0, ICON_WIDTH, ICON_HEIGHT, 0, 0);
	    else
	      XCopyArea(dpy, scr->def_icon_pixmap, pixmap, scr->copy_gc, 0, 0, 
			ICON_WIDTH, ICON_HEIGHT, 0, 0);
	}
#ifdef METROBUG
	if (icon->pix_image)
	  XFreePixmap(dpy, icon->pix_image);
	icon->pix_image = pixmap;
#endif
    }
 
    /* draw user specified text */
    if (icon->icon_text && pixmap) {
	char *text;
	int x=2, y=2;
	text = wmalloc(strlen(icon->icon_text));
	sscanf(icon->icon_text, "%i,%i,%s", &x, &y, text);
	if (strlen(text)>0) {
	    if (icon->show_title && icon->icon_name!=NULL)
	      y+=(ICON_HEIGHT - 48 - title_h - 1);
#ifdef I18N_MB
	    XmbDrawString(dpy, pixmap, scr->icon_title_font.font, 
			  scr->icon_title_gc, x, y + scr->icon_title_font.y, 
			  text, strlen(text));
#else
	    XDrawString(dpy, pixmap, scr->icon_title_gc, x, 
			y + scr->icon_title_font.y, text, strlen(text));
#endif
	}
	wfree(text);
    }
#ifdef METROBUG
    if (icon->pix_image) {
	XSetWindowBackgroundPixmap(dpy, icon->core->window, icon->pix_image);
    }
#else /* !METROBUG */
    if (pixmap) {
	XSetWindowBackgroundPixmap(dpy, icon->core->window, pixmap);
	XFreePixmap(dpy, pixmap);
    }
#endif /* !METROBUG */
}


void
wIconPaint(WIcon *icon)
{
    WScreen *scr=icon->core->screen_ptr;
    GC gc = scr->icon_title_gc;
    int x;
    char *tmp;

    XClearWindow(dpy, icon->core->window);
    
    /* draw the icon title */
    if (icon->show_title && icon->icon_name!=NULL) {
	int l;
	int w;
#ifdef I18N_MB
	l = strlen(icon->icon_name);
	w = MyTextWidth(scr->icon_title_font.font,icon->icon_name,l);
#else
	tmp = ShrinkString(&scr->icon_title_font, icon->icon_name, 
			   ICON_WIDTH-4);
	w = MyTextWidth(scr->icon_title_font.font, tmp, l=strlen(tmp));
#endif
	if (w > icon->core->width - 4)
	  x = (icon->core->width - 4) - w;
	else
	  x = (icon->core->width - w)/2;

#ifdef I18N_MB
 	XmbDrawString(dpy, icon->core->window, scr->icon_title_font.font, gc, 
 		      x, 1 + scr->icon_title_font.y, icon->icon_name, l);
#else
  	XDrawString(dpy, icon->core->window, gc, x, 
  		    1 + scr->icon_title_font.y, tmp, l);
	free(tmp);
#endif
    }
}


/******************************************************************/
static void 
miniwindowDblClick(WObjDescriptor *desc, XEvent *event)
{
    WIcon *icon = desc->parent;

    /* It hangs WindowMaker
    if (event->xbutton.button == Button3) {
	WWindow *wwin = icon->owner;
	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 */ {
	wDeiconifyWindow(icon->owner);
    }
}


static void 
miniwindowMouseDown(WObjDescriptor *desc, XEvent *event)
{
    WIcon *icon = desc->parent;
    WWindow *wwin = icon->owner;
    XEvent ev;
    int x=wwin->icon_x, y=wwin->icon_y;
    int dx=event->xbutton.x, dy=event->xbutton.y;
    int grabbed=0;

#ifdef DEBUG
    puts("Moving miniwindow");
#endif
    if (event->xbutton.state & Mod1Mask)
      wLowerFrame(icon->core);
    else
      wRaiseFrame(icon->core);

    if (XGrabPointer(dpy, icon->core->window, False, ButtonMotionMask
		     |ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, 
		     None, CurrentTime) !=GrabSuccess) {
#ifdef DEBUG0
	wWarning("pointer grab failed for icon move");
#endif
    }
    while(1) {
	XMaskEvent(dpy, PointerMotionMask|ButtonReleaseMask|ButtonMotionMask
		   |ExposureMask, &ev);
	switch (ev.type) {
	 case Expose:
	    DispatchEvent(&ev);
	    break;

	 case MotionNotify:
	    if (!grabbed) {
		if (abs(dx-ev.xmotion.x)>=MOVE_THRESHOLD
		    || abs(dy-ev.xmotion.y)>=MOVE_THRESHOLD) {
		    XChangeActivePointerGrab(dpy, ButtonMotionMask
					     |ButtonReleaseMask, 
					     wCursor[WCUR_MOVE], CurrentTime);
		    grabbed=1;
		} else {
		    break;
		}
	    }
	    x = ev.xmotion.x_root - dx;
	    y = ev.xmotion.y_root - dy;
	    XMoveWindow(dpy, icon->core->window, x, y);
	    break;

	 case ButtonRelease:
	    if (wwin->icon_x!=x || wwin->icon_y!=y)
	      wwin->flags.icon_moved = 1;

	    XMoveWindow(dpy, icon->core->window, x, y);

	    wwin->icon_x = x;
	    wwin->icon_y = y;
#ifdef DEBUG
	    puts("End miniwindow move");
#endif
	    XUngrabPointer(dpy, CurrentTime);
	    return;
	    
	}
    }
}
    
