/* GKrellM
|  Copyright (C) 1999-2021 Bill Wilson
|
|  Author:  Bill Wilson
|  Latest versions might be found at:  https://gkrellm.srcbox.net
|
|
|  GKrellM 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 3 of the License, or
|  (at your option) any later version.
|
|  GKrellM 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, see http://www.gnu.org/licenses/
|
|
|  Additional permission under GNU GPL version 3 section 7
|
|  If you modify this program, or any covered work, by linking or
|  combining it with the OpenSSL project's OpenSSL library (or a
|  modified version of that library), containing parts covered by
|  the terms of the OpenSSL or SSLeay licenses, you are granted
|  additional permission to convey the resulting work.
|  Corresponding Source for a non-source form of such a combination
|  shall include the source code for the parts of OpenSSL used as well
|  as that of the covered work.
*/

#include "gkrellm.h"
#include "gkrellm-private.h"

#include <assert.h>

static GkrellmBorder	zero_border;
static GkrellmMargin	zero_margin;

  /* Smooth the krell response with an exponential moving average.
  |  Eponential MA is quicker responding than pure moving average.
  |  a   =  2 / (period + 1)
  |  ema = ema + a * (reading - ema)
  |  Don't need floating point precision here, so do the int math in an
  |  order to minimize roundoff error and scale by 256 for some precision.
  */
static gint
exp_MA(GkrellmKrell *k)
	{
	gint	ema, p, reading, round_up;

	/* First, help the krell settle to zero and full scale. This gives
	|  on/off fast response while the ema is smoothing in between.
	*/
	if (k->reading == 0 && k->last_reading == 0)
		return 0;
	if (k->reading >= k->full_scale && k->last_reading >= k->full_scale)
		return k->full_scale;
	if (k->last_reading == 0)	/* Fast liftoff as well */
		return k->reading;
	ema = k->ema << 8;
	p   = k->period + 1;		/* Don't scale this! */
	reading = (k->reading) << 8;

	ema = ema + 2 * reading / p - 2 * ema / p;
	round_up = ((ema & 0xff) > 127) ? 1 : 0;

	return (ema >> 8) + round_up;
	}

  /* If the Krell has moved, redraw its layer on its stencil.
  */
void
gkrellm_update_krell(GkrellmPanel *p, GkrellmKrell *k, gulong value)
	{
	gint	xnew, x_src, y_src, x_dst, w1, w2, w_overlap, d, frame;

	if (!p || !k || k->full_scale == 0)
		return;
	k->value = value;
	if (value < k->previous)		/* unsigned long overflow? */
		{
		k->previous = value;
		return;
		}
	if (!k->monotonic)
		k->previous = 0;

	assert(value >= k->previous);
	const unsigned long value_diff = value - k->previous;
	k->reading = (value_diff > INT_MAX ? INT_MAX : (int)value_diff) * k->full_scale_expand;
	if (k->reading > k->full_scale)
		k->reading = k->full_scale;

	k->ema = (k->period > 1) ? exp_MA(k) : k->reading;
	if (k->monotonic)
		k->previous = value;
	k->last_reading = k->reading;

	const unsigned long scaled_ema = (unsigned long)k->ema * (unsigned long)k->w_scale / (unsigned long)k->full_scale;
	xnew = k->x0 + (scaled_ema > INT_MAX ? INT_MAX : (int)scaled_ema);

	if (xnew == k->x0 && k->reading)
		{
		xnew = k->x0 + 1;	/* Show something for all activity */
		k->ema = 1;
		}
	if (xnew == k->x_position)
		return;
	k->x_position = xnew;


	/* If the krell has depth, pick the frame to display as function of
	|  ema.  Depth == 2 means display frame 0 at xnew == 0, and frame 1
	|  everywhere else.  Depth > 2 means same thing for frame 0,
	|  display frame depth - 1 at xnew == full_scale, and display other
	|  frames in between at xnew proportional to ema/full_scale.
	*/
	d = k->depth;
	if (k->ema == 0 || xnew == k->x0) /* Krell can settle before ema*/
		frame = 0;
	else if (k->ema >= k->full_scale)
		frame = d - 1;
	else
		{
		if (d == 1)
			frame = 0;
		else if (d == 2)
			frame = 1;
		else
			frame = 1 + ((d - 2) * k->ema / k->full_scale);
		}
	y_src = k->h_frame * frame;
	if (k->bar_mode)
		{
		x_src = (k->bar_mode == KRELL_EXPAND_BAR_MODE_SCALED) ? 0 : k->x0;
		x_dst = k->x0;
		w_overlap = xnew - k->x0;
		}
	else
		{
		x_src = k->x_hot - (xnew - k->x0);
		if (x_src < 0)
			x_src = 0;
		x_dst = xnew - k->x_hot;
		if (x_dst < k->x0)
			x_dst = k->x0;
		w1 = k->w - x_src;
		w2 = (k->x0 + k->w_scale + 1) - x_dst;
		w_overlap = (w2 > w1) ? w1 : w2;
		}

	/* Clear the krell stencil bitmap and draw the mask.
	*/
	gdk_draw_rectangle(k->stencil, _GK.bit0_GC, TRUE,
				0,0,  k->w, k->h_frame);
	if (k->mask)
		gdk_draw_drawable(k->stencil, _GK.bit1_GC, k->mask,
				x_src, y_src, x_dst, 0, w_overlap, k->h_frame);
	else
		gdk_draw_rectangle(k->stencil, _GK.bit1_GC,
				TRUE, x_dst, 0, w_overlap, k->h_frame);

	if (!k->modified)		/* Preserve original old draw if there has been */
		k->old_draw = k->draw;	/* no intervening draw_panel_layers() */

	k->draw.x_src = x_src;
	k->draw.y_src = y_src;
	k->draw.x_dst = x_dst;
	k->draw.y_dst = k->y0;
	k->draw.w = w_overlap;
	k->draw.h = k->h_frame;

	k->modified = TRUE;
	}

void
gkrellm_decal_get_size(GkrellmDecal *d, gint *w, gint *h)
	{
	if (!d)
		return;
	if (w)
		*w = d->w;
	if (h)
		*h = d->h;
	}

void
gkrellm_draw_decal_on_chart(GkrellmChart *cp, GkrellmDecal *d,
			gint xd, gint yd)
	{
	PangoLayout			*layout;
	PangoRectangle		ink;
	GList				*list;
	GdkRectangle		rect, *r;
	GkrellmTextstyle	*ts;
	GkrellmText			*tx;
	gint				x, y, max;

	if (!cp || !d || d->state != DS_VISIBLE)
		return;
	d->x = xd;
	d->y = yd;

	if (d->text_list)
		{
		r = &d->ink;
		if (d->modified || d->chart_sequence_id != cp->bg_sequence_id)
			{
			gdk_draw_drawable(cp->bg_text_pixmap, _GK.draw1_GC, cp->bg_pixmap,
						d->x, d->y, d->x, d->y, d->w, d->h);
			layout = gtk_widget_create_pango_layout(
						gkrellm_get_top_window(), NULL);
			memset(r, 0, sizeof(GdkRectangle));
			rect.x = d->x;
			rect.y = d->y;
			rect.width = d->w;
			rect.height = d->h;
			gdk_gc_set_clip_rectangle(_GK.text_GC, &rect);
			for (list = d->text_list; list; list = list->next)
				{
				tx = (GkrellmText *) list->data;
				ts = &tx->text_style;
				pango_layout_set_font_description(layout, ts->font);
				if (d->flags & DF_TEXT_USE_MARKUP)
					pango_layout_set_markup(layout, tx->text,strlen(tx->text));
				else
					pango_layout_set_text(layout, tx->text, strlen(tx->text));
				x = d->x + tx->x_off;
				y = d->y + tx->y_off;
				if (ts->effect)
					{
					gdk_gc_set_foreground(_GK.text_GC, &ts->shadow_color);
					gdk_draw_layout_with_colors(cp->bg_text_pixmap,
							_GK.text_GC,
							x + 1, y + 1, layout,
							&ts->shadow_color, NULL);
					}
				gdk_gc_set_foreground(_GK.text_GC, &ts->color);
				gdk_draw_layout(cp->bg_text_pixmap, _GK.text_GC, x, y, layout);

				pango_layout_get_pixel_extents(layout, &ink, NULL);
				if (r->x == 0 || ink.x + x < r->x)	/* Shadow ??? */
					r->x = ink.x + x;
				if (r->y == 0 || ink.y + y < r->y)
					r->y = ink.y + y;
				max = ink.x + x + ink.width;
				if (r->x + r->width < max)
					r->width = max - r->x;
				max = ink.y + y + ink.height;
				if (r->y + r->height < max)
					r->height = max - r->y;
				}
			gdk_gc_set_clip_rectangle(_GK.text_GC, NULL);
			g_object_unref(layout);
			d->chart_sequence_id = cp->bg_sequence_id;
			d->modified = FALSE;
			}
		gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_text_pixmap,
					r->x, r->y, r->x, r->y, r->width, r->height);
		}
	else
		{
		gdk_gc_set_clip_mask(_GK.text_GC, d->stencil);
		gdk_gc_set_clip_origin(_GK.text_GC, xd, yd);
		gdk_draw_drawable(cp->pixmap, _GK.text_GC, d->pixmap,
					0, d->y_src, xd, yd, d->w, d->h);

		gdk_gc_set_clip_mask(_GK.text_GC, NULL);
		gdk_gc_set_clip_origin(_GK.text_GC, 0, 0);
		}
	}

