/* Clementine Window Manager
   Copyright 2002 Dave Berton <db@mosey.org>

   based on aewm Copyright 1998-2001 Decklin Foster <decklin@red-bean.com>

   This program is free software; see LICENSE for details. */

#include "painter.h"
#include <cassert>
#include <algorithm>
#include <cmath>
#include <iostream>

#define DEF_FONT "fixed"
#define DEF_ACTIVE_FG   "white"
#define DEF_ACTIVE_BG   "slategrey"
#define DEF_INACTIVE_FG "black"
#define DEF_INACTIVE_BG   "grey"

Image::Image( Display* d, Pixmap p )
    : dpy(d), pixmap(p)
{
    assert(dpy);
}

Image::Image( const Image& other )
    : dpy(other.dpy), pixmap(other.pixmap)
{
    ((Image&)other).pixmap = None;
}

Image::~Image()
{
    if ( pixmap != None )
	XFreePixmap( dpy, pixmap );
}

Image& Image::operator=( const Image& other )
{
    dpy = other.dpy;
    pixmap = other.pixmap;
    ((Image&)other).pixmap = None;
    return *this;
}

///////////

Painter::Painter( Display* d, Window r, int s )
    : dpy(d), root(r), screen(s)
{
    assert(dpy);

    /* font */
    char* opt_font = DEF_FONT;
    font = XLoadQueryFont(dpy, opt_font);
    assert(font);


    /* GCs */
    char* opt_active_fg = DEF_ACTIVE_FG;
    char* opt_active_bg = DEF_ACTIVE_BG;
    char* opt_inactive_fg = DEF_INACTIVE_FG;
    char* opt_inactive_bg = DEF_INACTIVE_BG;
    
    XGCValues gv;
    XColor dummyc;
    XAllocNamedColor(dpy, DefaultColormap(dpy, screen), opt_active_fg, 
		     &active_fg, &dummyc);
    XAllocNamedColor(dpy, DefaultColormap(dpy, screen), opt_active_bg, 
		     &active_bg, &dummyc);
    XAllocNamedColor(dpy, DefaultColormap(dpy, screen), opt_inactive_fg, 
		     &inactive_fg, &dummyc);
    XAllocNamedColor(dpy, DefaultColormap(dpy, screen), opt_inactive_bg, 
		     &inactive_bg, &dummyc);
    // db more error checking for above 

    gv.function = GXcopy;
    gv.foreground = active_fg.pixel;
    gv.background = active_bg.pixel;
    gv.font = font->fid;
    gv.line_width = 1;
    gc = XCreateGC(dpy, root, 
		   GCFunction|GCForeground|GCBackground|GCFont|GCLineWidth, 
		   &gv);
    gv.function = GXinvert;
    gv.subwindow_mode = IncludeInferiors;
    invert_gc = XCreateGC(dpy, root, GCFunction|GCForeground|
			  GCSubwindowMode|GCLineWidth|GCFont, 
			  &gv);

    assert(gc);
    assert(invert_gc);
    look = new Look( dpy, screen );

}

Painter::~Painter()
{
    assert(look);
    delete look;
    XFreeFont(dpy, font);
    XFreeGC( dpy, gc );
    XFreeGC(dpy, invert_gc);
}

void Painter::setForeground( int r, int g, int b )
{
    RGBColor c = look->colorManager()->alloc( r, g, b );
    setForeground( c.pixel() );
}

void Painter::rubberBand( int x, int y, 
			  unsigned int width, unsigned int height )
{
    XDrawRectangle(dpy, root, invert_gc,
		   x, y, width, height );
}

void Painter::drawRectangle( Drawable d, 
			     int x, int y, int width, int height )
{
    XDrawRectangle(dpy, d, gc,
		   x, y, width, height );
}
    
void Painter::setForeground( unsigned long pixel )
{
    XSetForeground( dpy, gc, pixel );
}

