/*
 * stepgfx - Generic graphics and misc. utility module
 * 
 * Copyright (c) 1996 Alfredo K. Kojima (kojima@inf.ufrgs.br)
 * 
 * This module 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 <X11/Xlib.h>
#include <stdlib.h>
#include <stdio.h>
#include "../configure.h"

#include "afterstep.h"
#include <X11/Xatom.h>
#include "misc.h"

#include "screen.h"
#include "hashtable.h"

static int table_initialized=0;
static HashTable ColorTable;
static Visual *ScreenVisual;
static int rshift, gshift, bshift;
static int Bits;

static unsigned long
getTrueColor(XColor *color)
{
    unsigned long pixel;
    int r, g, b;

    r = (rshift < 0 ? color->red << (-rshift) : color->red >> rshift);

    g = (gshift < 0 ? color->green << (-gshift) : color->green >> gshift);

    b = (bshift < 0 ? color->blue << (-bshift) : color->blue >> bshift); 

    pixel = ((r & ScreenVisual->red_mask) 
		    | (g & ScreenVisual->green_mask) 
		    | (b & ScreenVisual->blue_mask));

    return pixel;
}


static int 
get_shifts(unsigned long mask)
{
    int i=0;
    
    while (mask) {
	mask>>=1;
	i++;
    }
    return i-1;
}

/*
 * Allocates a color or get's it from the color cache.
 * If the display is TrueColor, make the color by ourselves.
 */
static int
MyAllocColor(Display *dpy, Colormap cmap, XColor *color)
{
    unsigned long key, r, g, b;
    
    if (!table_initialized) {
	table_initialized=1;
	table_init(&ColorTable);
	ScreenVisual=DefaultVisual(dpy, Scr.screen);
	if (ScreenVisual->class==TrueColor) {
	    rshift = 15 - get_shifts(ScreenVisual->red_mask);
	    gshift = 15 - get_shifts(ScreenVisual->green_mask);
	    bshift = 15 - get_shifts(ScreenVisual->blue_mask);
	}
	Bits = 16 - ScreenVisual->bits_per_rgb;
    }
    if (ScreenVisual->class==TrueColor) {
	/* make true color ourself */
	color->pixel = getTrueColor(color);
	return 1;
    } else {
	r = color->red>>Bits;
	g = color->green>>Bits;
	b = color->blue>>Bits;
	key = r*r+g*g+b*b;
	
	if (!table_iget(&ColorTable, key, &color->pixel)) {
	    /* color not in cache, have to allocate this new color */
	    if (XAllocColor(dpy, cmap, color)==0)
	      return 0;
	    /* put color in cache */
	    table_iput(&ColorTable, key, color->pixel);
	}
	return 1;
    }
}




/* Masks to apply to color components when allocating colors 
 * you may want to set them to 0xffff if your display supports 16bpp+
 */
#define RED_MASK	0xff00
#define GREEN_MASK	0xff00
#define BLUE_MASK	0xff00
/*
 *********************************************************************** 
 * Allocates colors and fill a pixel value array. Color values are 
 * truncated with RED_MASK, GREEN_MASK and BLUE_MASK
 * 
 * from,to are the primary colors to use
 * maxcols is the number of colors
 * colors is the array of pixel values
 * light,dark are the colors for the relief
 * dr,dg,db are the increment values for the colors
 * alloc_relief if colors for relief should be allocated
 * incr - 0 if gradient is light to dark, 1 if dark to light
 ***********************************************************************
 */