void
gkrellm_draw_decal_pixmap(GkrellmPanel *p, GkrellmDecal *d, gint index)
	{
	gint	y_src;

	if (!d)
		return;
	if (d->value != index)
		{
		/* Write the new decal mask onto the panel stencil
		*/
		y_src = index * d->h;
		d->y_src = y_src;
		if (d->mask)		/* Can be NULL if no transparency	*/
			gdk_draw_drawable(d->stencil, _GK.bit1_GC, d->mask,
					0, y_src, 0, 0, d->w, d->h);
		else	/* Fill decal extent with white	*/
			gdk_draw_rectangle(d->stencil, _GK.bit1_GC,
					TRUE, 0, 0, d->w, d->h);
		d->modified = TRUE;
		}
	d->value = index;
	}

static void
decal_text_list_free(GList **text_list)
	{
	GList	*list;

	if (!text_list || !*text_list)
		return;
	for (list = *text_list; list; list = list->next)
		g_free(((GkrellmText *) list->data)->text);
	gkrellm_free_glist_and_data(text_list);
	}


  /* Draw to first GkrellmText in text_list and truncate text_list to make
  |  gkrellm_draw_decal_text() compatible with plugin code written before
  |  the text_list implementation.  For backwards compatibility, d->x_off
  |  shadows the first tx->x_off.
  |  Combining gkrellm_draw_decal_text() and gkrellm_decal_text_insert()
  |  functions on the same decal may lead to unexpected results.
  */
static void
gkrellm_draw_decal_internal(GkrellmPanel *p, GkrellmDecal *d, gchar *s,
			gint value)
	{
	GkrellmText			*tx;
	GList				*list;

	if (!s || !d || d->state == DS_INVISIBLE)
		return;
	if (!d->text_list)
		d->text_list = g_list_append(d->text_list, g_new0(GkrellmText, 1));
	list = d->text_list;
	tx = (GkrellmText *) list->data;
	if (   gkrellm_dup_string(&tx->text, s)
		|| tx->x_off_prev != d->x_off
		|| tx->text_style.font != d->text_style.font
		|| tx->text_style.color.pixel != d->text_style.color.pixel
	   )
		d->modified = TRUE;
	tx->x_off = d->x_off;
	tx->x_off_prev = d->x_off;
	tx->y_off = d->y_off - d->y_ink;
	tx->text_style = d->text_style;
	if (list->next)
		decal_text_list_free(&list->next);
	d->value = value;
	}

void
gkrellm_draw_decal_text(GkrellmPanel *p, GkrellmDecal *d, gchar *s, gint value)
	{
	d->flags &= ~DF_TEXT_USE_MARKUP;

	/* In case mixing scroll_text and draw_decal_text calls
	*/
	d->flags &= ~DF_SCROLL_TEXT_DIVERTED;
	if (d->scroll_text)
		{
		g_free(d->scroll_text);
		d->scroll_text = NULL;
		}

	gkrellm_draw_decal_internal(p, d, s, value);
	}

void
gkrellm_draw_decal_markup(GkrellmPanel *p, GkrellmDecal *d, gchar *s)
	{
	d->flags |= DF_TEXT_USE_MARKUP;

	/* In case mixing scroll_text and draw_decal_text calls
	*/
	d->flags &= ~DF_SCROLL_TEXT_DIVERTED;
	if (d->scroll_text)
		{
		g_free(d->scroll_text);
		d->scroll_text = NULL;
		}

	gkrellm_draw_decal_internal(p, d, s, 0);
	}

void
gkrellm_decal_text_get_offset(GkrellmDecal *d, gint *x, gint *y)
	{
	if (!d)
		return;
	if (x)
		*x = d->x_off;
	if (y)
		*y = d->y_off;
	}

  /* Setting offset of text decal means setting offset of first GkrellmText
  |  in text_list.  For backwards compatibility with plugin code that
  |  directly accesses d->x_off, shadow d->x_off to first tx->x_off.
  */
void
gkrellm_decal_text_set_offset(GkrellmDecal *d, gint x, gint y)
	{
	GList		*list;
	GkrellmText	*tx;

	if (!d || (d->x_off == x && d->y_off == y))
		return;

	d->x_off = x;
	d->y_off = y;
	if ((list = d->text_list) != NULL)
		{
		tx = (GkrellmText *) list->data;
		tx->x_off = x;
		tx->x_off_prev = x;
		tx->y_off = y - d->y_ink;;
		}
	d->modified = TRUE;
	}

void
gkrellm_decal_text_clear(GkrellmDecal *d)
	{
	GList		*list;
	GkrellmText	*tx;

	if (!d || ((list = d->text_list) == NULL))
		return;
	tx = (GkrellmText *) list->data;

	if (list->next)
		decal_text_list_free(&list->next);

	gkrellm_dup_string(&tx->text, "");
	tx->text_style = d->text_style;

	if (d->scroll_text)
		g_free(d->scroll_text);
	d->scroll_text = NULL;

	d->modified = TRUE;
	}

  /* Insert text into a decal's text_list at a given offset.  Using text
  |  insert functions makes d->x_off and d->y_off meaningless since offsets
  |  are in each GkrellmText's tx->x_off and tx->y_off.
  */
void
gkrellm_decal_text_insert(GkrellmDecal *d, gchar *s, GkrellmTextstyle *ts,
		gint x_off, gint y_off)
	{
	GkrellmText	*tx = NULL;

	if (!s || !d || d->state == DS_INVISIBLE)
		return;
	d->flags &= ~DF_TEXT_USE_MARKUP;
	if (!ts)
		ts = &d->text_style;
	if (   d->text_list
		&& !*((GkrellmText *) d->text_list->data)->text
	   )
		tx = (GkrellmText *) d->text_list->data;

	if (!tx)
		{
		tx = g_new0(GkrellmText, 1);
		d->text_list = g_list_append(d->text_list, tx);
		}

	gkrellm_dup_string(&tx->text, s);
	tx->x_off = x_off;
	tx->x_off_prev = x_off;
	tx->y_off = y_off - d->y_ink;
	tx->text_style = *ts;

	d->modified = TRUE;
	}

void
gkrellm_decal_text_markup_insert(GkrellmDecal *d, gchar *s,
		GkrellmTextstyle *ts, gint x_off, gint y_off)
	{
	GkrellmText	*tx = NULL;

	if (!s || !d || d->state == DS_INVISIBLE)
		return;
	d->flags |= DF_TEXT_USE_MARKUP;
	if (!ts)
		ts = &d->text_style;
	if (   d->text_list
		&& !*((GkrellmText *) d->text_list->data)->text
	   )
		tx = (GkrellmText *) d->text_list->data;

	if (!tx)
		{
		tx = g_new0(GkrellmText, 1);
		d->text_list = g_list_append(d->text_list, tx);
		}

	gkrellm_dup_string(&tx->text, s);
	tx->x_off = x_off;
	tx->x_off_prev = x_off;
	tx->y_off = y_off - d->y_ink;
	tx->text_style = *ts;

	d->modified = TRUE;
	}

void
gkrellm_decal_text_nth_inserted_set_offset(GkrellmDecal *d, gint n,
			gint x, gint y)
	{
	GList		*list;
	GkrellmText	*tx;

	if (!d || (list = g_list_nth(d->text_list, n)) == NULL)
		return;

	tx = (GkrellmText *) list->data;
	if (tx->x_off != x || tx->y_off != y)
		{
		tx->x_off = x;
		tx->x_off_prev = x;
		tx->y_off = y - d->y_ink;;
		d->modified = TRUE;
		}
	}

void
gkrellm_decal_text_nth_inserted_get_offset(GkrellmDecal *d, gint n,
			gint *x, gint *y)
	{
	GList		*list;
	GkrellmText	*tx;

	if (!d || (list = g_list_nth(d->text_list, n)) == NULL)
		return;

	tx = (GkrellmText *) list->data;
	if (x)
		*x = tx->x_off;
	if (y)
		*y = tx->y_off + d->y_ink;
	}

