
/***************************************************************************
                          progressbar-renderer.c
                          ----------------------
    begin                : Mon Oct 6 2003
    copyright            : (C) 2003 by Tim-Philipp Müller
    email                : t.i.m at orange dot net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "core-conn.h" /* eek */
#include "progressbar-renderer.h"
#include "options.h"


#define PROGRESSBAR_MINIMUM_WIDTH     50
#define PROGRESSBAR_INVISIBLE_WIDTH   25
#define PROGRESSBAR_MINIMUM_HEIGHT    5

#define FIXED_WIDTH   100
#define FIXED_HEIGHT  10

#define EXTRA_PAD     1

/* Some boring function declarations: GObject type system stuff */

static void     gui_cell_renderer_progress_init       (GuiCellRendererProgress      *cellprogress);

static void     gui_cell_renderer_progress_class_init (GuiCellRendererProgressClass *klass);

static void     gui_cell_renderer_progress_get_property  (GObject                    *object,
                                                          guint                       param_id,
                                                          GValue                     *value,
                                                          GParamSpec                 *pspec);

static void     gui_cell_renderer_progress_set_property  (GObject                    *object,
                                                          guint                       param_id,
                                                          const GValue               *value,
                                                          GParamSpec                 *pspec);

static void     gui_cell_renderer_progress_finalize (GObject *gobject);


static void     gui_cell_renderer_progress_get_size   (GtkCellRenderer            *cell,
                                                       GtkWidget                  *widget,
                                                       GdkRectangle               *cell_area,
                                                       gint                       *x_offset,
                                                       gint                       *y_offset,
                                                       gint                       *width,
                                                       gint                       *height);

static void     gui_cell_renderer_progress_render     (GtkCellRenderer            *cell,
                                                       GdkWindow                  *window,
                                                       GtkWidget                  *widget,
                                                       GdkRectangle               *background_area,
                                                       GdkRectangle               *cell_area,
                                                       GdkRectangle               *expose_area,
                                                       guint                       flags);


enum
{
  PROP_GAPLIST = 1,
  PROP_PAUSED,
  PROP_COL,
};

static   gpointer parent_class;


/***************************************************************************
 *
 *  gui_cell_renderer_progress_get_type
 *
 *  Here we register our type with the GObject type system if we
 *   haven't done so yet. Everything else is done in the callbacks.
 *
 ***************************************************************************/

GType
gui_cell_renderer_progress_get_type (void)
{
  static GType cell_progress_type = 0;

  if (cell_progress_type)
    return cell_progress_type;

  if (1)
  {
    static const GTypeInfo cell_progress_info =
    {
      sizeof (GuiCellRendererProgressClass),
      NULL,                                                     /* base_init */
      NULL,                                                     /* base_finalize */
      (GClassInitFunc) gui_cell_renderer_progress_class_init,
      NULL,                                                     /* class_finalize */
      NULL,                                                     /* class_data */
      sizeof (GuiCellRendererProgress),
      0,                                                        /* n_preallocs */
      (GInstanceInitFunc) gui_cell_renderer_progress_init,
    };

    /* Derive from GtkCellRenderer */
    cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER,
                                                 "GuiCellRendererProgress",
                                                  &cell_progress_info,
                                                  0);
  }

  return cell_progress_type;
}

/***************************************************************************
 *
 *  gui_cell_renderer_progress_init_alloc_colour
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_init_alloc_colour (GdkColor *colours, guint32 rgb, guint colnum)
{
	colours[colnum].pixel = 0;
	colours[colnum].red   = (rgb&0xff0000) >> 8;
	colours[colnum].green = (rgb&0x00ff00) >> 0;
	colours[colnum].blue  = (rgb&0x0000ff) << 8;

	(void) gdk_colormap_alloc_color (gdk_colormap_get_system(), &colours[colnum], FALSE, TRUE);
}

/***************************************************************************
 *
 *  gui_cell_renderer_progress_init
 *
 *  Set some default properties of the parent (GtkCellRenderer).
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_init (GuiCellRendererProgress *cellrendererprogress)
{
  GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT;
  GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2;
  GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2;

	cellrendererprogress->gaplist = NULL;
	cellrendererprogress->paused = FALSE;

	cellrendererprogress->brushes_initialised = FALSE; /* do this when we have a widget */
}