int MakeColors(Display *dpy, Drawable d, int from[3], int to[3], int maxcols, 
	       unsigned long *colors, unsigned long *dark,
	       unsigned long *light, int dr, int dg, int db,
	       int alloc_relief, int incr)
{
    float rv, gv, bv;
    float sr,sg,sb;
    int red, green, blue;
    XColor color;
    int i;

    sr = (float)dr / (float)maxcols;
    sg = (float)dg / (float)maxcols;
    sb = (float)db / (float)maxcols;    
    rv = (float)from[0];
    gv = (float)from[1];
    bv = (float)from[2];
    /* kludge to frce color allocation in the first iteration on any case */
    color.red = (from[0] == 0) ? 0xffff : 0;
    color.green = 0;
    color.blue = 0;
    for(i = 0; i < maxcols; i++) {
	/* color allocation saver */	
	red = ((short)rv) & RED_MASK;
	green = ((short)gv) & GREEN_MASK;
	blue = ((short)bv) & BLUE_MASK;
	if (color.red != red || color.green != green || color.blue != blue) {
	    color.red = red;
	    color.green = green;
	    color.blue = blue;
	    if (MyAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
			&color)==0) {
	        return 0;
	    }
	}
	colors[i] = color.pixel;
	rv += sr;
	gv += sg;
	bv += sb;
	rv = ((rv > 65535.0) || (rv < 0.0)) ? rv -= sr : rv;
	gv = ((gv > 65535.0) || (gv < 0.0)) ? gv -= sg : gv;
	bv = ((bv > 65535.0) || (bv < 0.0)) ? bv -= sb : bv;
    }
    /* allocate 2 colors for the bevel */
    if (alloc_relief) {
	if (incr) {
	    rv = (float)from[0] * 0.8;
	    gv = (float)from[1] * 0.8;
	    bv = (float)from[2] * 0.8;
	} else {
	    rv = (float)to[0] * 0.8;
	    gv = (float)to[1] * 0.8;
	    bv = (float)to[2] * 0.8;
	}
	color.red = (unsigned short)(rv<0?0:rv);
    color.green = (unsigned short)(gv<0?0:gv);
	color.blue = (unsigned short)(bv<0?0:bv);		
	if (MyAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),&color))
	  *dark = color.pixel;
	else
	  *dark = colors[incr ? 0 : maxcols-1];
	
	if (incr) {
	    float avg;
	    avg = (float)(to[0]+to[1]+to[2])/3;	
	    rv = avg + (float)(to[0]/2);
	    gv = avg + (float)(to[1]/2);
	    bv = avg + (float)(to[2]/2);
	} else {
            float avg; 	
	    avg = (float)(from[0]+from[1]+from[2])/3;
	    rv = avg + (float)(from[0]/2);
	    gv = avg + (float)(from[1]/2);
	    bv = avg + (float)(from[2]/2);
	}
	color.red = (unsigned short)(rv>65535.0 ? 65535.0:rv);
    color.green = (unsigned short)(gv>65535.0 ? 65535.0:gv);
	color.blue = (unsigned short)(bv>65535.0 ? 65535.0:bv);
	if (MyAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)), 
			&color))
	  *light = color.pixel;
	else
	  *light = colors[incr ? maxcols-1 : 0];
    } else {
	*light = colors[incr ? maxcols-1 : 0];
	*dark = colors[incr ? 0 : maxcols-1];   
    }    
    return 1;
}


/*
 ************************************************************************
 * 
 * Draws a texture similar to Wharf's button background
 *
 * from: r,g,b values of top left color
 * to: r,g,b values of bottom right color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: # of max colors to use
 * 
 * Aborts and returns 0 on error
 ************************************************************************
 */

int DrawDegradeRelief(Display *dpy, Drawable d, int x, int y, int w, int h, 
		       int from[3], int to[3], int relief, int maxcols)
{
    int		i,j,k;
    int		dr, dg, db;  /* delta */
    int		dmax, dmax2;
    GC		gc, gc2;
    XGCValues	gcv;
    int		px, py, pd;
    unsigned long lightcolor, darkcolor;
    int 	alloc_relief;
    unsigned long  *colors;
    float	c1,s1;

    if (w <= 0 || h <= 0)
      return 0;

    gcv.foreground = 1;
    gc = XCreateGC(dpy, d, GCForeground, &gcv);
    gc2 = XCreateGC(dpy, d, GCForeground, &gcv);

    dr = to[0] - from[0];
    dg = to[1] - from[1];
    db = to[2] - from[2];
    
    dmax = abs(dr);
    dmax2 = dr;
    if (dmax < abs(dg)) {
	dmax = abs(dg);
	dmax2 = dg;
    }
    if (dmax < abs(db)) {
	dmax = abs(db);
	dmax2 = db;
    }    
    /* if colors from and to are the same, force to one color */
    if (dmax==0)  dmax=1;

    if (relief!=0) {
	if (maxcols>4) {
	    alloc_relief=1;
	    maxcols -= 2; /* reserve two colors for the relief */
	} else {
	    alloc_relief=0;
	}
    } else
      alloc_relief = 0;
    
    if (dmax < maxcols) {
	maxcols = dmax; /* we need less colors than we expected */
    }
    
    colors = malloc(sizeof(unsigned long)*maxcols);
    /* alloc colors */
    if (!MakeColors(dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
	       dr, dg, db, alloc_relief, dmax2>0))
      return 0;
    
    /* display it */
    s1 = (float)(maxcols)/((float)w*2.0);
    pd = relief==0 ? 0 : 1;
    for(py = pd; py < h-pd; py++) {
	c1 = ((float)maxcols/2.0)*((float)py/(float)((h-pd*2)-1));
	j = -1;
	k=pd+x;
	for(px = pd+x; px < w-pd+x; px++) {
	    i = (int)c1;
	    if (i>=maxcols) i=maxcols-1;
	    if (j != colors[i]) {
		j = colors[i];
		XSetForeground(dpy, gc, j);
		XDrawLine(dpy, d, gc, k, y+py, px, y+py);
		k=px;
	    }
	    c1+=s1;
	}
	XSetForeground(dpy, gc, colors[i]);
	XDrawLine(dpy, d, gc, k, y+py, w-pd+x, y+py);
    }
    /* draw borders */
    if (relief != 0) {
	if (relief>0) {
	    XSetForeground(dpy, gc, lightcolor);
	    XSetForeground(dpy, gc2, darkcolor);
	} else {
	    XSetForeground(dpy, gc, darkcolor);
	    XSetForeground(dpy, gc2, lightcolor);	    
	}
	XDrawLine(dpy, d, gc, x, y, x, y+h-1);
	XDrawLine(dpy, d, gc, x, y, x+w-1, y);
	XDrawLine(dpy, d, gc2, x, y+h-1, x+w-1, y+h-1);
	XDrawLine(dpy, d, gc2, x+w-1, y, x+w-1, y+h-1);
    }
    XFreeGC(dpy,gc);
    XFreeGC(dpy,gc2);
    free(colors);
    return 1;
}