void
gkrellm_decal_scroll_text_align_center(GkrellmDecal *d, gboolean center)
	{
	gint	prev_center;

	if (!d)
		return;
	prev_center = d->flags & DF_TEXT_CENTER;
	if ((center && prev_center) || (!center && !prev_center))
		return;

	if (center)
		d->flags |= DF_TEXT_CENTER;
	else
		d->flags &= ~DF_TEXT_CENTER;
	d->modified = TRUE;
	}

void
gkrellm_decal_scroll_text_horizontal_loop(GkrellmDecal *d, gboolean loop)
	{
	gint	prev_loop;

	if (!d)
		return;
	prev_loop = d->flags & DF_SCROLL_TEXT_H_LOOP;
	if ((loop && prev_loop) || (!loop && !prev_loop))
		return;

	if (loop)
		d->flags |= DF_SCROLL_TEXT_H_LOOP;
	else
		d->flags &= ~DF_SCROLL_TEXT_H_LOOP;
	d->modified = TRUE;
	}

void
gkrellm_decal_scroll_text_vertical_loop(GkrellmDecal *d, gboolean loop)
	{
	gint	prev_loop;

	if (!d)
		return;
	prev_loop = d->flags & DF_SCROLL_TEXT_V_LOOP;
	if ((loop && prev_loop) || (!loop && !prev_loop))
		return;

	if (loop)
		d->flags |= DF_SCROLL_TEXT_V_LOOP;
	else
		d->flags &= ~DF_SCROLL_TEXT_V_LOOP;
	d->modified = TRUE;
	}

void
gkrellm_decal_scroll_text_get_size(GkrellmDecal *d, gint *w, gint *h)
	{
	if (!d)
		return;
	if (w)
		*w = d->scroll_width;
	if (h)
		*h = d->scroll_height
					+ ((d->flags & DF_SCROLL_TEXT_V_LOOP) ? d->y_ink : 0);
	}

static PangoLayout *
decal_scroll_text_layout(GkrellmDecal *d, gchar *text, gint *yink)
	{
	PangoLayout			*layout;
	PangoRectangle		ink, logical;
	GkrellmTextstyle	*ts;
	gint				y_ink;

	ts = &d->text_style;
	layout = gtk_widget_create_pango_layout(gkrellm_get_top_window(), NULL);
	pango_layout_set_spacing(layout, 0);
	if (d->flags & DF_TEXT_CENTER)
		pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
	pango_layout_set_font_description(layout, ts->font);
	if (d->flags & DF_TEXT_USE_MARKUP)
		pango_layout_set_markup(layout, text, strlen(text));
	else
		pango_layout_set_text(layout, text, strlen(text));

	pango_layout_get_pixel_extents(layout, &ink, &logical);
	d->scroll_width = logical.width + ts->effect;	/* incl trailing spaces */
	d->scroll_height = ink.height + ts->effect;
	y_ink = ink.y - logical.y;

	/* y_ink change can happen if markup up text sizes differently from sizing
	|  at decal creation.  Adjust the decal y_ink so text doesn't get bumped
	|  upward which clips top of text string.  This means the decal height
	|  should probably also be changed because now descenders might get
	|  clipped when switching back out of scrolling.
	|  But this is much less noticeable... later.
	*/
	if (d->y_ink > y_ink)
		d->y_ink = y_ink;

	if (yink)
		*yink = y_ink;
	return layout;
	}

  /* If mixing draw_decal_text and scroll_text calls, must reset decal regions
  |  in bg_text_layer_pixmap to bg_pixmap at first switch to scroll_text calls.
  */
static void
reset_text_layer_pixmap_decal_region(GkrellmPanel *p, GkrellmDecal *d)
	{
	if (d->flags & DF_MOVED)
		{
		gdk_draw_drawable(p->bg_text_layer_pixmap, _GK.draw1_GC,
					p->bg_pixmap,
					d->x_old, d->y_old, d->x_old, d->y_old, d->w, d->h);
		d->flags &= ~DF_MOVED;
		}
	else
		gdk_draw_drawable(p->bg_text_layer_pixmap, _GK.draw1_GC,
					p->bg_pixmap,
					d->x, d->y,  d->x, d->y,   d->w, d->h);
	}

  /* Draw Pango rendered text onto a pixmap which can be scrolled across
  |  a decal's extents.  So when scrolling, a slow Pango redraw at each scroll
  |  step can be avoided if the text string has not changed.
  |  There are a couple of limitations because it's impossible to generate a
  |  stencil bitmask of Pango rendered text (Pango doesn't allow writing to
  |  depth 1 and it looks at the background pixmap colors as it renders):
  |    1) A scroll text decal is not transparent and will hide any objects
  |       underneath it, so when this condition is detected, scroll drawing
  |       is diverted to Pango at each step gkrellm_draw_decal_text().
  |    2) The scroll text must be rendered onto a background which for many
  |       themes won't exactly blend with the panel background as the scroll
  |       pixmap is scrolled.  It should be at worst only slightly noticeable
  |       for most themes, but might be a problem for themes with high gradient
  |       panel backgrounds.
  */
static void
gkrellm_decal_scroll_text_set_internal(GkrellmPanel *p, GkrellmDecal *d,
			gchar *text)
	{
	GtkWidget			*top_win = gkrellm_get_top_window();
	PangoLayout			*layout;
	GdkPixbuf			*pixbuf;
	GList				*list;
	GkrellmDecal		*dcheck;
	GkrellmBorder		*b;
	GkrellmTextstyle	*ts;
	gint				dx, y_ink;
	gboolean			new_text, no_scroll_caching, first_scroll;

	if (!p || !text || !d || d->state == DS_INVISIBLE)
		return;

	first_scroll = (d->scroll_text == NULL);

	new_text = gkrellm_dup_string(&d->scroll_text, text);
	no_scroll_caching = (p->transparency || p->scroll_text_cache_off);

	for (list = p->decal_list; list; list = list->next)
		{
		dcheck = (GkrellmDecal *) list->data;

		/* Scroll text underneath is OK */
		if (dcheck == d && !no_scroll_caching)
			break;

		/* But scroll text overlapping and on top of any other decal requires
		|  scroll text to be pushed.
		*/
		if (   no_scroll_caching
			|| (   d->x + d->w > dcheck->x
				&& d->x < dcheck->x + dcheck->w
				&& d->y + d->h > dcheck->y
				&& d->y < dcheck->y + dcheck->h
			   )
		   )
			{
			if (first_scroll && d->text_list)
				reset_text_layer_pixmap_decal_region(p, d);

			d->flags |= (DF_TEXT_OVERLAPS | DF_SCROLL_TEXT_DIVERTED);
			if (new_text)
				{
				layout = decal_scroll_text_layout(d, text, NULL);
				g_object_unref(G_OBJECT(layout));
				}
			gkrellm_draw_decal_internal(p, d, text, -1);
			return;
			}
		}
	if (d->text_list)	/* Erase any previous gkrellm_draw_decal_text()	*/
		{
		reset_text_layer_pixmap_decal_region(p, d);
		decal_text_list_free(&d->text_list);
		d->value = -1;
		}
	d->modified = TRUE;

	if (!new_text && !(d->flags & DF_SCROLL_TEXT_DIVERTED))
		return;

	layout = decal_scroll_text_layout(d, text, &y_ink);
	gkrellm_free_pixmap(&d->pixmap);
	d->pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win),
					d->scroll_width, d->scroll_height, -1);

	if (!d->pixmap)		/* too wide maybe? */
		{
		d->flags |= (DF_TEXT_OVERLAPS | DF_SCROLL_TEXT_DIVERTED);
		g_object_unref(G_OBJECT(layout));
		gkrellm_draw_decal_text(p, d, text, -1);
		return;
		}
	d->flags &= ~(DF_TEXT_OVERLAPS | DF_SCROLL_TEXT_DIVERTED);

	dx = d->w - d->scroll_width;

	if (dx >= 0)
		pixbuf = gdk_pixbuf_get_from_drawable(NULL, p->bg_pixmap, NULL,
				d->x + ((d->flags & DF_TEXT_CENTER) ? dx / 2 : 0), d->y,
				0, 0, d->scroll_width, d->h);
	else
		{
		gint	xfudge, yfudge;	/* Many themes have sloppy margins XXX */

		xfudge = d->w > 20 ? d->w / 6 : 0;	/* and gradient compensate a bit */
		yfudge = d->h > 5 ? d->h / 5 : 0;
		pixbuf = gdk_pixbuf_get_from_drawable(NULL, p->bg_pixmap, NULL,
				d->x + xfudge, d->y + yfudge, 0, 0,
				d->w - 2 * xfudge, d->h - 2 * yfudge);
		}

	if (pixbuf)
		{
		gkrellm_paste_pixbuf(pixbuf, d->pixmap, 0, 0,
					d->scroll_width, d->scroll_height);
		g_object_unref(G_OBJECT(pixbuf));
		}
	else
		{
		b = &p->bg_piximage->border;
		gkrellm_paste_pixbuf(p->bg_piximage->pixbuf, d->pixmap,
					b->left, b->top,
					d->scroll_width + b->left + b->right,
					d->scroll_height + b->top + b->bottom); /* XXX */
		}

	ts = &d->text_style;
	if (ts->effect)
		{
		gdk_gc_set_foreground(_GK.text_GC, &ts->shadow_color);
		gdk_draw_layout_with_colors(d->pixmap, _GK.text_GC,
					1, -y_ink + 1, layout,
					&ts->shadow_color, NULL);
		}
	gdk_gc_set_foreground(_GK.text_GC, &ts->color);
	gdk_draw_layout(d->pixmap, _GK.text_GC, 0, -y_ink, layout);

	g_object_unref(G_OBJECT(layout));
	}