void Painter::setBackground( unsigned long pixel )
{
    XSetBackground( dpy, gc, pixel );
}

void Painter::setForegroundBackground( unsigned long foreground, 
				       unsigned long background )
{
    setForeground( foreground );
    setBackground( background );
}

void Painter::setActiveColors()
{
    setForegroundBackground( active_fg.pixel, active_bg.pixel );
}

void Painter::setInactiveColors()
{
    setForegroundBackground( inactive_fg.pixel, inactive_bg.pixel );
}
    

void Painter::drawGradient( Drawable d,
			    int in_x, int in_y,
			    unsigned int in_width, unsigned int in_height, 
			    ColorSet cs,
			    bool horizontal, bool ascend )
{
    static int NCOLORSHADES=128; // this many shades in gradient

    int cxCap = in_width;
    int cyCap = in_height;

    // Get the intensity values for the ending colormap
    unsigned int r1, g1, b1;
    r1 = g1 = b1 = 111;
    cs.dark.RGB( r1, g1, b1 );
    
    // Get the intensity values for the begining color
    unsigned int r2, g2, b2;
    r2 = g2 = b2 = 111;
    cs.light.RGB( r2, g2, b2 );

    unsigned int r, g, b;
    r = g = b = 0;

/*
    diagonal
    int x = 0;
    int y = 0;
    int width = cxCap;
    int height = cyCap;
    int xDelta = std::max(width/NCOLORSHADES,1);
    int yDelta = std::max(height/NCOLORSHADES,1);
    for ( ; x < cxCap; x += xDelta ) {
	for ( y = 0; y < cyCap; y += yDelta ) {
	    r = (( r1 + (( r2 - r1) / 2.0)
		   * (((double) x / (double) width)
		      + ((double) y / (double) height)))
		 + 0.5);
	    g = (( g1 + (( g2 - g1) / 2.0)
		   * (((double) x / (double) width)
		      + ((double) y / (double) height)))
		 + 0.5);
	    b = ((b1 + ((b2 - b1) / 2.0)
		  * (((double) x / (double) width)
		     + ((double) y / (double) height)))
		 + 0.5);
	    setForeground( r, g, b );
	    fillRectangle( image.pixmap, x, y,
			   xDelta, yDelta );
	}
    }
    return;

*/
    if( horizontal )  { //paint horizontal rect;
	int x = cxCap + std::max(cxCap/NCOLORSHADES,1);	
	int w = x; // width of area to shade
	int xDelta= std::max(w/NCOLORSHADES,1);	// width of one shade band
	while ( x > in_x - xDelta ) {
	    x -= xDelta;
	    if (r1 > r2)
		r = r1 - (r1-r2)*(w-x)/w;
	    else
		r = r1 + (r2-r1)*(w-x)/w;
	    
	    if (g1 > g2)
		g = g1 - (g1-g2)*(w-x)/w;
	    else
		g = g1 + (g2-g1)*(w-x)/w;
	    
	    if (b1 > b2)
		b = b1 - (b1-b2)*(w-x)/w;
	    else
		b = b1 + (b2-b1)*(w-x)/w;

	    setForeground( r, g, b );
            if(ascend) { // Paint from  left to right;
		drawFill( d, x, in_y,
			  xDelta, cyCap );
	    } else  {   // Paint from  right to left;
		drawFill( d, in_width-x-xDelta, in_y,
			       xDelta, cyCap );
	    }
	}
    } else  {   //paint vertical rect;
	int y = cyCap + std::max(cyCap/NCOLORSHADES,1);	
	int w = y; // height of area to shade
	int yDelta= std::max(w/NCOLORSHADES,1);	// height of one shade band
	    //while (y >= yDelta) {
        while (y > in_y - yDelta) {
	    y -= yDelta;
	    if (r1 > r2)
		r = r1 - (r1-r2)*(w-y)/w;
	    else
		r = r1 + (r2-r1)*(w-y)/w;
	    
	    if (g1 > g2)
		g = g1 - (g1-g2)*(w-y)/w;
	    else
		g = g1 + (g2-g1)*(w-y)/w;
	    
	    if (b1 > b2)
		b = b1 - (b1-b2)*(w-y)/w;
	    else
		b = b1 + (b2-b1)*(w-y)/w;
	    
	    setForeground( r, g, b );
            if(ascend) { // Paint from top to bottom
		drawFill( d, in_x, y,
			       cxCap, yDelta );
	    } else  {   // Paint from bottom to top
		drawFill( d, in_x, in_height-y-yDelta,
			       cxCap, yDelta );
	    }
	}
    }


}