/***************************************************************************
 *
 *  gui_cell_renderer_progress_class_init:
 *
 *  set up our own get_property and set_property functions, and
 *  override the parent's functions that we need to implement.
 *  And make our new "gaplist" property known to the type system.
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_class_init (GuiCellRendererProgressClass *klass)
{
  GtkCellRendererClass *cell_class   = GTK_CELL_RENDERER_CLASS(klass);
  GObjectClass         *object_class = G_OBJECT_CLASS(klass);

  parent_class           = g_type_class_peek_parent (klass);
  object_class->finalize = gui_cell_renderer_progress_finalize;

  /* Hook up functions to set and get our
   *   custom cell renderer properties */
  object_class->get_property = gui_cell_renderer_progress_get_property;
  object_class->set_property = gui_cell_renderer_progress_set_property;

  /* Override the two crucial functions that are the heart
   *   of a cell renderer in the parent class */
  cell_class->get_size = gui_cell_renderer_progress_get_size;
  cell_class->render   = gui_cell_renderer_progress_render;

  /* Install our very own properties */
  g_object_class_install_property (object_class,
                                   PROP_GAPLIST,
                                   g_param_spec_pointer("gaplist",
                                                        "GuiGapList",
                                                         "The gap list to draw a download progress bar from",
                                                         G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_PAUSED,
                                   g_param_spec_boolean("paused",
                                                        "Paused",
                                                         "Whether the gaplist should be drawn in paused state",
	                                                       FALSE,
                                                         G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_COL,
                                   g_param_spec_string("available-color",
                                                       "AvailableColor",
                                                       "Which colour the available parts should be rendered in",
	                                                     "green",
                                                       G_PARAM_WRITABLE));
}


/***************************************************************************
 *
 *  gui_cell_renderer_progress_finalize: free any resources here
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_finalize (GObject *object)
{
/*
  GuiCellRendererProgress *cellrendererprogress = GUI_CELL_RENDERER_PROGRESS(object);
*/

  /* FIXME: free any dynamically allocated resources here */
	/*        Do we have anything to free?                  */

  (* G_OBJECT_CLASS (parent_class)->finalize) (object);
}


/***************************************************************************
 *
 *  gui_cell_renderer_progress_get_property
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_get_property (GObject    *object,
                                         guint       param_id,
                                         GValue     *value,
                                         GParamSpec *psec)
{
  GuiCellRendererProgress  *cellprogress = GUI_CELL_RENDERER_PROGRESS(object);

  switch (param_id)
  {
    case PROP_GAPLIST:
      g_value_set_pointer(value, (gpointer) cellprogress->gaplist);
      break;

    case PROP_PAUSED:
      g_value_set_boolean(value, cellprogress->paused);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec);
      break;
  }
}


/***************************************************************************
 *
 *  gui_cell_renderer_progress_set_property
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_set_property (GObject      *object,
                                         guint         param_id,
                                         const GValue *value,
                                         GParamSpec   *pspec)
{
  GuiCellRendererProgress *cellprogress = GUI_CELL_RENDERER_PROGRESS (object);

  switch (param_id)
  {
    case PROP_GAPLIST:
      cellprogress->gaplist = g_value_get_pointer(value);
      break;

    case PROP_PAUSED:
      cellprogress->paused = g_value_get_boolean(value);
      break;

		case PROP_COL:
		{
			const gchar *colstr = g_value_get_string(value);
			if ((colstr) && g_ascii_strcasecmp(colstr, "blue") == 0)
			{
				cellprogress->progressbar_pens = cellprogress->progressbar_pens_blue;
			}
			else
			{
				if ((colstr) && g_ascii_strcasecmp(colstr, "green") != 0)
					g_warning("GuiProgressBar: colour '%s' not implemented. Using default colour 'green'.\n", colstr);

				cellprogress->progressbar_pens = cellprogress->progressbar_pens_green;
			}
			break;
		}

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
      break;
  }
}

/***************************************************************************
 *
 *  gui_cell_renderer_progress_new: return a new cell renderer instance
 *
 ***************************************************************************/