void
gkrellm_decal_scroll_text_set_markup(GkrellmPanel *p, GkrellmDecal *d,
			gchar *text)
	{
	if (!d)
		return;
	d->flags |= DF_TEXT_USE_MARKUP;
	gkrellm_decal_scroll_text_set_internal(p, d, text);
	}

void
gkrellm_decal_scroll_text_set_text(GkrellmPanel *p, GkrellmDecal *d,
			gchar *text)
	{
	if (!d)
		return;
	d->flags &= ~DF_TEXT_USE_MARKUP;
	gkrellm_decal_scroll_text_set_internal(p, d, text);
	}


void
gkrellm_move_krell_yoff(GkrellmPanel *p, GkrellmKrell *k, gint y)
	{
	if (!k)
		return;
	if (!k->modified)
		k->old_draw = k->draw;
	k->y0 = k->draw.y_dst = y;
	k->modified = TRUE;
	}

static void
_destroy_krell(GkrellmKrell *k)
	{
	if (!k)
		return;
	gkrellm_free_bitmap(&k->stencil);
	gkrellm_free_pixmap(&k->pixmap);
	gkrellm_free_bitmap(&k->mask);
	g_free(k);
	}

void
gkrellm_destroy_krell(GkrellmKrell *k)
	{
	if (!k)
		return;
	gkrellm_remove_krell((GkrellmPanel *) k->panel, k);
	_destroy_krell(k);
	}

void
gkrellm_remove_krell(GkrellmPanel *p, GkrellmKrell *k)
	{
	GkrellmDrawrec	*dr;

	if (!k || !k->panel || !p || !g_list_find(p->krell_list, k))
		return;
	p->krell_list = g_list_remove(p->krell_list, k);
	k->panel = NULL;
	dr = &k->draw;
	if (p->pixmap && p->bg_pixmap)
		{
		gdk_draw_drawable(p->pixmap, _GK.draw1_GC, p->bg_pixmap,
			dr->x_dst, dr->y_dst, dr->x_dst, dr->y_dst, dr->w, dr->h);
		if (p->drawing_area && gtk_widget_get_window(p->drawing_area))
			gdk_draw_drawable(gtk_widget_get_window(p->drawing_area), _GK.draw1_GC,
				p->bg_pixmap,
				dr->x_dst, dr->y_dst, dr->x_dst, dr->y_dst, dr->w, dr->h);
		gkrellm_draw_panel_layers_force(p);
		}
	}

void
gkrellm_insert_krell(GkrellmPanel *p, GkrellmKrell *k, gboolean append)
	{
	if (!k || k->panel || !p || g_list_find(p->krell_list, k))
		return;
	if (append)
		p->krell_list = g_list_append(p->krell_list, k);
	else
		p->krell_list = g_list_prepend(p->krell_list, k);
	k->panel = (GkrellmPanel *) p;
	gkrellm_draw_panel_layers_force(p);
	}

void
gkrellm_insert_krell_nth(GkrellmPanel *p, GkrellmKrell *k, gint n)
	{
	if (!p || !k || g_list_find(p->krell_list, k))
		return;
	p->krell_list = g_list_insert(p->krell_list, k, n);
	k->panel = (GkrellmPanel *) p;
	gkrellm_draw_panel_layers_force(p);
	}

void
gkrellm_destroy_krell_list(GkrellmPanel *p)
	{
	GList	*list;

	if (!p)
		return;
	for (list = p->krell_list; list; list = list->next)
		_destroy_krell((GkrellmKrell *) list->data);
	if (p->krell_list)
		g_list_free(p->krell_list);
	p->krell_list = NULL;
	}

void
gkrellm_set_krell_margins(GkrellmPanel *p, GkrellmKrell *k,
				gint left, gint right)
	{
	if (!k)
		return;
	if (left > _GK.chart_width - 3)
		left = _GK.chart_width - 3;
	k->x0 = left;
	k->w_scale  = _GK.chart_width - k->x0 - right - 1;
	if (k->w_scale < 2)
		k->w_scale = 2;
	if (p && p->pixmap && p->bg_pixmap)
		{
		/* Move the krell proportionally between the new limits
		*/
		k->x_position = -1;
		if (k->monotonic)
			k->previous -= k->value;
		gkrellm_update_krell(p, k, k->value);
		gkrellm_draw_panel_layers_force(p);
		}
	}

void
gkrellm_set_krell_full_scale(GkrellmKrell *k, gint full_scale, gint expand)
	{
	if (!k)
		return;
	k->full_scale_expand = (expand <= 0 ) ? 1 : expand;
	k->full_scale = full_scale * k->full_scale_expand;
	}

GkrellmKrell *
gkrellm_create_krell(GkrellmPanel *p, GkrellmPiximage *im, GkrellmStyle *style)
	{
	GtkWidget		*top_win = gkrellm_get_top_window();
	GkrellmKrell	*k;
	gint			w, h, h_render, w_render;

	k = (GkrellmKrell *) g_new0(GkrellmKrell, 1);
	if (p)
		p->krell_list = g_list_append(p->krell_list, k);
	k->panel = (gpointer) p;
	k->bar_mode = k->flags = 0;

	if (im == NULL || style == NULL)
		{
		g_warning(_("create_krell: NULL image or style\n"));
		exit(0);
		}

	/* Set left krell margin */
	k->x0 = style->krell_left_margin;
	if (k->x0 > _GK.chart_width - 3)
		k->x0 = _GK.chart_width - 3;

	/* Set right krell margin via w_scale (-1 adjust to keep x_hot visible */
	k->w_scale  = _GK.chart_width - k->x0 - style->krell_right_margin - 1;
	if (k->w_scale < 2)
		k->w_scale = 2;

	/* krell_yoff values: >= 0, put krell at krell_yoff in the panel.
	|                     == -1 put krell inside of margins at top margin.
    |                     == -2 put krell at bottom of panel
    |                     == -3 put krell inside of margins at bottom margin.
	|  For anything else, caller must gkrellm_krell_move_yoff() after create.
	|  Inside of top margin handled here, but panel configure must handle
	|  bottom of panel adjustments.
	*/
	k->y0 = style->krell_yoff;
	if (k->y0 > 0 && !style->krell_yoff_not_scalable )
		k->y0 = k->y0 * _GK.theme_scale / 100;
	if (k->y0 == -1 || k->y0 == -3)
		k->flags |= KRELL_FLAG_BOTTOM_MARGIN;
	if (k->y0 == -1)
		gkrellm_get_top_bottom_margins(style, &k->y0, NULL);
	w = gdk_pixbuf_get_width(im->pixbuf);
	h = gdk_pixbuf_get_height(im->pixbuf);

	if (style->krell_x_hot < 0)
		style->krell_x_hot = w / 2;
	w_render = w * _GK.theme_scale / 100;
	k->x_hot = style->krell_x_hot * _GK.theme_scale / 100;

	switch (style->krell_expand)
		{
		case KRELL_EXPAND_NONE:
			break;
		case KRELL_EXPAND_BAR_MODE:
			w_render = _GK.chart_width;
			k->bar_mode = KRELL_EXPAND_BAR_MODE;
			break;
		case KRELL_EXPAND_BAR_MODE_SCALED:
			w_render = k->w_scale + 1;		/* undo -1 from w_scale calc */
			k->bar_mode = KRELL_EXPAND_BAR_MODE_SCALED;
			break;
		case KRELL_EXPAND_LEFT:
			if (style->krell_x_hot > 0 && style->krell_x_hot < w)
				{
				w_render = _GK.chart_width * w / style->krell_x_hot;
				k->x_hot = _GK.chart_width - 1;
				}
			break;
		case KRELL_EXPAND_LEFT_SCALED:
			if (style->krell_x_hot > 0 && style->krell_x_hot < w)
				{
				w_render = (k->w_scale + 1) * w / style->krell_x_hot;
				k->x_hot = k->w_scale;
				}
			break;
		case KRELL_EXPAND_RIGHT:
			if (w > style->krell_x_hot)
				{
				w_render = _GK.chart_width * w / (w - style->krell_x_hot);
				k->x_hot = style->krell_x_hot * w_render / w;
				}
			break;
		case KRELL_EXPAND_RIGHT_SCALED:
			if (w > style->krell_x_hot)
				{
				w_render = (k->w_scale + 1) * w / (w - style->krell_x_hot);
				k->x_hot = style->krell_x_hot * w_render / w;
				}
			break;
		}
	k->depth = style->krell_depth;
	if (k->depth < 1)
		k->depth = 1;
	k->h_frame = h / k->depth * _GK.theme_scale / 100;
	if (k->h_frame < 1)
		k->h_frame = 1;
	h_render = k->h_frame * k->depth;

	gkrellm_scale_piximage_to_pixmap(im, &k->pixmap, &k->mask,
				w_render, h_render);

	k->h = h_render;
	k->w = w_render;

	k->stencil = gdk_pixmap_new(gtk_widget_get_window(top_win), _GK.chart_width,
					k->h_frame, 1);
	k->period = style->krell_ema_period;
	if (k->period >= 4 * _GK.update_HZ)
		k->period = 4 * _GK.update_HZ;

	k->x_position = -1;      /* Force initial draw   */
	k->full_scale_expand = 1;
	k->monotonic = TRUE;
	return k;
	}