/*
 ************************************************************************
 * 
 * Draws a horizontal gradient
 *
 * from: r,g,b values of top color
 * to: r,g,b values of bottom color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: max colors to use
 * type: gradient type. 0 for one-way, != 0 for cilindrical
 * 
 * aborts and returns 0 on error.
 ************************************************************************
 */
int DrawHGradient(Display *dpy, Drawable d, int x, int y, int w, int h, 
		       int from[3], int to[3], int relief, int maxcols,
		       int type)
{
    int		i,j;
    XGCValues	gcv;
    GC		gc, gc2;
    int		dr,dg,db;
    float	s,c;
    int 	dmax;
    unsigned long  *colors, lightcolor, darkcolor;
    int 	py,pd;
    int 	alloc_relief;
    
    if (w <= 1 || h <= 1)
      return 0;

    gcv.foreground = 1;
    gc = XCreateGC(dpy, d, GCForeground, &gcv);
    gc2 = XCreateGC(dpy, d, GCForeground, &gcv);
    
    dr = to[0] - from[0];
    dg = to[1] - from[1];
    db = to[2] - from[2];
    
    dmax = dr;
    if (abs(dmax) < abs(dg)) {
	dmax = dg;
    }
    if (abs(dmax) < abs(db)) {
	dmax = db;
    }
    if (relief!=0) {
	if (maxcols>4) {
	    alloc_relief=1;
	    maxcols -= 2; /* reserve two colors for the relief */
	} else {
	    alloc_relief=0;
	}
    } else
      alloc_relief=0;
    
    if (type) {
	if (h/2 < maxcols) {
	    maxcols = h/2; /* we need less colors than we expected */
	}
    } else {
	if (h < maxcols) {
	    maxcols = h; /* we need less colors than we expected */
	}
    }
    /* alloc colors */
    colors=malloc(maxcols*sizeof(unsigned long));
    if (!MakeColors(dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
	       dr, dg, db, alloc_relief, dmax>0))
      return 0;
    /* display it */
    if (type) {
	s = ((float)maxcols*2)/(float)h;
    } else {
	s = (float)maxcols/(float)h;	
    }    
    pd = relief==0 ? 0 : 1;
    c=0;
    j=-1;
    if (type==0) { /* one-way */
	for(py = pd; py < h-pd; py++) {	
	    i=(int)c;
	    if (i>=maxcols) i=maxcols-1;
	    if (j != i) {
		XSetForeground(dpy, gc, colors[i]);
	    }
	    XDrawLine(dpy, d, gc, x+pd, y+py, x+w-1-pd, y+py);
	    j = i;
	    c += s;
	}
    } else { /* cylindrical */
	for(py = pd; py < h-pd; py++) {	
	    i=(int)c;
	    if (i>=maxcols) i=maxcols-1;
	    if (j != i) {
		XSetForeground(dpy, gc, colors[i]);
	    }
	    XDrawLine(dpy, d, gc, x+pd, y+py, x+w-1-pd, y+py);
	    j = i;
	    if (py == h/2) s = -s;
	    c += s;
	}
    }    
    /* draw borders */
    if (relief != 0) {
	if (relief>0) {
	    XSetForeground(dpy, gc, lightcolor);
	    XSetForeground(dpy, gc2, darkcolor);
	} else {
	    XSetForeground(dpy, gc, darkcolor);
	    XSetForeground(dpy, gc2, lightcolor);	    
	}
	XDrawLine(dpy, d, gc, x, y, x, y+h-1);
	XDrawLine(dpy, d, gc, x, y, x+w-1, y);
	XDrawLine(dpy, d, gc2, x, y+h-1, x+w-1, y+h-1);
	XDrawLine(dpy, d, gc2, x+w-1, y, x+w-1, y+h-1);
    }
    XFreeGC(dpy,gc);
    XFreeGC(dpy,gc2);
    free(colors);
    return 1;
}