GtkCellRenderer *
gui_cell_renderer_progress_new (void)
{
  return g_object_new(GUI_TYPE_CELL_RENDERER_PROGRESS, NULL);
}


/***************************************************************************
 *
 *  gui_cell_renderer_progress_get_size
 *
 *  Calculate the size of our cell, taking into account
 *   padding and alignment properties of parent.
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_get_size (GtkCellRenderer *cell,
                                     GtkWidget       *widget,
                                     GdkRectangle    *cell_area,
                                     gint            *x_offset,
                                     gint            *y_offset,
                                     gint            *width,
                                     gint            *height)
{
  gint calc_width;
  gint calc_height;

	if (cell_area)
	{
	  calc_width  = (gint) cell->xpad * 2 + cell_area->width;
  	calc_height = (gint) cell->ypad * 2 + cell_area->height;
	}
	else
	{
	  calc_width  = (gint) cell->xpad * 2 + FIXED_WIDTH;
  	calc_height = (gint) cell->ypad * 2 + FIXED_HEIGHT;
	}

  if (width)
    *width = calc_width;

  if (height)
    *height = calc_height;

  if (cell_area)
  {
    if (x_offset)
    {
      *x_offset = cell->xalign * (cell_area->width - calc_width);
      *x_offset = MAX (*x_offset, 0);
    }

    if (y_offset)
    {
      *y_offset = cell->yalign * (cell_area->height - calc_height);
      *y_offset = MAX (*y_offset, 0);
    }
  }
}


/***************************************************************************
 *
 *  gui_cell_renderer_init_brushes
 *
 ***************************************************************************/

static void
gui_cell_renderer_init_brushes (GuiCellRendererProgress *cellprogress, GtkWidget *widget)
{
	GdkColor  green[PROGRESSBAR_PENS_N], blue[PROGRESSBAR_PENS_N];
	guint     i;

	/* Green colours --- remember these: darker: 0x06500C --- even darker: 0x05440B */
	gui_cell_renderer_progress_init_alloc_colour (green, 0xE0E0E0, PROGRESSBAR_PEN_PAUSED);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x000000, PROGRESSBAR_PEN_GOT_IT);
	gui_cell_renderer_progress_init_alloc_colour (green, 0xB40000, PROGRESSBAR_PEN_NOT_AVAILABLE);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x086510, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_128);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x096E11, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_64);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x0A7D13, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_32);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x0B8F16, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_16);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x0C9E19, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_8);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x0Eb61C, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_4);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x10C91F, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_2);
	gui_cell_renderer_progress_init_alloc_colour (green, 0x11D922, PROGRESSBAR_PEN_AVAILABLE);
	gui_cell_renderer_progress_init_alloc_colour (green, 0xA3A3A3, PROGRESSBAR_PEN_ASKED_FOR);

	/* Blue colours --- remember these: darker: 0x5081AC --- even darker: 0x404894 */
	gui_cell_renderer_progress_init_alloc_colour (blue, 0xE0E0E0, PROGRESSBAR_PEN_PAUSED);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x000000, PROGRESSBAR_PEN_GOT_IT);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0xB40000, PROGRESSBAR_PEN_NOT_AVAILABLE);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x598FBF, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_128);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x5D95C7, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_64);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x619CD0, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_32);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x72BAD7, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_16);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x76C0DE, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_8);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x7AC7E6, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_4);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x7FCFEF, PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_2);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0x87DDFF, PROGRESSBAR_PEN_AVAILABLE);
	gui_cell_renderer_progress_init_alloc_colour (blue, 0xA3A3A3, PROGRESSBAR_PEN_ASKED_FOR);


	for ( i = 0;  i < PROGRESSBAR_PENS_N;  ++i)
	{
		GdkGC  *newgc;

//		if (progressbar_pens[i])
//			g_object_unref(G_OBJECT(progressbar_pens[i]));

		newgc = gdk_gc_new(widget->window);
		cellprogress->progressbar_pens_green[i] = newgc;
		gdk_gc_set_foreground(newgc, &green[i]);

		newgc = gdk_gc_new(widget->window);
		cellprogress->progressbar_pens_blue[i] = newgc;
		gdk_gc_set_foreground(newgc, &blue[i]);
	}

	if (opt_get_bool(OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR))
		cellprogress->progressbar_pens = cellprogress->progressbar_pens_blue;
	else
		cellprogress->progressbar_pens = cellprogress->progressbar_pens_green;

	cellprogress->brushes_initialised = TRUE;
}