void
gkrellm_monotonic_krell_values(GkrellmKrell *k, gboolean mono)
	{
	if (k)
		k->monotonic = mono;
	}

/* -------------------------------------------------------------- */

static void
remove_from_button_list(GkrellmPanel *p, GkrellmDecalbutton *b)
	{
	if (!p || !b)
		return;
	p->button_list = g_list_remove(p->button_list, b);
	g_free(b);

	if (!p->button_list && p->drawing_area)
		{
		g_signal_handler_disconnect(G_OBJECT(p->drawing_area), p->id_press);
		g_signal_handler_disconnect(G_OBJECT(p->drawing_area), p->id_release);
		g_signal_handler_disconnect(G_OBJECT(p->drawing_area), p->id_enter);
		g_signal_handler_disconnect(G_OBJECT(p->drawing_area), p->id_leave);
		p->button_signals_connected = FALSE;
		}
	}

void
gkrellm_move_decal(GkrellmPanel *p, GkrellmDecal *d, gint x, gint y)
	{
	if (!d || (d->x == x && d->y == y))
		return;
	if (!(d->flags & DF_MOVED))	/* If no intervening draw_panel_layers() */
		{						/* then preserve original old position   */
		d->x_old = d->x;
		d->y_old = d->y;
		}
	d->x = x;
	d->y = y;
	d->flags |= DF_MOVED;
	d->modified = TRUE;

	if (p)
		p->need_decal_overlap_check = TRUE;
	}

void
gkrellm_decal_on_top_layer(GkrellmDecal *d, gboolean top)
	{
	GkrellmPanel	*p = (GkrellmPanel *) d->panel;
	if (!d)
		return;
	if (top)
		d->flags |= DF_TOP_LAYER;
	else
		d->flags &= ~DF_TOP_LAYER;
	if (p)
		p->need_decal_overlap_check = TRUE;

	d->modified = TRUE;
	}

static void
_destroy_decal(GkrellmDecal *d)
	{
	if (!d)
		return;
	if ((d->flags & DF_LOCAL_PIXMAPS) && d->pixmap)
		g_object_unref(G_OBJECT(d->pixmap));
	if (d->stencil)
		g_object_unref(G_OBJECT(d->stencil));
	if (d->text_list)
		decal_text_list_free(&d->text_list);
	if (d->scroll_text)
		g_free(d->scroll_text);
	if (d->panel)
		((GkrellmPanel *)d->panel)->need_decal_overlap_check = TRUE;

	g_free(d);
	}

static void
_remove_decal(GkrellmPanel *p, GkrellmDecal *d)
	{
	if (!g_list_find(p->decal_list, d))
		return;
	p->decal_list = g_list_remove(p->decal_list, d);
	p->need_decal_overlap_check = TRUE;
	d->panel = NULL;
	if (p->pixmap && p->bg_pixmap)
		{
		gdk_draw_drawable(p->pixmap, _GK.draw1_GC, p->bg_pixmap,
				d->x, d->y,  d->x, d->y,   d->w, d->h);
		if (p->drawing_area && gtk_widget_get_window(p->drawing_area))
			gdk_draw_drawable(gtk_widget_get_window(p->drawing_area), _GK.draw1_GC,
					p->bg_pixmap, d->x, d->y,  d->x, d->y,   d->w, d->h);
		gkrellm_draw_panel_layers_force(p);
		}
	}

void
gkrellm_destroy_decal(GkrellmDecal *d)
	{
	GkrellmDecalbutton	*b;

	if (d && d->panel)
		{
		if ((b = gkrellm_decal_is_button(d)) != NULL)
			gkrellm_destroy_button(b);
		else
			{
			_remove_decal((GkrellmPanel *) d->panel, d);
			_destroy_decal(d);
			}
		}
	else
		_destroy_decal(d);
	}

void
gkrellm_remove_decal(GkrellmPanel *p, GkrellmDecal *d)
	{
	if (!p || !d || gkrellm_decal_is_button(d))
		return;
	_remove_decal(p, d);
	}

void
gkrellm_insert_decal(GkrellmPanel *p, GkrellmDecal *d, gboolean append)
	{
	if (!p || !d || g_list_find(p->decal_list, d))
		return;
	if (append)
		p->decal_list = g_list_append(p->decal_list, d);
	else
		p->decal_list = g_list_prepend(p->decal_list, d);
	d->panel = (gpointer) p;
	p->need_decal_overlap_check = TRUE;
	gkrellm_draw_panel_layers_force(p);
	}

void
gkrellm_insert_decal_nth(GkrellmPanel *p, GkrellmDecal *d, gint n)
	{
	if (!p || !d || g_list_find(p->decal_list, d))
		return;
	p->decal_list = g_list_insert(p->decal_list, d, n);
	d->panel = (gpointer) p;
	p->need_decal_overlap_check = TRUE;
	gkrellm_draw_panel_layers_force(p);
	}

void
gkrellm_destroy_decal_list(GkrellmPanel *p)
	{
	GkrellmDecalbutton	*b;
	GkrellmDecal		*d;
	GList				*list;

	if (!p)
		return;
	for (list = p->decal_list; list; list = list->next)
		{
		d = (GkrellmDecal *) list->data;
		if ((b = gkrellm_decal_is_button(d)) != NULL)
			remove_from_button_list(p, b);
		_destroy_decal(d);
		}
	if (p->decal_list)
		g_list_free(p->decal_list);
	p->decal_list = NULL;
	}

GkrellmDecal *
gkrellm_create_decal_pixmap(GkrellmPanel *p,
				GdkPixmap *pixmap, GdkBitmap *mask,
				gint depth, GkrellmStyle *style, gint x, gint y)
	{
	GtkWidget		*top_win;
	GkrellmMargin	*m;
	GkrellmDecal	*d;

	if (!pixmap)
		return NULL;
	top_win = gkrellm_get_top_window();
	d = (GkrellmDecal *) g_new0(GkrellmDecal, 1);
	if (p)
		{
		p->decal_list = g_list_append(p->decal_list, d);
		p->need_decal_overlap_check = TRUE;
		}
	d->panel = (gpointer) p;

	gdk_drawable_get_size(pixmap, &d->w, &d->h);
	if (depth > 0)
		d->h /= depth;
	if (d->h == 0)
		d->h = 1;

	d->x = x;
	m = gkrellm_get_style_margins(style);
	if (d->x < 0 && style)
		{
		if (   style->label_position < 50
			&& style->label_position != GKRELLM_LABEL_NONE
		   )
			d->x = _GK.chart_width - d->w - m->right;
		else
			d->x = m->left;
		}
	d->y = y;
	if (d->y < 0)
		d->y = m->top;

	d->pixmap = pixmap;
	d->mask   = mask;
	d->stencil = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h, 1);

	d->value = -1;		/* Force initial draw */
	d->flags = 0;
	d->state = DS_VISIBLE;
	return d;
	}