/*
 ************************************************************************
 * 
 * Draws a vertical gradient
 *
 * from: r,g,b values of left color
 * to: r,g,b values of right color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: max colors to use
 * type: gradient type. 0 for one-way, != 0 for cilindrical
 * 
 * aborts and returns 0 on error.
 ************************************************************************
 */
int DrawVGradient(Display *dpy, Drawable d, int x, int y, int w, int h, 
		       int from[3], int to[3], int relief, int maxcols,
		       int type)
{
    int		i,j;
    XGCValues	gcv;
    GC		gc, gc2;
    int		dr,dg,db;
    float	s,c;
    int 	dmax;
    unsigned long  *colors, lightcolor, darkcolor;
    int 	px,pd;
    int 	alloc_relief;
    
    if (w <= 1 || h <= 1)
      return 0;

    gcv.foreground = 1;
    gc = XCreateGC(dpy, d, GCForeground, &gcv);
    gc2 = XCreateGC(dpy, d, GCForeground, &gcv);
    
    dr = to[0] - from[0];
    dg = to[1] - from[1];
    db = to[2] - from[2];
    
    dmax = dr;
    if (abs(dmax) < abs(dg)) {
	dmax = dg;
    }
    if (abs(dmax) < abs(db)) {
	dmax = db;
    }
    if (relief!=0) {
	if (maxcols>4) {
	    alloc_relief=1;
	    maxcols -= 2; /* reserve two colors for the relief */
	} else {
	    alloc_relief=0;
	}
    } else
      alloc_relief=0;
    
    if (type) {
	if (w/2 < maxcols) {
	    maxcols = w/2; /* we need less colors than we expected */
	}
    } else {
	if (w < maxcols) {
	    maxcols = w; /* we need less colors than we expected */
	}
    }
    /* alloc colors */
    colors=malloc(maxcols*sizeof(unsigned long));
    if (!MakeColors(dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
	       dr, dg, db, alloc_relief, dmax>0))
      return 0;
    /* display it */
    if (type) {
	s = ((float)maxcols*2)/(float)w;
    } else {
	s = (float)maxcols/(float)w;	
    }    
    pd = relief==0 ? 0 : 1;
    c=0;
    j=-1;
    if (type==0) { /* one-way */
	for(px = pd; px < w-pd; px++) {	
	    i=(int)c;
	    if (i>=maxcols) i=maxcols-1;
	    if (j != i) {
		XSetForeground(dpy, gc, colors[i]);
	    }
	    XDrawLine(dpy, d, gc, x+px, y+pd, x+px, y+h-pd-1);
	    j = i;
	    c += s;
	}
    } else { /* cylindrical */
	for(px = pd; px < w-pd; px++) {	
	    i=(int)c;
	    if (i>=maxcols) i=maxcols-1;
	    if (j != i) {
		XSetForeground(dpy, gc, colors[i]);
	    }
	    XDrawLine(dpy, d, gc, x+px, y+pd, x+px, y+h-pd-1);
	    j = i;
	    if (px == w/2) s = -s;
	    c += s;
	}
    }
    /* draw borders */
    if (relief != 0) {
	if (relief>0) {
	    XSetForeground(dpy, gc, lightcolor);
	    XSetForeground(dpy, gc2, darkcolor);
	} else {
	    XSetForeground(dpy, gc, darkcolor);
	    XSetForeground(dpy, gc2, lightcolor);	    
	}
	XDrawLine(dpy, d, gc, x, y, x, y+h-1);
	XDrawLine(dpy, d, gc, x, y, x+w-1, y);
	XDrawLine(dpy, d, gc2, x, y+h-1, x+w-1, y+h-1);
	XDrawLine(dpy, d, gc2, x+w-1, y, x+w-1, y+h-1);
    }
    XFreeGC(dpy,gc);
    XFreeGC(dpy,gc2);
    free(colors);
    return 1;
}