/******************************************************************************
 *
 *   progressbar_get_brush_from_value
 *
 *   takes the gap status and returns the right colour/brush
 *
 ***/

static inline GdkGC *
progressbar_get_brush_from_value (guint status, gboolean paused, GdkGC **pens)
{
	/* got it? */
	if ( status == GAPSTATUS_FILLED )
		return pens[PROGRESSBAR_PEN_GOT_IT];

	/* !status means 'not available' */
	if ( ((!status) && (paused)) || ((paused) && !gui_core_conn_is_newer_than(core,12,7,2003))) /* <= v49.5 */
		return pens[PROGRESSBAR_PEN_PAUSED];

	/* not available? */
	if (!status)
		return pens[PROGRESSBAR_PEN_NOT_AVAILABLE];

	/* asked for? */
	if ( (status-(status % GAPSTATUS_ASKED)) == GAPSTATUS_ASKED )
			return pens[PROGRESSBAR_PEN_ASKED_FOR];

	/* this is a core fsck-up - don't show error message any more */
	if (status >= GAPSTATUS_ASKED)
	{
		if (!gui_core_conn_is_newer_than(core,11,7,2003)) /* <= v49.5 */
			return pens[PROGRESSBAR_PEN_ASKED_FOR];
		else
			return pens[PROGRESSBAR_PEN_GOT_IT];
	}

	if (paused)
		return pens[PROGRESSBAR_PEN_PAUSED];

	/* means NOW (after previous checks): ---->  available  <---- (status = sources here) */
	if (status >= 128)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_128];

	if (status >= 64)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_64];

	if (status >= 32)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_32];

	if (status >= 16)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_16];

	if (status >= 8)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_8];

	if (status >= 4)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_4];

	if (status >= 2)
		return pens[PROGRESSBAR_PEN_AVAILABLE_MORE_THAN_2];

	return pens[PROGRESSBAR_PEN_AVAILABLE];
}


/***************************************************************************
 *
 *  gui_cell_renderer_render_progressbar
 *
 ***************************************************************************/