static GkrellmDecal *
_create_decal_text(GkrellmPanel *p, gchar *string,
				GkrellmTextstyle *ts, GkrellmStyle *style,
				gint x, gint y, gint w, gint h, gint y_ink, gboolean markup)
	{
	GtkWidget		*top_win;
	GkrellmMargin	*m;
	GkrellmDecal	*d;
	GkrellmText		*tx;
	gint			width, height, baseline;

	top_win = gkrellm_get_top_window();
	d = (GkrellmDecal *) g_new0(GkrellmDecal, 1);
	if (p)
		{
		p->decal_list = g_list_append(p->decal_list, d);
		p->need_decal_overlap_check = TRUE;
		}
	d->panel = (gpointer) p;

	d->text_style = ts ? *ts : *gkrellm_meter_textstyle(0);
	tx = g_new0(GkrellmText, 1);
	d->text_list = g_list_append(d->text_list, tx);
	gkrellm_dup_string(&tx->text, "");
	tx->text_style = d->text_style;

	if (string && *string)
		{
		if (markup)
			gkrellm_text_markup_extents(d->text_style.font,
					string, strlen(string),
					&width, &height, &baseline, &d->y_ink);
		else
			gkrellm_text_extents(d->text_style.font,
					string, strlen(string),
					&width, &height, &baseline, &d->y_ink);
		d->h = height + d->text_style.effect;
		}
	else
		{
		width = 2;
		d->h = h;
		d->y_ink = y_ink;
		}
	if (d->h <= 0)
		d->h = 2;

	/* Width defaults to full chart width
	|  minus borders unless w > 0, or w == 0 to use string width.
	*/
	if (style)
		{
		m = gkrellm_get_style_margins(style);
		if (w < 0)
			d->w = _GK.chart_width - m->left - m->right;
		else if (w == 0)
			d->w = width + d->text_style.effect;
		else
			d->w = w;

		d->x = (x >= 0) ? x : m->left;

		d->y = y;
		if (d->y < 0)
			d->y = m->top;
		}
	else
		{
		if (w < 0)
			d->w = _GK.chart_width;
		else if (w == 0)
			d->w = width;
		else
			d->w = w;
		d->x = x;
		d->y = y;
		}
	if (d->w == 0)
		d->w = 1;
	d->pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h, -1);;
	d->mask   = NULL;
	d->stencil = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h, 1);

	d->value = -1;		/* Force initial draw */
	d->flags = DF_LOCAL_PIXMAPS;
	d->state = DS_VISIBLE;

	return d;
	}

GkrellmDecal *
gkrellm_create_decal_text(GkrellmPanel *p, gchar *string,
				GkrellmTextstyle *ts, GkrellmStyle *style,
				gint x, gint y, gint w)
	{
	return _create_decal_text(p, string, ts, style, x, y, w, 0, 0, FALSE);
	}

GkrellmDecal *
gkrellm_create_decal_text_markup(GkrellmPanel *p, gchar *string,
				GkrellmTextstyle *ts, GkrellmStyle *style,
				gint x, gint y, gint w)
	{
	return _create_decal_text(p, string, ts, style, x, y, w, 0, 0, TRUE);
	}

GkrellmDecal *
gkrellm_create_decal_text_with_height(GkrellmPanel *p,
				GkrellmTextstyle *ts, GkrellmStyle *style,
				gint x, gint y, gint w, gint h, gint y_ink)
	{
	return _create_decal_text(p, NULL, ts, style, x, y, w, h, y_ink, FALSE);
	}

void
gkrellm_make_decal_invisible(GkrellmPanel *p, GkrellmDecal *d)
	{
	if (!p || !d || d->state == DS_INVISIBLE)
		return;
	d->state = DS_INVISIBLE;
	d->modified = TRUE;
	p->need_decal_overlap_check = TRUE;
	gkrellm_draw_panel_layers(p);
	}

void
gkrellm_make_decal_visible(GkrellmPanel *p, GkrellmDecal *d)
	{
	if (!p || !d || d->state == DS_VISIBLE)
		return;
	d->state = DS_VISIBLE;
	d->modified = TRUE;
	p->need_decal_overlap_check = TRUE;
	gkrellm_draw_panel_layers(p);
	}

gint
gkrellm_is_decal_visible(GkrellmDecal *d)
	{
	if (!d)
		return FALSE;
	return (d->state == DS_VISIBLE) ? TRUE : FALSE;
	}

/* ===================================================================== */
#define	AUTO_HIDE_BUTTON 	1


gboolean
gkrellm_in_decal(GkrellmDecal *d, GdkEventButton *ev)
	{
	if (!d || !ev)
		return FALSE;
	if (   ev->x >= d->x && ev->x < d->x + d->w
		&& ev->y >= d->y && ev->y < d->y + d->h
	   )
		return TRUE;
	return FALSE;
	}

static void
set_button_index(GkrellmDecalbutton *b, gint index, gint do_draw)
	{
	if (!b)
		return;
	gkrellm_draw_decal_pixmap(b->panel, b->decal, index);
	b->cur_index = index;
	if (do_draw)
		gkrellm_draw_panel_layers(b->panel);
	}

void
gkrellm_set_button_sensitive(GkrellmDecalbutton *b, gboolean sensitive)
	{
	if (b)
		b->sensitive = sensitive;
	}

void
gkrellm_hide_button(GkrellmDecalbutton *b)
	{
	if (!b)
		return;
	b->sensitive = FALSE;
	gkrellm_make_decal_invisible(b->panel, b->decal);
	}

void
gkrellm_show_button(GkrellmDecalbutton *b)
	{
	if (!b)
		return;
	b->sensitive = TRUE;
	gkrellm_make_decal_visible(b->panel, b->decal);
	}

gboolean
gkrellm_in_button(GkrellmDecalbutton *b, GdkEventButton *ev, gpointer data)
	{
	return gkrellm_in_decal(b->decal, ev);
	}

static gint
cb_decal_button_press(GtkWidget *widget, GdkEventButton *ev, GkrellmPanel *p)
	{
	GList				*list;
	GkrellmDecalbutton	*b;
	gboolean			stop_sig = FALSE;

	if (ev->type != GDK_BUTTON_PRESS)	/* I'm connected to "event" */
		return FALSE;
	for (list = p->button_list; list; list = list->next)
		{
		b = (GkrellmDecalbutton *) list->data;
		if (   b->sensitive && (*(b->cb_in_button))(b, ev, b->in_button_data)
			&& (   (b->cb_button_click && ev->button == 1)
				|| (b->cb_button_right_click && ev->button == 3)
			   )
		   )
			{
			if (b->cur_index != b->pressed_index)
				{
				b->saved_index = b->cur_index;
				set_button_index(b, b->pressed_index, 1);
				}
			stop_sig = TRUE;
			break;
			}
		}
	return stop_sig;
	}

static gint
cb_decal_button_release(GtkWidget *widget, GdkEventButton *ev, GkrellmPanel *p)
	{
	GList				*list;
	GkrellmDecalbutton	*b;
	gboolean			stop_sig = FALSE;

	if (ev->type != GDK_BUTTON_RELEASE)	/* I'm connected to "event" */
		return FALSE;
	for (list = p->button_list; list; list = list->next)
		{
		b = (GkrellmDecalbutton *) list->data;
		if (b->cur_index == b->pressed_index)
			{
			set_button_index(b, b->saved_index, 1);
			if ( (*(b->cb_in_button))(b, ev, b->in_button_data) )
				{
				if (b->cb_button_click && ev->button == 1)
					(*(b->cb_button_click))(b, b->data);
				else if (b->cb_button_right_click && ev->button == 3)
					(*(b->cb_button_right_click))(b, b->right_data);
				}
			stop_sig = TRUE;
		 	}
		}
	return stop_sig;
	}

static gint
cb_decal_button_leave(GtkWidget *widget, GdkEventButton *ev, GkrellmPanel *p)
	{
	GList				*list;
	GkrellmDecalbutton	*b;

	if (ev->type != GDK_LEAVE_NOTIFY)	/* I'm connected to "event" */
		return FALSE;
	for (list = p->button_list; list; list = list->next)
		{
		b = (GkrellmDecalbutton *) list->data;
		if (b->type == AUTO_HIDE_BUTTON)
			{
			if (b->sensitive)
				{
				set_button_index(b, 0, 0);
				b->decal->state = DS_INVISIBLE;
				b->decal->modified = TRUE;
				p->need_decal_overlap_check = TRUE;
				gkrellm_draw_panel_layers(p);
				}
			}
		else
			if (b->cur_index == b->pressed_index)
				set_button_index(b, b->saved_index, 1);
		}
	return FALSE;
	}