void Painter::drawFrame( Drawable d, ColorSet cs, bool reverse, 
			  int x, int y, int width, int height )
{
    if ( reverse )
	setForeground( cs.light.pixel() );
    else
	setForeground( cs.dark.pixel() );
    drawLine( d, x, y+height-1, x+width-1, y+height-1);
    drawLine( d,  x+width-1, y+1, x+width-1, y+height);
    if ( reverse )
	setForeground( cs.dark.pixel() );
    else
	setForeground( cs.light.pixel() );
    drawLine( d, x, y, x+width-1, y);
    drawLine( d, x, y+1, x, y+height);
    setForeground( cs.color.pixel() );
    drawPoint( d, x + width - 1, y );
    drawPoint( d, x, y + height-1 );
}

void Painter::drawFill( Drawable d, 
			int x, int y,
			int width, int height )
{
    XFillRectangle(dpy, d, gc, x, y, width, height);
}

void Painter::setBackground( Drawable w, const Image& image )
{
    XSetWindowBackgroundPixmap(dpy, w, image.pixmap );
    XClearWindow(dpy,w);
}


Image Painter::createBlankImage( int width, int height )
{
    Pixmap pixmap = XCreatePixmap(dpy,root, width,
				  height, DefaultDepth(dpy, screen) );
    if (pixmap == None) {
	std::cerr << "Painter::createBlankImage unable to create pixmap" << 
	    std::endl;
	return Image( dpy, None );
    }
    return Image(dpy,pixmap);
}

void Painter::drawString( Drawable d, bool active,
			  int x, int y, const std::string& s )
{
    RGBColor c;
    if ( active )
	c = look->activeFacet()->color().color;
    else
	c = look->inactiveFacet()->color().color;
    drawString( d, c, x, y, s );
}

void Painter::drawString( Drawable d, RGBColor c, 
			  int x, int y, const std::string& s )
{
    setForeground( c.pixel() );
    XDrawString( dpy, d, gc, x, y, (char*)s.c_str(), s.length() );
}


void Painter::drawBackground( Drawable d, Facet* facet,
			      bool down,
			      int width, int height )
{
    assert( facet );
    ColorSet cs;
    Style style;
    style = facet->style();
    int borderOffset = 0;
    if ( style.isFlat() ) {
	cs = facet->background();
	setForeground( cs.color.pixel() );
	drawFill( d, borderOffset, borderOffset,
		  width - 2*borderOffset, height );
    }
    if ( style.isGradient() ) {
	cs = facet->color();
	drawGradient( d, borderOffset, borderOffset,
		      width-(2*borderOffset), height, 
		      cs, style.isHorizontal(), style.isAscending() );
    }
    if ( style.isBeveled() ) {
	cs = facet->background();
	drawFrame( d, cs, down, 0, 0, width, height );
    }
}

void Painter::setTitlebarBackground( Drawable d, bool active, 
				     int width, int height )
{
    Image image = createBlankImage( width, height );
    Facet* f;
    if ( active )
	f = look->activeFacet();
    else 
	f = look->inactiveFacet();
    drawBackground( image.pixmap, f, false, width, height );
    setBackground( d, image );
}   