/************************************************************************
 * 
 * Draws text with a texture
 * 
 * d - target drawable
 * font - font to draw text
 * x,y - position of text
 * gradient - texture pixmap. size must be at least as large as text
 * text - text to draw
 * chars - chars in text
 ************************************************************************/
#ifdef I18N
void DrawTexturedText(Display *dpy, Drawable d, XFontStruct *font, XFontSet fontset,
		      int x, int y, Pixmap gradient, char *text, int chars)
#else
void DrawTexturedText(Display *dpy, Drawable d, XFontStruct *font,
		      int x, int y, Pixmap gradient, char *text, int chars)
#endif
{
    Pixmap mask;
    int w,h;
    GC gc;
    XGCValues gcv;
    
    /* make the mask pixmap */
#ifdef I18N
    w = XmbTextEscapement(fontset,text,chars);
#else
    w = XTextWidth(font,text,chars);
#endif
    h = font->ascent+font->descent;
    mask=XCreatePixmap(dpy,DefaultRootWindow(dpy),w+1,h+1,1);
	gcv.foreground = 0;
	gcv.function = GXcopy;
    gcv.font = font->fid;	
	gc = XCreateGC(dpy,mask,GCFunction|GCForeground|GCFont,&gcv);
    XFillRectangle(dpy,mask,gc,0,0,w,h);
	XSetForeground(dpy,gc,1);
#ifdef I18N
/* ref: misc.h */
    XmbDrawString(dpy,mask,fontset,gc,0,font->ascent,text,chars);
#else
    XDrawString(dpy,mask,gc,0,font->ascent,text,chars);
#endif
	XFreeGC(dpy,gc);
	/* draw the texture */
	gcv.function=GXcopy;
	gc = XCreateGC(dpy,d,GCFunction,&gcv);
    XSetClipOrigin(dpy,gc,x,y);
    XSetClipMask(dpy,gc,mask);
    XCopyArea(dpy,gradient,d,gc,0,0,w,h,x,y);
    XFreeGC(dpy,gc);
    XFreePixmap(dpy,mask);
}




int MakeShadowColors(Display *dpy, int from[3], int to[3],
		     unsigned long *dark, unsigned long *light)
{
    float rv, gv, bv;
    XColor color;
    int i;
	int incr;
	int dr,dg,db,dmax;

    dr = to[0] - from[0];
    dg = to[1] - from[1];
    db = to[2] - from[2];
    
    dmax = dr;
    if (abs(dmax) < abs(dg)) {
		dmax = dg;
    }
    if (abs(dmax) < abs(db)) {
		dmax = db;
    }
	incr=(dmax>0);
    /* allocate 2 colors for the bevel */    
	if (incr) {
	    rv = (float)from[0] * 0.8;
	    gv = (float)from[1] * 0.8;
	    bv = (float)from[2] * 0.8;
	} else {
	    rv = (float)to[0] * 0.8;
	    gv = (float)to[1] * 0.8;
	    bv = (float)to[2] * 0.8;
	}
	color.red = (short)(rv<0?0:rv);
    color.green = (short)(gv<0?0:gv);
	color.blue = (short)(bv<0?0:bv);
	if (MyAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),&color))
	  *dark = color.pixel;
	else
	  return 0;
	
	if (incr) {
	    float avg;
	    avg = (float)(to[0]+to[1]+to[2])/3;	
	    rv = avg + (float)(to[0]/2);
	    gv = avg + (float)(to[1]/2);
	    bv = avg + (float)(to[2]/2);
	} else {
        float avg; 	
	    avg = (float)(from[0]+from[1]+from[2])/3;
	    rv = avg + (float)(from[0]/2);
	    gv = avg + (float)(from[1]/2);
	    bv = avg + (float)(from[2]/2);
	}
	color.red = (unsigned short)(rv>65535.0 ? 65535.0:rv);
    color.green = (unsigned short)(gv>65535.0 ? 65535.0:gv);
	color.blue = (unsigned short)(bv>65535.0 ? 65535.0:bv);	
	if (MyAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
					&color))
	  *light = color.pixel;
	else
	  return 0;
    return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1