static gint
cb_decal_button_enter(GtkWidget *widget, GdkEventButton *ev, GkrellmPanel *p)
	{
	GList				*list;
	GkrellmDecalbutton	*b;

	if (ev->type != GDK_ENTER_NOTIFY)	/* I'm connected to "event" */
		return FALSE;
	for (list = p->button_list; list; list = list->next)
		{
		b = (GkrellmDecalbutton *) list->data;
		if (b->type != AUTO_HIDE_BUTTON || ! b->sensitive)
			continue;
		b->decal->state = DS_VISIBLE;
		set_button_index(b, 0, 0);
		b->decal->modified = TRUE;
		p->need_decal_overlap_check = TRUE;
		gkrellm_draw_panel_layers(b->panel);
		}
	return FALSE;
	}

void
gkrellm_set_decal_button_index(GkrellmDecalbutton *b, gint index)
	{
	if (!b)
		return;
	if (b->cur_index == b->pressed_index)
		b->saved_index = index;	/* Throw away old save */
	else
		set_button_index(b, index, 0);
	}

GkrellmDecalbutton *
gkrellm_decal_is_button(GkrellmDecal *d)
	{
	GkrellmPanel		*p;
	GkrellmDecalbutton	*b;
	GList				*list;

	if ((p = (GkrellmPanel *) d->panel) == NULL)
		return NULL;
	for (list = p->button_list; list; list = list->next)
		{
		b = (GkrellmDecalbutton *) list->data;
		if (b->decal == d)
			return b;
		}
	return NULL;
	}

void
gkrellm_destroy_button(GkrellmDecalbutton *b)
	{
	GkrellmPanel	*p;

	if (!b)
		return;
	p = b->panel;
	if (p && b->decal)
		_remove_decal(p, b->decal);
	if (b->decal)
		_destroy_decal(b->decal);
	remove_from_button_list(p, b);
	}


void
gkrellm_set_in_button_callback(GkrellmDecalbutton *b,
			gint (*func)(), gpointer data)
	{
	if (!b)
		return;
	b->cb_in_button = func;
	b->in_button_data = data;
	}

void
gkrellm_decal_button_connect(GkrellmDecalbutton *b, void (*func)(), void *data)
	{
	if (!b)
		return;
	b->data = data;
	b->cb_button_click = func;
	}

void
gkrellm_decal_button_right_connect(GkrellmDecalbutton *b,
				void (*func)(), void *data)
	{
	if (!b)
		return;
	b->right_data = data;
	b->cb_button_right_click = func;
	}

void
gkrellm_panel_button_signals_connect(GkrellmPanel *p)
	{
	/* I want DecalButton event handlers to be called before any monitor
	|  handlers, but DecalButtons will be recreated at theme changes and
	|  thus its handlers will be after panel handlers. So, connect to
	|  "event" which is called first, and check for the right event in
	|  the callback.
	|  There is only one event handler per signal per panel.  Buttons do
	|  not have their own event box so they may be efficiently animated.
	*/
	if (   !p || p->button_signals_connected
		|| !p->button_list || !p->drawing_area
	   )
		return;
	p->id_press = g_signal_connect(G_OBJECT(p->drawing_area),
				"event", G_CALLBACK(cb_decal_button_press), p);
	p->id_release = g_signal_connect(G_OBJECT(p->drawing_area),
				"event", G_CALLBACK(cb_decal_button_release), p);
	p->id_enter = g_signal_connect(G_OBJECT(p->drawing_area),
				"event", G_CALLBACK(cb_decal_button_enter), p);
	p->id_leave = g_signal_connect(G_OBJECT(p->drawing_area),
				"event", G_CALLBACK(cb_decal_button_leave), p);
	p->button_signals_connected = TRUE;
	}

  /* Make an existing decal into a decal button.  The decal should already
  |  be created and be in the panel's decal list.
  */
GkrellmDecalbutton *
gkrellm_make_decal_button(GkrellmPanel *p, GkrellmDecal *d, void (*func)(),
			void *data, gint cur_index, gint pressed_index)
	{
	GkrellmDecalbutton	*b;

	if (!p || !d)
		return NULL;
	b = g_new0(GkrellmDecalbutton, 1);
	b->panel = p;
	b->decal = d;
	b->pressed_index = pressed_index;
	b->data = data;
	b->cb_button_click = func;
	b->cb_in_button = gkrellm_in_button;
	b->in_button_data = NULL;
	b->sensitive = TRUE;
	set_button_index(b, cur_index, 0);

	p->button_list = g_list_append(p->button_list, b);
	gkrellm_panel_button_signals_connect(p);
	p->need_decal_overlap_check = TRUE;

	return b;
	}


GkrellmDecalbutton *
gkrellm_make_overlay_button(GkrellmPanel *p, void (*func)(), void *data,
				gint x, gint y, gint w, gint h,
				GkrellmPiximage *normal_piximage,
				GkrellmPiximage *pressed_piximage)
	{
	GtkWidget			*top_win;
	GkrellmDecalbutton	*b;
	GkrellmDecal		*d;
	GdkPixmap			*pm = NULL, *pixmap;
	GdkBitmap			*m = NULL, *mask;

	if (!p)
		return NULL;
	top_win = gkrellm_get_top_window();
	if (x < 0)
		{
		w -= x;
		x = 0;
		}
	if (y < 0)
		{
		h -= y;
		y = 0;
		}
	if (x + w > _GK.chart_width)
		w = _GK.chart_width - x;
	if (p->h > 0 && y + h > p->h)
		h = p->h - y;
	if (h < 2)
		h = 2;
	if (w < 4)
		w = 4;

	pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), w, 2 * h, -1);
	mask = gdk_pixmap_new(gtk_widget_get_window(top_win), w, 2 * h, 1);

	if (normal_piximage && pressed_piximage)
		{
		gkrellm_scale_piximage_to_pixmap(normal_piximage, &pm, &m, w, h);
		gdk_draw_drawable(pixmap, _GK.draw1_GC, pm, 0, 0, 0, 0, w, h);
		if (m)
			gdk_draw_drawable(mask, _GK.bit1_GC, m, 0, 0, 0, 0, w, h);
		else
			gdk_draw_rectangle(mask, _GK.bit1_GC, TRUE, 0, 0, w, h);
		gkrellm_free_pixmap(&pm);
		gkrellm_free_bitmap(&m);

		gkrellm_scale_piximage_to_pixmap(pressed_piximage, &pm, &m, w, h);
		gdk_draw_drawable(pixmap, _GK.draw1_GC, pm, 0, 0, 0, h, w, h);
		if (m)
			gdk_draw_drawable(mask, _GK.bit1_GC, m, 0, 0, 0, h, w, h);
		else
			gdk_draw_rectangle(mask, _GK.bit1_GC, TRUE, 0, h, w, h);
		gkrellm_free_pixmap(&pm);
		gkrellm_free_bitmap(&m);
		}
	else	/* Make a default frame. */
		{
		GdkColor	gray0, gray1;
		GdkColormap	*cmap;

		cmap = gdk_colormap_get_system();
		gdk_color_parse("gray65", &gray0);
		gdk_color_parse("gray100", &gray1);
		gdk_colormap_alloc_color(cmap, &gray0, FALSE, TRUE);
		gdk_colormap_alloc_color(cmap, &gray1, FALSE, TRUE);

		gdk_gc_set_foreground(_GK.draw1_GC, &gray1);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, 0, w - 1, 0);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, 0, 0, h - 1);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, 2 * h - 1, w - 1, 2 * h - 1);
		gdk_draw_line(pixmap, _GK.draw1_GC, w - 1, 2 * h - 1, w - 1, h);

		gdk_gc_set_foreground(_GK.draw1_GC, &gray0);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, h - 1, w - 1, h - 1);
		gdk_draw_line(pixmap, _GK.draw1_GC, w - 1, h - 1, w - 1, 0);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, h, w - 1, h);
		gdk_draw_line(pixmap, _GK.draw1_GC, 0, h, 0, 2 * h - 1);

		gdk_draw_rectangle(mask, _GK.bit1_GC, TRUE, 0, 0, w, 2 * h);
		gdk_draw_rectangle(mask, _GK.bit0_GC, TRUE, 1, 1, w - 2, h - 2);
		gdk_draw_rectangle(mask, _GK.bit0_GC, TRUE, 1, h + 1, w - 2, h - 2);

		gdk_colormap_free_colors(cmap, &gray0, 1);
		gdk_colormap_free_colors(cmap, &gray1, 1);
		}

	d = gkrellm_create_decal_pixmap(p, pixmap, mask, 2, NULL, x, y);
	d->flags |= (DF_LOCAL_PIXMAPS | DF_OVERLAY_PIXMAPS);
	d->state = DS_INVISIBLE;

	b = gkrellm_make_decal_button(p, d, func, data, 0, 1);
	b->type = AUTO_HIDE_BUTTON;

	return b;
	}