void Painter::setBorderBackground( Drawable d, bool active, 
				   int width, int height, int titleHeight )
{
    Image image = createBlankImage( width, height );
    ColorSet cs;
    if ( active )
	cs = look->activeFacet()->border();
    else
	cs = look->inactiveFacet()->border();
    setForeground( cs.color.pixel() );
    drawFill( image.pixmap,0,0,width,height);
//    drawRectangle( image.pixmap, 0, 0, width-1, height-1 );
//    drawLine( image.pixmap, 0, titleHeight-1, width, titleHeight-1 );
    setBackground( d, image );
}

void Painter::drawCloseControl( Drawable d, bool active,
				int x, int y, 
				int width, int height )
{
    Facet* f;
    if ( active )
	f = look->activeButtonFacet();
    else 
	f = look->inactiveButtonFacet();
    ColorSet cs = f->color();
    setForeground( cs.color.pixel() );
    drawLine( d, x, y, x + width, y + height );
    drawLine( d, x + 1, y, x + width, y + height - 1 );
    drawLine( d, x, y + 1, x + width -1 , y + height );

    drawLine( d, x, y + height-1, x + width, y-1 );
    drawLine( d, x, y + height - 2, x + width - 1, y-1 );
    drawLine( d, x + 1, y + height-1, x + width, y  );
}

void Painter::drawIconizeControl( Drawable d, bool active,
				  int x, int y, 
				  int width, int height )
{
    ColorSet cs;
    if ( active )
	cs = look->activeButtonFacet()->color();
    else
	cs = look->inactiveButtonFacet()->color();
    if ( std::fmod((float)width,2) == 0 )
	width--;
    height = width;
    int half = height/2;
    // use bottom half of area
    y += half - int(.2*height); // move down a bit
    height = half;              // reduce height
    setForeground( cs.color.pixel() );
    drawLine( d, x, y, x + (int) std::ceil((float)width/2) + 1, y + height + 1 );
    drawLine( d, x + 1, y, x + (int) std::ceil((float)width/2) + 1, y + height );
    drawLine( d, x, y + 1,  x + (int) std::ceil((float)width/2), y + height + 1 );
    drawLine( d, x + (int) std::ceil((float)width/2), y + height, x + width, y -1  );
    drawLine( d, x + (int) std::ceil((float)width/2), y + height-1, x +width-1, y-1 );
    drawLine( d, x + (int) std::ceil((float)width/2)+1, y + height, x +width, y );
    drawPoint( d, x + (int) std::ceil((float)width/2), y + height + 1 );
}

void Painter::setButtonBackground( Drawable d, Painter::Button b, bool active, 
				   bool down, int width, int height )
{
    Image image = createBlankImage( width, height );
    Facet* f;
    if ( active )
	f = look->activeButtonFacet();
    else 
	f = look->inactiveButtonFacet();
    /* This will handle the button background and border, 'depressing'
     * the button if down */
    drawBackground( image.pixmap, f, down, width, height );

    /* Paint the control differently if there is no bevel (hence, no
     * way to 'depress' the button), which provides feedback in this
     * case. */
    if ( down && !f->style().isBeveled() )
	active = !active;

    switch( b ) {
    case CLOSE_BUTTON:
	drawCloseControl( image.pixmap, active, 
			  4, 4, width-8, height-8 );
	break;
    case ICONIZE_BUTTON:
	drawIconizeControl( image.pixmap, active, 
			  4, 4, width-8, height-8 );
	break;
    }

    setBackground( d, image );    
}

void Painter::drawLine( Drawable d, int x1, int y1, int x2, int y2 )
{
    XDrawLine( dpy, d, gc, x1, y1, x2, y2 );
}

void Painter::drawPoint( Drawable d, int x, int y )
{
    XDrawPoint( dpy, d, gc, x, y );
}



syntax highlighted by Code2HTML, v. 0.9.1