static void
gui_cell_renderer_render_progressbar (const progressbarGap *pbgaps,
                                      GdkDrawable *drawable,
                                      GtkWidget   *widget,
                                      guint        xbase,
                                      guint        ybase,
                                      guint        width,
                                      guint        height,
                                      gboolean     paused,
                                      GdkGC      **pens)
{
	guint      gapnum; //, lastx, x;
	GdkGC     *brush = NULL;
	gfloat     w, gap_width_in_pixels, fwidth;

	/* too small? assume they don't want a progress bar */
	if ( width <= PROGRESSBAR_INVISIBLE_WIDTH )
		return;

	if ( width < PROGRESSBAR_MINIMUM_WIDTH )
		width = PROGRESSBAR_MINIMUM_WIDTH;

	if ( height < PROGRESSBAR_MINIMUM_HEIGHT )
		height = PROGRESSBAR_MINIMUM_HEIGHT;

#if 0
	/* Alternative drawing algorithm */

	gapnum = 0;
	lastx = 0;
	fwidth = (gfloat) width;
	while ((pbgaps[gapnum].start | pbgaps[gapnum].end) != 0) /* terminating empty element? */
	{
		brush = progressbar_get_brush_from_value (pbgaps[gapnum].status, paused, pens);

		x = (gint)(pbgaps[gapnum].startrel*fwidth) + xbase;

		if (x == lastx && gapnum > 0 && pbgaps[gapnum].widthrel > pbgaps[gapnum-1].widthrel)
			++x;

		gdk_draw_rectangle ( drawable, brush, TRUE, x, 0 + ybase,
		                     (gint)(pbgaps[gapnum].widthrel*fwidth)+2, height );

		lastx = (gint)(pbgaps[gapnum].startrel*fwidth) + xbase;

		++gapnum;
	}
#endif

	/* Current drawing algorithm: */
	w = 0.0;
	gapnum = 0;
	fwidth = (gfloat) width;

	while ( w < width )
	{
		if ( (pbgaps[gapnum].start | pbgaps[gapnum].end) == 0) /* terminating empty element? */
			break;

		gap_width_in_pixels = pbgaps[gapnum].widthrel * fwidth;

		if ( gap_width_in_pixels <= 0 )
			gap_width_in_pixels = 1;

		brush = progressbar_get_brush_from_value (pbgaps[gapnum].status, paused, pens);

//		gdk_draw_rectangle ( drawable, brush, TRUE, (gint)w + xbase, 0 + ybase, (guint)(w+gap_width_in_pixels)+2, height );
		gdk_draw_rectangle ( drawable, brush, TRUE, (gint)w + xbase, 0 + ybase + EXTRA_PAD, (guint)gap_width_in_pixels+2, height);

		/* If we just drew a large gap, or the discrepancy between should- and is- x-position is to big, recalculate */
		if (gap_width_in_pixels > 15 || (w - (pbgaps[gapnum].endrel * fwidth)) > 10)
		{
			w = pbgaps[gapnum].endrel * fwidth;
		}
		else
		{
			while ( ((guint)(w + gap_width_in_pixels)) == (guint)w )
				gap_width_in_pixels += 0.2;

			w = w + gap_width_in_pixels;
		}

		++gapnum;
	}

}

/***************************************************************************
 *
 *  gui_cell_renderer_progress_render
 *
 ***************************************************************************/

static void
gui_cell_renderer_progress_render (GtkCellRenderer *cell,
                                   GdkWindow       *window,
                                   GtkWidget       *widget,
                                   GdkRectangle    *background_area,
                                   GdkRectangle    *cell_area,
                                   GdkRectangle    *expose_area,
                                   guint            flags)
{
  GuiCellRendererProgress *cellprogress = GUI_CELL_RENDERER_PROGRESS (cell);
  gint                     width, height;
  gint                     x_offset, y_offset;

	if (!cellprogress->gaplist)
		return;

  gui_cell_renderer_progress_get_size (cell, widget, cell_area, &x_offset, &y_offset, &width, &height);

	if (!cellprogress->brushes_initialised)
		gui_cell_renderer_init_brushes(cellprogress, widget);

	gui_cell_renderer_render_progressbar(cellprogress->gaplist, window, widget,
	                                     cell_area->x + x_offset + cell->xpad,
	                                     cell_area->y + y_offset + cell->ypad,
	                                     width - (cell->xpad*4), height - (cell->ypad*4) - (2*EXTRA_PAD),
	                                     cellprogress->paused,
	                                     cellprogress->progressbar_pens);
}