GkrellmDecalbutton *
gkrellm_put_decal_in_panel_button(GkrellmPanel *p, GkrellmDecal *d,
			void (*func)(), void *data, GkrellmMargin *margin_pad)
	{
	GkrellmDecalbutton	*b;
	GkrellmBorder		*border;
	GkrellmMargin		*margin;
	gint				x, y, w, h, a;

	if (!p || !d)
		return NULL;
	border = &_GK.button_panel_border;
	margin = margin_pad ? margin_pad : &zero_margin;

	x = d->x - border->left - margin->left;
	a = d->x + d->w + border->right + margin->right;
	w = a - x;

	y = d->y - border->top - margin->top;
	a = d->y + d->h + border->bottom + margin->bottom;
	h = a - y;

	b = gkrellm_make_overlay_button(p, func, data, x, y, w, h,
				_GK.button_panel_out_piximage, _GK.button_panel_in_piximage);
	return b;
	}

GkrellmDecalbutton *
gkrellm_put_decal_in_meter_button(GkrellmPanel *p, GkrellmDecal *d,
			void (*func)(), void *data, GkrellmMargin *margin_pad)
	{
	GkrellmDecalbutton	*b;
	GkrellmBorder		*border;
	GkrellmMargin		*margin;
	gint				x, y, w, h, a;

	if (!p || !d)
		return NULL;
	border = &_GK.button_meter_border;
	margin = margin_pad ? margin_pad : &zero_margin;

	x = d->x - border->left - margin->left;
	a = d->x + d->w + border->right + margin->right;
	w = a - x;

	y = d->y - border->top - margin->top;
	a = d->y + d->h + border->bottom + margin->bottom;
	h = a - y;

	b = gkrellm_make_overlay_button(p, func, data, x, y, w, h,
				_GK.button_meter_out_piximage, _GK.button_meter_in_piximage);
	return b;
	}


static GkrellmDecalbutton *
put_label_in_button(GkrellmPanel *p, void (*func)(), void *data, gint type,
			gint pad)
	{
	GkrellmDecalbutton	*b;
	GkrellmLabel		*lbl;
	GkrellmMargin		*m;
	GkrellmPiximage		*normal_piximage, *pressed_piximage;
	GkrellmBorder		*style_border, *fr_border;
	gint				x, y, w, h;

	if (!p || p->h == 0)
		return NULL;

	fr_border = (type == METER_PANEL_TYPE) ? &_GK.button_meter_border
										: &_GK.button_panel_border;
	normal_piximage = (type == METER_PANEL_TYPE)
			? _GK.button_meter_out_piximage : _GK.button_panel_out_piximage;
	pressed_piximage = (type == METER_PANEL_TYPE)
			? _GK.button_meter_in_piximage : _GK.button_panel_in_piximage;

	/* If no panel label, put the whole panel in the button.
	*/
	if ((lbl = p->label) == NULL || lbl->string == NULL || lbl->position < 0)
		{
		m = gkrellm_get_style_margins(p->style);
		style_border = p->style ? &p->style->border : &zero_border;
		x = 0 + m->left;
		y = 0 + style_border->top - fr_border->top;
		w = p->w - x - m->right;
		h = p->h - y - style_border->bottom + fr_border->bottom;
		}
	else
		{
		x = lbl->x_panel - fr_border->left - pad;
		y = lbl->y_panel - fr_border->top;
		w = lbl->width + fr_border->left + fr_border->right + 2 * pad;
		h = lbl->height + (p->textstyle->effect ? 1 : 0)
				+ fr_border->top + fr_border->bottom;
		}
	b = gkrellm_make_overlay_button(p, func, data, x, y, w, h,
					normal_piximage, pressed_piximage);
	return b;
	}


GkrellmDecalbutton *
gkrellm_put_label_in_panel_button(GkrellmPanel *p, void (*func)(), void *data,
				gint pad)
	{
	GkrellmDecalbutton	*b;

	b = put_label_in_button(p, func, data, CHART_PANEL_TYPE, pad);
	return b;
	}


GkrellmDecalbutton *
gkrellm_put_label_in_meter_button(GkrellmPanel *p, void (*func)(), void *data,
				gint pad)
	{
	GkrellmDecalbutton	*b;

	b = put_label_in_button(p, func, data, METER_PANEL_TYPE, pad);
	return b;
	}

GkrellmDecalbutton *
gkrellm_make_scaled_button(GkrellmPanel *p, GkrellmPiximage *im,
			void (*func)(), void *data,
			gboolean auto_hide, gboolean set_default_border,
			gint depth, gint cur_index, gint pressed_index,
			gint x, gint y, gint w, gint h)
	{
	GtkWidget			*top_win;
	GkrellmDecalbutton	*b;
	GkrellmDecal		*d;
	GkrellmBorder		*bdr;
	GdkPixmap			*pixmap;
	GdkBitmap			*mask;

	if (!p)
		return NULL;
	if (!im)
		{
		im = _GK.decal_button_piximage;
		depth = 2;
		cur_index = 0;
		pressed_index = 1;
		}
	if (depth < 2)
		depth = 2;
	if (w < 0)
		w = gkrellm_chart_width();
	else
		{
		if (w == 0)
			w = gdk_pixbuf_get_width(im->pixbuf);
		if ((w = w * _GK.theme_scale / 100) < 2)
			w = 2;
		}

	if (h < 0)
		h = p->h;
	else
		{
		if (h == 0)
			h = gdk_pixbuf_get_height(im->pixbuf) / depth;
		if ((h = h * _GK.theme_scale / 100) < 2)
			h = 2;
		}

	top_win = gkrellm_get_top_window();
	pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), w, depth * h, -1);
	mask = gdk_pixmap_new(gtk_widget_get_window(top_win), w, depth * h, 1);
	if (set_default_border)
		{
		bdr = &im->border;
		bdr->left = bdr->right = bdr->top = bdr->bottom = 1;
		}
	gkrellm_scale_piximage_to_pixmap(im, &pixmap, &mask, w, depth * h);
	d = gkrellm_create_decal_pixmap(p, pixmap, mask, depth, NULL, x, y);
	d->flags |= DF_LOCAL_PIXMAPS;
	b = gkrellm_make_decal_button(p, d, func, data, cur_index, pressed_index);
	if (auto_hide)
		{
		d->state = DS_INVISIBLE;
		b->type = AUTO_HIDE_BUTTON;
		}
	return b;
	}


GkrellmDecal *
gkrellm_make_scaled_decal_pixmap(GkrellmPanel *p, GkrellmPiximage *im,
			GkrellmStyle *style, gint depth,
			gint x, gint y, gint w, gint h)
	{
	GtkWidget		*top_win;
	GdkPixmap		*pixmap;
	GdkBitmap		*mask;
	GkrellmMargin	*m;
	GkrellmDecal	*d;

	if (!im)
		return NULL;
	top_win = gkrellm_get_top_window();
	d = (GkrellmDecal *) g_new0(GkrellmDecal, 1);
	if (p)
		{
		p->decal_list = g_list_append(p->decal_list, d);
		p->need_decal_overlap_check = TRUE;
		}
	d->panel = (gpointer) p;

	if (depth < 1)
		depth = 1;

	if (   w < 1
		&& (w = gdk_pixbuf_get_width(im->pixbuf) * _GK.theme_scale / 100) < 1
	   )
		w = 1;
	d->w = w;

	if (h < 1)
		h = gdk_pixbuf_get_height(im->pixbuf) / depth * _GK.theme_scale / 100;

	if (h < 1)
		h = 1;
	d->h = h;

	pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h * depth, -1);
	mask = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h * depth, 1);
	gkrellm_scale_piximage_to_pixmap(im, &pixmap, &mask, d->w, d->h * depth);

	d->x = x;
	m = gkrellm_get_style_margins(style);
	if (d->x < 0 && style)
		{
		if (   style->label_position < 50
			&& style->label_position != GKRELLM_LABEL_NONE
		   )
			d->x = _GK.chart_width - d->w - m->right;
		else
			d->x = m->left;
		}
	d->y = y;
	if (d->y < 0)
		d->y = m->top;

	d->pixmap = pixmap;
	d->mask   = mask;
	d->stencil = gdk_pixmap_new(gtk_widget_get_window(top_win), d->w, d->h, 1);

	d->value = -1;		/* Force initial draw */
	d->flags = DF_LOCAL_PIXMAPS;
	d->state = DS_VISIBLE;
	return d;
	}
