#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include "../../include/string.h"
#include "../../include/fio.h"
#include "../../include/prochandle.h"
#include "../guiutils.h"
#include "../statictip.h"
#include "sysinfowin.h"
#include "config.h"

#include "../images/icon_power_20x20.xpm"
#include "../images/icon_close_20x20.xpm"

#include "../images/icon_cpu_32x32.xpm"
#include "../images/icon_cpu_48x48.xpm"


static gint SysInfoEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void SysInfoShowMemoryToggleCB(GtkWidget *widget, gpointer data);
static void SysInfoShowAPMPowerToggleCB(GtkWidget *widget, gpointer data);
static void SysInfoShowCPUDetailsToggleCB(GtkWidget *widget, gpointer data);
static void SysInfoCloseCB(GtkWidget *widget, gpointer data);

static gchar *SysInfoGetCPUVendor(void);
static gchar *SysInfoGetCPUModel(void);
static gfloat SysInfoGetCPULoadAvg(void);
static void SysInfoGetMemory(gulong *free, gulong *total);
static gfloat SysInfoGetAPMPower(void);

static void SysInfoDrawBar(
	sysinfo_win_struct *si,
	GtkWidget *w,		/* GtkDrawingArea */
	GdkPixmap *pixmap,	/* Back buffer */
	gfloat v, gfloat v_mid, gfloat v_high
);
static void SysInfoDrawCPULoad(sysinfo_win_struct *si, gfloat v);
static void SysInfoDrawCPULoadAvg(sysinfo_win_struct *si, gfloat v);
static void SysInfoDrawMemory(sysinfo_win_struct *si, gfloat v);
static void SysInfoDrawAPMPower(sysinfo_win_struct *si, gfloat v);
static void SysInfoUpdateDetails(sysinfo_win_struct *si);

sysinfo_win_struct *SysInfoNew(
	gpointer core_ptr,
	sysinfo_display_flags display,
	sysinfo_bar_style bar_style,
	sysinfo_bar_orientation bar_orientation
);
void SysInfoSetDisplay(
	sysinfo_win_struct *si, sysinfo_display_flags display
);
void SysInfoSetBarStyle(
	sysinfo_win_struct *si, sysinfo_bar_style bar_style
);
void SysInfoSetBarOrientation(
	sysinfo_win_struct *si, sysinfo_bar_orientation bar_orientation
);
void SysInfoUpdate(sysinfo_win_struct *si);
gboolean SysInfoIsMapped(sysinfo_win_struct *si);
void SysInfoMap(sysinfo_win_struct *si);
void SysInfoUnmap(sysinfo_win_struct *si);
void SysInfoDelete(sysinfo_win_struct *si);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)	(((x) < 0) ? -(x) : (x))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	SysInfo Window any GtkWidget event signal callback.
 */
static gint SysInfoEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventConfigure *configure;
	GdkEventExpose *expose;
	GdkEventFocus *focus;
	GdkEventButton *button;
	GdkEventCrossing *crossing;
	sysinfo_win_struct *si = SYSINFO_WIN(data);


	switch((gint)event->type)
	{
	  case GDK_DELETE:
	    if(widget == si->toplevel)
	    {
		SysInfoUnmap(si);
		status = TRUE;
	    }
	    break;

	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    if(widget == si->toplevel)
	    {
		status = TRUE;
	    }
	    else if(widget == si->cpu_load_da)
	    {
		GDK_PIXMAP_UNREF(si->bar_pixmap);
		si->bar_pixmap = gdk_pixmap_new(
		    widget->window, configure->width, configure->height,
		    -1
		);
		status = TRUE;
	    }
	    else if(widget == si->cpu_loadavg_da)
	    {
		status = TRUE;
	    }
	    else if(widget == si->memory_da)
	    {
		status = TRUE;
	    }
	    break;

	  case GDK_EXPOSE:
	    expose = (GdkEventExpose *)event;
	    if(widget == si->toplevel)
	    {
		status = TRUE;
	    }
	    else if(widget == si->cpu_load_da)
	    {
		SysInfoDrawCPULoad(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->cpu_loadavg_da)
	    {
		SysInfoDrawCPULoadAvg(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->memory_da)
	    {
		SysInfoDrawMemory(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->apm_power_da)
	    {
		SysInfoDrawAPMPower(si, -1.0f);
		status = TRUE;
	    }
	    break;

	  case GDK_FOCUS_CHANGE:
	    focus = (GdkEventFocus *)event;
	    if(GTK_WIDGET_CAN_FOCUS(widget))
	    {
		if(!GTK_WIDGET_HAS_FOCUS(widget) && focus->in)
		    GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
		else if(GTK_WIDGET_HAS_FOCUS(widget) && !focus->in)
		    GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
		status = TRUE;
	    }
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    /* Grab focus on the event widget if possible */
	    if(!GTK_WIDGET_HAS_FOCUS(widget) &&
	       GTK_WIDGET_CAN_FOCUS(widget)
	    )
		gtk_widget_grab_focus(widget);

	    /* All else check if Button3 to map right click menu */
	    if(button->button == 3)
	    {
		GtkWidget *w = si->menu;
		if(w != NULL)
		    gtk_menu_popup(
			GTK_MENU(w), NULL, NULL,
			NULL, NULL,
			button->button, button->time
		    );
		status = TRUE;
	    }
	    break;

	  case GDK_ENTER_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
	    if(widget == si->cpu_load_da)
	    {
		/* Update the pointer_in_widget and redraw to set/update
		 * the static tip                                 
		 */
		si->pointer_in_widget = widget;
		SysInfoDrawCPULoad(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->cpu_loadavg_da)
	    {
		/* Update the pointer_in_widget and redraw to set/update
		 * the static tip
		 */
		si->pointer_in_widget = widget;
		SysInfoDrawCPULoadAvg(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->memory_da)
	    {
		/* Update the pointer_in_widget and redraw to set/update
		 * the static tip
		 */
		si->pointer_in_widget = widget;
		SysInfoDrawMemory(si, -1.0f);
		status = TRUE;
	    }
	    else if(widget == si->apm_power_da)
	    {
		/* Update the pointer_in_widget and redraw to set/update
		 * the static tip
		 */
		si->pointer_in_widget = widget;
		SysInfoDrawAPMPower(si, -1.0f);
		status = TRUE;
	    }
	    break;

	  case GDK_LEAVE_NOTIFY:
	    if(widget == si->cpu_load_da)
	    {
		StaticTipSet(widget, NULL, 0, 0, 0, 0);
		status = TRUE;
	    }
	    else if(widget == si->cpu_loadavg_da)
	    {
		StaticTipSet(widget, NULL, 0, 0, 0, 0);
		status = TRUE;
	    }
	    else if(widget == si->memory_da)
	    {
		StaticTipSet(widget, NULL, 0, 0, 0, 0);
		status = TRUE;
	    }
	    else if(widget == si->apm_power_da)
	    {
		StaticTipSet(widget, NULL, 0, 0, 0, 0);
		status = TRUE;
	    }
	    /* Reset pointer_in_widget */
	    si->pointer_in_widget = NULL;
	    break;
	}
	return(status);
}

/*
 *	SysInfo Window show Memory toggle callback.
 */
static void SysInfoShowMemoryToggleCB(GtkWidget *widget, gpointer data)
{
	sysinfo_display_flags display;
	sysinfo_win_struct *si = SYSINFO_WIN(data);

	if(si->freeze_count > 0)
	    return;

	display = si->display;
	if(display & SYSINFO_DISPLAY_MEMORY)
	    display &= ~SYSINFO_DISPLAY_MEMORY;
	else
	    display |= SYSINFO_DISPLAY_MEMORY;

	SysInfoSetDisplay(si, display);
}

/*
 *	SysInfo Window show APM Power toggle callback.
 */
static void SysInfoShowAPMPowerToggleCB(GtkWidget *widget, gpointer data)
{
	sysinfo_display_flags display;
	sysinfo_win_struct *si = SYSINFO_WIN(data);

	if(si->freeze_count > 0)
	    return;

	display = si->display;
	if(display & SYSINFO_DISPLAY_APM_POWER)
	    display &= ~SYSINFO_DISPLAY_APM_POWER;
	else
	    display |= SYSINFO_DISPLAY_APM_POWER;

	SysInfoSetDisplay(si, display);
}

/*
 *	SysInfo Window show CPU Details toggle callback.
 */
static void SysInfoShowCPUDetailsToggleCB(GtkWidget *widget, gpointer data)
{
	sysinfo_display_flags display;
	sysinfo_win_struct *si = SYSINFO_WIN(data);

	if(si->freeze_count > 0)
	    return;

	display = si->display;
	if(display & SYSINFO_DISPLAY_CPU_DETAILS)
	    display &= ~SYSINFO_DISPLAY_CPU_DETAILS;
	else
	    display |= SYSINFO_DISPLAY_CPU_DETAILS;

	SysInfoSetDisplay(si, display);
}

/*
 *	SysInfo Window close callback.
 */
static void SysInfoCloseCB(GtkWidget *widget, gpointer data)
{
	sysinfo_win_struct *si = SYSINFO_WIN(data);
	SysInfoUnmap(si);
}


/*
 *	Returns a statically allocated string describing the CPU's
 *	vendor name.
 */
static gchar *SysInfoGetCPUVendor(void)
{
	gchar *s = NULL;
	const gchar *pfx = "vendor_id";
	static gchar buf[256];

	/* Open cpuinfo file for reading */
	FILE *fp = FOpen("/proc/cpuinfo", "rb");
	*buf = '\0';
	if(fp == NULL)
	    return(buf);

	/* Read each line until we get to the line that contains the
	 * information we want
	 */
	while(fgets(buf, sizeof(buf), fp) != NULL)
	{
	    if(strpfx(buf, pfx))
	    {
		s = buf;
		while((*s != '\0') && (*s != ':'))
		    s++;
		if(*s == ':')
		    s++;
		while(ISBLANK(*s))
		    s++;
		break;
	    }
	}
	FClose(fp);

	return((s != NULL) ? s : buf);
}

/*
 *	Returns a statically allocated string describing the CPU's
 *	model name.
 */
static gchar *SysInfoGetCPUModel(void)
{
	gchar *s = NULL;
	const gchar *pfx = "model name";
	static gchar buf[256];

	/* Open cpuinfo file for reading */
	FILE *fp = FOpen("/proc/cpuinfo", "rb");
	*buf = '\0';
	if(fp == NULL)
	    return(buf);

	/* Read each line until we get to the line that contains the
	 * information we want
	 */
	while(fgets(buf, sizeof(buf), fp) != NULL)
	{
	    if(strpfx(buf, pfx))
	    {
		s = strpbrk(buf, "\n\r");
		if(s != NULL)
		    *s = '\0';

		s = buf;
		while((*s != '\0') && (*s != ':'))
		    s++;
		if(*s == ':')
		    s++;
		while(ISBLANK(*s))
		    s++;
		break;
	    }
	}
	FClose(fp);

	return((s != NULL) ? s : buf);
}

/*
 *	Gets the CPU load average as a coefficient value from 0.0 to
 *	1.0.
 */
static gfloat SysInfoGetCPULoadAvg(void)
{
	gchar *s, buf[256];
	FILE *fp = FOpen("/proc/loadavg", "rb");
	if(fp == NULL)
	    return(0.0f);

	if(fgets(buf, sizeof(buf), fp) != NULL)
	    buf[sizeof(buf) - 1] = '\0';
	else
	    *buf = '\0';
	FClose(fp);

	s = buf;
	while(ISBLANK(*s))
	    s++;
	return(ATOF(s));
}

/*
 *	Gets the memory free and total, in bytes.
 */
static void SysInfoGetMemory(gulong *free, gulong *total)
{
	gchar *s = NULL;
	const gchar *pfx = "Mem:";
	static gchar buf[256];

	/* Open meminfo file for reading */
	FILE *fp = FOpen("/proc/meminfo", "rb");
	if(free != NULL)
	    *free = 0l;
	if(total != NULL)
	    *total = 0l;
	if(fp == NULL)
	    return;

	/* Read each line until we get to the line that contains the
	 * information we want
	 */
	while(fgets(buf, sizeof(buf), fp) != NULL)
	{
	    if(strpfx(buf, pfx))
	    {
		s = buf;
		while((*s != '\0') && (*s != ':'))
		    s++;
		if(*s == ':')
		    s++;
		while(ISBLANK(*s))
		    s++;
		break;
	    }
	}
	FClose(fp);

	if(s == NULL)
	    return;

	/* Total */	
	if(total != NULL)
	    *total = (gulong)ATOL(s);
	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Used (skip) */
	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Free */
	if(free != NULL)
	    *free = (gulong)ATOL(s);
}

/*
 *	Gets the APM power level as a coefficient value from 0.0 to
 *	1.0 (or -1.0 if on AC power).
 */
static gfloat SysInfoGetAPMPower(void)
{
	gchar *s = NULL;
	static gchar buf[256];

	/* Open apm file for reading */
	FILE *fp = FOpen("/proc/apm", "rb");
	*buf = '\0';
	if(fp == NULL)
	    return(0.0f);

	if(fgets(buf, sizeof(buf), fp) != NULL)
	    buf[sizeof(buf) - 1] = '\0';
	else
	    *buf = '\0';
	FClose(fp);

	s = buf;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	while((*s != '\0') && !ISBLANK(*s))
	    s++;
	while(ISBLANK(*s))
	    s++;

	return(ATOF(s));
}


/*
 *	Redraws the SysInfo Window's Bar.
 */
static void SysInfoDrawBar(
	sysinfo_win_struct *si,
	GtkWidget *w,		/* GtkDrawingArea */
	GdkPixmap *pixmap,	/* Back buffer */
	gfloat v, gfloat v_mid, gfloat v_high
)
{
	gint	x, y,
		width = w->allocation.width,
		height = w->allocation.height,
		total_ticks = 10;
	GdkWindow *window = w->window;
	GdkDrawable *drawable = (pixmap != NULL) ? pixmap : window;
	GdkGC *gc;
	GtkStateType state = GTK_WIDGET_STATE(w);
	GtkStyle *style = gtk_widget_get_style(w);
	if((width <= 0) || (height <= 0) || (window == NULL))
	    return;

	/* Draw by bar style */
	switch(si->bar_style)
	{
	  case SYSINFO_BAR_STYLE_CONTINUOUS:
	    /* Draw background */
	    gc = style->base_gc[state];
	    gdk_draw_rectangle(
		drawable, gc, TRUE,
		0, 0, width, height
	    );
	    /* Draw value */
	    gc = style->bg_gc[GTK_STATE_SELECTED];
	    switch(si->bar_orientation)
	    {
	      case SYSINFO_BAR_ORIENTATION_HORIZONTAL:
		x = 0;
		gdk_draw_rectangle(
		    drawable, gc, TRUE,
		    x, 0, width * v, height
		);
		break;
	      case SYSINFO_BAR_ORIENTATION_VERTICAL:
		y = height - (height * v);
		gdk_draw_rectangle(
		    drawable, gc, TRUE,
		    0, y, width, height - y
		);
		break;
	    }
	    break;
	  case SYSINFO_BAR_STYLE_DISCRETE:
	    /* Draw background */
	    gc = style->base_gc[state];
	    gdk_draw_rectangle(
		drawable, gc, TRUE,
		0, 0, width, height
	    );
	    /* Draw value */
	    if(total_ticks > 0)
	    {
		gint tick_len, stop_pos, border = 1;

		gc = style->bg_gc[GTK_STATE_SELECTED];

		switch(si->bar_orientation)
		{
		  case SYSINFO_BAR_ORIENTATION_HORIZONTAL:
		    tick_len = MAX(width / total_ticks, 3);
		    stop_pos = width * v;
		    for(x = 0;
			(x + (tick_len / 2)) < stop_pos;
			x += tick_len
		    )
			gdk_draw_rectangle(
			    drawable, gc, TRUE,
			    x, 0,
			    tick_len - border, height
			);
		    break;
		  case SYSINFO_BAR_ORIENTATION_VERTICAL:
		    tick_len = MAX(height / total_ticks, 3);
		    stop_pos = height - (height * v);
		    for(y = height - 1;
			(y - (tick_len / 2)) > stop_pos;
			y -= tick_len
		    )
			gdk_draw_rectangle(
			    drawable, gc, TRUE,
			    0, y - tick_len + border,
			    width, tick_len - border
			);
		    break;
		}
	    }
	    break;
	  case SYSINFO_BAR_STYLE_LED:
	    /* Draw background */
	    gc = si->bar_led_gc;
	    gdk_gc_set_foreground(gc, &si->bar_led_bg_color);
	    gdk_draw_rectangle(
		drawable, gc, TRUE,
		0, 0, width, height
	    );
	    /* Draw value */
	    if(total_ticks > 0)
	    {
		gint n, tick_len, stop_pos, border = 2;
		gint mid_pos, high_pos;
		gfloat intensity;
		GdkColor	*c,
				*low_c = si->bar_led_low_color,
				*mid_c = si->bar_led_mid_color,
				*high_c = si->bar_led_high_color;

		switch(si->bar_orientation)
		{
		  case SYSINFO_BAR_ORIENTATION_HORIZONTAL:
		    tick_len = MAX(width / total_ticks, 3);
		    stop_pos = width * v;
		    mid_pos = width * v_mid;
		    high_pos = width * v_high;
		    for(x = 0;
			x < stop_pos;
			x += tick_len
		    )
		    {
			/* Calculate intensity coeff */
			intensity = ((x + tick_len) < stop_pos) ?
			    1.0f :
			    CLIP((gfloat)(stop_pos - x) / (gfloat)tick_len,
				0.0f, 1.0f
			    );
			/* Calculate intensity color index */
			n = CLIP(intensity * (SYSINFO_BAR_LED_COLORS - 1),
			    0, SYSINFO_BAR_LED_COLORS - 1
			);
			/* Get color array */
			if(x >= high_pos)
			    c = high_c;
			else if(x >= mid_pos)
			    c = mid_c;
			else
			    c = low_c;
			gdk_gc_set_foreground(gc, &c[n]);
			gdk_draw_rectangle(
			    drawable, gc, TRUE,
			    x, 0,
			    tick_len - border, height
			);
		    }
		    break;
		  case SYSINFO_BAR_ORIENTATION_VERTICAL:
		    tick_len = MAX(height / total_ticks, 3);
		    stop_pos = height - (height * v);
		    mid_pos = height - (height * v_mid);
		    high_pos = height - (height * v_high);
		    for(y = height - 1;
			y > stop_pos;
			y -= tick_len
		    )
		    {
			/* Calculate intensity coeff */
			intensity = ((y - tick_len) > stop_pos) ?
			    1.0f :
			    CLIP((gfloat)(y - stop_pos) / (gfloat)tick_len,
				0.0f, 1.0f
			    );
			/* Calculate intensity color index */
			n = CLIP(intensity * (SYSINFO_BAR_LED_COLORS - 1),
			    0, SYSINFO_BAR_LED_COLORS - 1
			);
			/* Get color array */
			if(y <= high_pos)
			    c = high_c;
			else if(y <= mid_pos)
			    c = mid_c;
			else
			    c = low_c;
			gdk_gc_set_foreground(gc, &c[n]);
			gdk_draw_rectangle(
			    drawable, gc, TRUE,
			    0, y - tick_len + border,
			    width, tick_len - border
			);
		    }
		    break;
		}
	    }
	    break;
	}


	/* Put drawable to window if it is not the same as the window */
	if(drawable != window)
	{
	    gc = style->fg_gc[state];
	    gdk_draw_pixmap(
		window, gc, drawable,
		0, 0,		/* Source coordinates */
		0, 0,		/* Destination coordinates */
		width, height
	    );
	}
}

/*
 *	Redraws the SysInfo Window's CPU Load Bar.
 */
static void SysInfoDrawCPULoad(sysinfo_win_struct *si, gfloat v)
{
	GtkWidget *w = si->cpu_load_da;

	if(v < 0.0f)
	    v = si->cpu_load_last_value;
	else if(v == si->cpu_load_last_value)
	    return;

	/* Draw the bar */
	SysInfoDrawBar(
	    si, w, si->bar_pixmap,
	    v, 0.5f, 0.75f
	);

	/* Update the static tip if the pointer is in this widget */
	if(w == si->pointer_in_widget)
	{
	    gchar *s = g_strdup_printf(
		"%i%%",
		(gint)(v * 100.0f)
	    );
	    StaticTipSet(
		w, s,
		STATIC_TIP_ALIGN_WIDGET_CENTER,
		STATIC_TIP_ALIGN_WIDGET_MAX,
		0, 5
	    );
	    g_free(s);
	}
}

/*
 *	Redraws the SysInfo Window's CPU Load Average Bar.
 */
static void SysInfoDrawCPULoadAvg(sysinfo_win_struct *si, gfloat v)
{
	GtkWidget *w = si->cpu_loadavg_da;

	if(v < 0.0f)
	    v = si->cpu_loadavg_last_value;
	else if(v == si->cpu_loadavg_last_value)
	    return;

	/* Draw the bar */
	SysInfoDrawBar(
	    si, w, si->bar_pixmap,
	    v, 0.5f, 0.75f
	);

	/* Update the static tip if the pointer is in this widget */
	if(w == si->pointer_in_widget)
	{
	    gchar *s = g_strdup_printf(
		"%i%%",
		(gint)(v * 100.0f)
	    );
	    StaticTipSet(
		w, s,
		STATIC_TIP_ALIGN_WIDGET_CENTER,
		STATIC_TIP_ALIGN_WIDGET_MAX,
		0, 5
	    );
	    g_free(s);
	}
}

/*
 *	Redraws the SysInfo Window's Memory Bar.
 */
static void SysInfoDrawMemory(sysinfo_win_struct *si, gfloat v)
{
	GtkWidget *w = si->memory_da;

	if(v < 0.0f)
	    v = si->memory_last_value;
	else if(v == si->memory_last_value)
	    return;

	/* Draw the bar */        
	SysInfoDrawBar(
	    si, w, si->bar_pixmap,
	    v, 1.1f, 1.1f
	);

	/* Update the static tip if the pointer is in this widget */
	if(w == si->pointer_in_widget)                              
	{
	    gchar *s = g_strdup_printf(
		"%i%%  Total: %ld mb  Free: %ld mb",
		(gint)(v * 100.0f),
		si->memory_total,
		si->memory_free
	    );
	    StaticTipSet(   
		w, s,
		STATIC_TIP_ALIGN_WIDGET_CENTER,
		STATIC_TIP_ALIGN_WIDGET_MAX,
		0, 5
	    );
	    g_free(s);
	}
}

/*
 *      Redraws the SysInfo Window's APM Power Bar.
 */
static void SysInfoDrawAPMPower(sysinfo_win_struct *si, gfloat v)
{
	GtkWidget *w = si->apm_power_da;

	if(v < 0.0f)
	    v = si->apm_power_last_value;
	else if(v == si->apm_power_last_value)
	    return;

	/* Draw the bar */
	SysInfoDrawBar(
	    si, w, si->bar_pixmap,
	    v,
	    (v < 0.2f) ? 1.1f : ((v < 0.4f) ? 0.0f : 1.1f),
	    (v < 0.2f) ? 0.0f : 1.1f
	);

	/* Update the static tip if the pointer is in this widget */
	if(w == si->pointer_in_widget)                              
	{
	    gchar *s = g_strdup_printf(
		"%i%%",
		(gint)(v * 100.0f)
	    );
	    StaticTipSet(
		w, s,
		STATIC_TIP_ALIGN_WIDGET_CENTER,
		STATIC_TIP_ALIGN_WIDGET_MAX,
		0, 5
	    );
	    g_free(s);
	}
}

/*
 *	Updates the SysInfo Window's Details.
 */
static void SysInfoUpdateDetails(sysinfo_win_struct *si)
{
	gchar *buf;
	GtkEditable *editable;
	GtkText *text;
	GtkWidget *w = (si != NULL) ? si->details_text : NULL;
	if(w == NULL)
	    return;

	editable = GTK_EDITABLE(w);
	text = GTK_TEXT(w);

	gtk_text_freeze(text);

	/* Delete existing message */
	gtk_editable_delete_text(editable, 0, -1);

	/* Format details message */
	buf = g_strdup_printf(
	    "\
Vendor: %s\n\
Model: %s\n",
		SysInfoGetCPUVendor(),
		SysInfoGetCPUModel()
	);

	/* Insert new message */
	gtk_text_insert(
	    text, NULL, NULL, NULL, buf, -1
	);

	gtk_text_thaw(text);
}


/*
 *	Creates a new SysInfo Window.
 */
sysinfo_win_struct *SysInfoNew(
	gpointer core_ptr,
	sysinfo_display_flags display,
	sysinfo_bar_style bar_style,
	sysinfo_bar_orientation bar_orientation
)
{
	const gint	border_major = 5,
			border_minor = 2,
			label_max_width = 35;
	const gboolean	vertical = (bar_orientation & SYSINFO_BAR_ORIENTATION_VERTICAL) ?
			    TRUE : FALSE;
	const gint	bar_width = vertical ? 10 : 100,
			bar_height = vertical ? 100 : 10;
	gint i;
	GdkColor *c;
	GdkColormap *colormap;
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget	*w, *main_box,
			*parent, *parent2, *parent3, *parent4, *parent5;
	GtkAccelGroup *accelgrp;
	sysinfo_win_struct *si = SYSINFO_WIN(g_malloc0(
	    sizeof(sysinfo_win_struct)
	));

	si->accelgrp = accelgrp = gtk_accel_group_new();
	si->processing = FALSE;
	si->busy_count = 0;
	si->freeze_count = 0;
	si->core_ptr = core_ptr;

	si->display = display;
	si->bar_style = bar_style;
	si->bar_orientation = bar_orientation;

	si->bar_pixmap = NULL;

	si->cpu_load_last_value = 0.0f;
	si->cpu_loadavg_last_value = 0.0f;
	si->memory_last_value = 0.0f;
	si->memory_free = 0l;
	si->memory_total = 0l;
	si->apm_power_last_value = 0.0f;

	si->pointer_in_widget = NULL;

	/* Begin creating widgets */

	/* Toplevel */
	si->toplevel = w = gtk_window_new(GTK_WINDOW_DIALOG);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_window_set_title(GTK_WINDOW(w), SysInfoGetCPUModel());
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "sysinfo", PROG_NAME
	);
	gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, TRUE);
	gtk_widget_realize(w);
	colormap = gtk_widget_get_colormap(w);
	style = gtk_widget_get_style(w);
	window = w->window;
	if(window != NULL)
	{
	    GdkGeometry geo;

	    geo.min_width = 10;
	    geo.min_height = 10;
	    geo.base_width = 0;
	    geo.base_height = 0;
	    geo.width_inc = 1;
	    geo.height_inc = 1;
	    gdk_window_set_geometry_hints(
		window, &geo,
		GDK_HINT_MIN_SIZE |
		GDK_HINT_BASE_SIZE |
		GDK_HINT_RESIZE_INC
	    );

	    gdk_window_set_decorations(
		window,
		GDK_DECOR_BORDER | GDK_DECOR_TITLE | GDK_DECOR_MENU |
		GDK_DECOR_MINIMIZE
	    );
	    gdk_window_set_functions(
		window,
		GDK_FUNC_MOVE | GDK_FUNC_MINIMIZE | GDK_FUNC_CLOSE
	    );
	    GUISetWMIcon(
		window,
		(guint8 **)icon_cpu_48x48_xpm
	    );
	}
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
	parent = w;

	/* Main box */
	si->main_box = main_box = w = vertical ?
	    gtk_hbox_new(FALSE, border_major) :
	    gtk_vbox_new(FALSE, border_major);
	gtk_container_border_width(GTK_CONTAINER(w), border_major);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;


	/* Box for CPU icon and load bars */
	w = vertical ?
	    gtk_vbox_new(FALSE, border_major) :
	    gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;


	/* Box for CPU Icon */
	w = vertical ? gtk_hbox_new(TRUE, 0) :
	    gtk_vbox_new(TRUE, 0);
	if(vertical)
	    gtk_box_pack_end(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	else
	    gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;
	/* CPU Icon */
	w = gtk_pixmap_new_from_xpm_d(
	    window,
	    (style != NULL) ?
		&style->bg[GTK_STATE_NORMAL] : NULL,
	    (guint8 **)icon_cpu_32x32_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Box for CPU Bars */
	w = vertical ? gtk_hbox_new(TRUE, border_minor) :
	    gtk_vbox_new(TRUE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;


	/* Box for CPU Load Bar */
	si->cpu_load_box = w = vertical ?
	    gtk_vbox_new(FALSE, border_minor) :
	    gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	if(display & SYSINFO_DISPLAY_CPU_LOAD)
	    gtk_widget_show(w);
	parent4 = w;

	if(vertical)
	{
	    /* Label */
	    w = gtk_label_new("L");
	    gtk_box_pack_end(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	}
	else
	{
	    /* Alignment for label */
	    w = gtk_alignment_new(1.0f, 0.5f, 0.0f, 0.0f);
	    gtk_widget_set_usize(w, label_max_width, -1);
	    gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent5 = w;
	    /* Label */
	    w = gtk_label_new("Load:");
	    gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	    gtk_container_add(GTK_CONTAINER(parent5), w);
	    gtk_widget_show(w);
	}

	w = vertical ? gtk_hbox_new(TRUE, 0) : gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;

	/* Frame for drawing area */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;
	/* Drawing area */
	si->cpu_load_da = w = gtk_drawing_area_new();
	gtk_widget_set_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_drawing_area_size(
	    GTK_DRAWING_AREA(w), bar_width, bar_height
	);
	gtk_container_add(GTK_CONTAINER(parent5), w);
	gtk_widget_show(w);


	/* Box for CPU Load Average Bar */
	si->cpu_loadavg_box = w = vertical ?
	    gtk_vbox_new(FALSE, border_minor) :
	    gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	if(display & SYSINFO_DISPLAY_CPU_LOADAVG)
	    gtk_widget_show(w);
	parent4 = w;

	if(vertical)
	{
	    /* Label */
	    w = gtk_label_new("A");
	    gtk_box_pack_end(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	}
	else
	{
	    /* Alignment for label */
	    w = gtk_alignment_new(1.0f, 0.5f, 0.0f, 0.0f);
	    gtk_widget_set_usize(w, label_max_width, -1);
	    gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent5 = w;
	    /* Label */
	    w = gtk_label_new("Avg:");
	    gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	    gtk_container_add(GTK_CONTAINER(parent5), w);
	    gtk_widget_show(w);
	}

	w = vertical ? gtk_hbox_new(TRUE, 0) : gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;

	/* Frame for drawing area */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;
	/* Drawing area */
	si->cpu_loadavg_da = w = gtk_drawing_area_new();
	gtk_widget_set_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_drawing_area_size(
	    GTK_DRAWING_AREA(w), bar_width, bar_height
	);
	gtk_container_add(GTK_CONTAINER(parent5), w);
	gtk_widget_show(w);


	/* Box for Memory Bar */
	si->memory_box = w = vertical ?
	    gtk_vbox_new(FALSE, border_minor) :
	    gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	if(display & SYSINFO_DISPLAY_MEMORY)
	    gtk_widget_show(w);
	parent4 = w;

	if(vertical)
	{
	    /* Label */
	    w = gtk_label_new("M");
	    gtk_box_pack_end(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	}
	else
	{
	    /* Alignment for label */
	    w = gtk_alignment_new(1.0f, 0.5f, 0.0f, 0.0f);
	    gtk_widget_set_usize(w, label_max_width, -1);
	    gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent5 = w;
	    /* Label */
	    w = gtk_label_new("Mem:");
	    gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	    gtk_container_add(GTK_CONTAINER(parent5), w);
	    gtk_widget_show(w);
	}

	w = vertical ? gtk_hbox_new(TRUE, 0) : gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;

	/* Frame for drawing area */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;
	/* Drawing area */
	si->memory_da = w = gtk_drawing_area_new();
	gtk_widget_set_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_drawing_area_size(
	    GTK_DRAWING_AREA(w), bar_width, bar_height
	);
	gtk_container_add(GTK_CONTAINER(parent5), w);
	gtk_widget_show(w);


	/* Box for APM Power Bar */
	si->apm_power_box = w = vertical ?
	    gtk_vbox_new(FALSE, border_minor) :
	    gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	if(display & SYSINFO_DISPLAY_APM_POWER)
	    gtk_widget_show(w);
	parent4 = w;

	if(vertical)
	{
	    w = GUICreateMenuItemIcon(
		(guint8 **)icon_power_20x20_xpm
	    );
	    gtk_box_pack_end(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	}
	else
	{
	    /* Alignment for icon */
	    w = gtk_alignment_new(1.0f, 0.5f, 0.0f, 0.0f);
	    gtk_widget_set_usize(w, label_max_width, -1);
	    gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent5 = w;
	    /* Icon */
	    w = GUICreateMenuItemIcon(
		(guint8 **)icon_power_20x20_xpm
	    );
	    gtk_container_add(GTK_CONTAINER(parent5), w);
	    gtk_widget_show(w);
	}

	w = vertical ? gtk_hbox_new(TRUE, 0) : gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;

	/* Frame for drawing area */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent5 = w;
	/* Drawing area */
	si->apm_power_da = w = gtk_drawing_area_new();
	gtk_widget_set_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(SysInfoEventCB), si
	);
	gtk_drawing_area_size(
	    GTK_DRAWING_AREA(w), bar_width, bar_height
	);
	gtk_container_add(GTK_CONTAINER(parent5), w);
	gtk_widget_show(w);


	/* Box for Details */
	si->details_box = w = vertical ?
	    gtk_vbox_new(FALSE, border_minor) :
	    gtk_vbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	if(display & SYSINFO_DISPLAY_CPU_DETAILS)
	    gtk_widget_show(w);
	parent2 = w;

	w = gtk_frame_new("Details");
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_ETCHED_IN);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Table for Details Text */
	w = gtk_table_new(2, 2, FALSE);
	gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_container_border_width(GTK_CONTAINER(w), border_major);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;

	if(parent2 != NULL)
	{
	    GtkRcStyle *rcstyle = gtk_rc_style_new();
	    GtkWidget *sb;
	    GtkTable *table = GTK_TABLE(parent2);
	    GtkEditable *editable;
	    GtkText *text;

	    rcstyle->font_name = STRDUP(
"-adobe-courier-medium-r-normal-*-12-*-*-*-*-*-iso8859-1"
	    );

	    /* Details GtkText */
	    si->details_text = w = gtk_text_new(NULL, NULL);
	    editable = GTK_EDITABLE(w);
	    text = GTK_TEXT(w);
	    gtk_widget_set_usize(w, -1, 80);
	    gtk_text_set_editable(text, FALSE);
	    gtk_text_set_word_wrap(text, TRUE);
	    gtk_table_attach(
		table, w,
		0, 1, 0, 1,
		GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		0, 0
	    );
	    gtk_widget_realize(w);
	    gtk_widget_modify_style(w, rcstyle);
/*	    gdk_window_set_cursor(w->window, d->text_cur); */
	    gtk_widget_show(w);

	    /* Vertical scroll bar */
	    sb = gtk_vscrollbar_new(GTK_TEXT(w)->vadj);
	    gtk_table_attach(
		table, sb,
		1, 2, 0, 1,
		GTK_FILL,
		GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		0, 0
	    );
	    gtk_widget_show(sb);

	    gtk_rc_style_unref(rcstyle);
	}



	/* Right-click menu */
	si->menu = w = GUIMenuCreate();
	if(w != NULL)
	{
	    guint8 **icon;
	    const gchar *label;
	    guint accel_key, accel_mods;
	    GtkAccelGroup *accel_group = NULL;
	    gpointer mclient_data = si;
	    GtkWidget *menu = w;
	    void (*func_cb)(GtkWidget *w, gpointer);

#define DO_ADD_MENU_ITEM_LABEL  {               \
 w = GUIMenuItemCreate(                         \
  menu, GUI_MENU_ITEM_TYPE_LABEL, accel_group,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb                         \
 );                                             \
}
#define DO_ADD_MENU_ITEM_CHECK	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accel_group,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_SEP         {               \
 w = GUIMenuItemCreate(                         \
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,     \
  NULL, NULL, 0, 0,                             \
  NULL,                                         \
  NULL, NULL                                    \
 );                                             \
}

	    icon = NULL;
	    label = "Show Memory";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = SysInfoShowMemoryToggleCB;
	    DO_ADD_MENU_ITEM_CHECK
	    si->display_memory_micheck = w;

	    icon = NULL;
	    label = "Show APM Power";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = SysInfoShowAPMPowerToggleCB;
	    DO_ADD_MENU_ITEM_CHECK
	    si->display_apm_power_micheck = w;

	    icon = NULL;
	    label = "Show CPU Details";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = SysInfoShowCPUDetailsToggleCB;
	    DO_ADD_MENU_ITEM_CHECK
	    si->display_cpu_details_micheck = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_close_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Cierre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fermer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Nah"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Vicino"
#elif defined(PROG_LANGUAGE_DUTCH)
"Einde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Próximo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Nær"
#else
"Close"
#endif
	    ;
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = SysInfoCloseCB;
	    DO_ADD_MENU_ITEM_LABEL

#undef DO_ADD_MENU_SEP
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_ITEM_LABEL
	}


	/* Allocate led colors */
	si->bar_led_gc = gdk_gc_new(window);

	c = &si->bar_led_bg_color;
	GDK_COLOR_SET_COEFF(c, 0.0f, 0.0f, 0.0f);
	GDK_COLORMAP_ALLOC_COLOR(colormap, c);

	for(i = 0; i < SYSINFO_BAR_LED_COLORS; i++)
	{
	    gfloat v = (gfloat)(i + 1) / (gfloat)SYSINFO_BAR_LED_COLORS;

	    c = &si->bar_led_low_color[i];
	    GDK_COLOR_SET_COEFF(c, 0.0f, v, 0.0f);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, c);

	    c = &si->bar_led_mid_color[i];
	    GDK_COLOR_SET_COEFF(c, v, v, 0.0f);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, c);

	    c = &si->bar_led_high_color[i];
	    GDK_COLOR_SET_COEFF(c, v, 0.0f, 0.0f);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, c);
	}


	/* Update Details */
	SysInfoUpdateDetails(si);


	return(si);
}

/*
 *	Sets the SysInfo Window's display flags.
 */
void SysInfoSetDisplay(
	sysinfo_win_struct *si, sysinfo_display_flags display
)
{
	GtkWidget *w;

	if(si == NULL)
	    return;

	if(si->display == display)
	    return;

	si->display = display;

	/* Display CPU Load */
	w = si->cpu_load_box;
	if(w != NULL)
	{
	    if(display & SYSINFO_DISPLAY_CPU_LOAD)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Display CPU Load Average */
	w = si->cpu_loadavg_box;
	if(w != NULL)
	{
	    if(display & SYSINFO_DISPLAY_CPU_LOADAVG)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Display Memory */
	w = si->memory_box;
	if(w != NULL)
	{
	    if(display & SYSINFO_DISPLAY_MEMORY)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Display APM Power */
	w = si->apm_power_box;
	if(w != NULL)
	{
	    if(display & SYSINFO_DISPLAY_APM_POWER)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Display Details */
	w = si->details_box;
	if(w != NULL)
	{
	    if(display & SYSINFO_DISPLAY_CPU_DETAILS)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Update widgets and redraw */
	SysInfoUpdate(si);
}

/*
 *	Sets the SysInfo Window's bar style.
 */
void SysInfoSetBarStyle(
	sysinfo_win_struct *si, sysinfo_bar_style bar_style
)
{
	if(si == NULL)
	    return;

	if(si->bar_style == bar_style)
	    return;

	si->bar_style = bar_style;

	SysInfoUpdate(si);
}

/*
 *	Sets the SysInfo Window's bar orientation.
 */
void SysInfoSetBarOrientation(
	sysinfo_win_struct *si, sysinfo_bar_orientation bar_orientation
)
{
	if(si == NULL)
	    return;

/* Currently the orientation cannot be changed after creation */

}

/*
 *	Updates the SysInfo Window's widgets to reflect current
 *	values.
 */
void SysInfoUpdate(sysinfo_win_struct *si)
{
	gfloat v, dv, max_change = 0.2f;
	gulong free, total;

	if(si == NULL)
	    return;

	/* Begin updating */
#define SET_CHECK_MENU_ITEM(_w_,_b_)		\
{ if((_w_) != NULL) {				\
 if(GTK_CHECK_MENU_ITEM(_w_)->active != (_b_))	\
  gtk_check_menu_item_set_active(		\
   GTK_CHECK_MENU_ITEM(_w_), (_b_)		\
  );						\
} }

	si->freeze_count++;

	SET_CHECK_MENU_ITEM(
	    si->display_memory_micheck,
	    (si->display & SYSINFO_DISPLAY_MEMORY) ? TRUE : FALSE
	);
	SET_CHECK_MENU_ITEM(
	    si->display_apm_power_micheck,
	    (si->display & SYSINFO_DISPLAY_APM_POWER) ? TRUE : FALSE
	);
	SET_CHECK_MENU_ITEM(
	    si->display_cpu_details_micheck,
	    (si->display & SYSINFO_DISPLAY_CPU_DETAILS) ? TRUE : FALSE
	);

	si->freeze_count--;


	/* CPU Load */
	v = ExecCPUGetLoad(-1);
	dv = v - si->cpu_load_last_value;
	if(dv > max_change)
	    v = si->cpu_load_last_value + max_change;
	else if(dv < -max_change)
	    v = si->cpu_load_last_value - max_change;
/*	v = CLIP(v, 0.0f, 1.0f);*/
	SysInfoDrawCPULoad(si, v);
	si->cpu_load_last_value = v;

	/* CPU Load Average */
	v = SysInfoGetCPULoadAvg();
	SysInfoDrawCPULoadAvg(si, v);
	si->cpu_loadavg_last_value = v;

	/* Memory */
	SysInfoGetMemory(&free, &total);
	si->memory_free = free;
	si->memory_total = total;
	v = (total > 0l) ? (gfloat)free / (gfloat)total : 0.0f;
	SysInfoDrawMemory(si, v);
	si->memory_last_value = v;

	/* APM Power */
	v = SysInfoGetAPMPower();
	/* On AC? */
	if(v < 0.0f)
	    v = 1.0f;
	SysInfoDrawAPMPower(si, v);
	si->apm_power_last_value = v;


#undef SET_CHECK_MENU_ITEM
}

/*
 *	Checks if the SysInfo Window is mapped.
 */
gboolean SysInfoIsMapped(sysinfo_win_struct *si)
{
	GtkWidget *w = (si != NULL) ? si->toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
}

/*
 *	Maps the SysInfo Window.
 */
void SysInfoMap(sysinfo_win_struct *si)
{
	GtkWidget *w = (si != NULL) ? si->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_show_raise(w);
}

/*
 *	Unmaps the SysInfo Window.
 */
void SysInfoUnmap(sysinfo_win_struct *si)
{
	GtkWidget *w = (si != NULL) ? si->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
}

/*
 *	Deletes the SysInfo Window.
 */
void SysInfoDelete(sysinfo_win_struct *si)
{
	GdkColormap *colormap;

	if(si == NULL)
	    return;

	colormap = gtk_widget_get_colormap(si->toplevel);
	GDK_COLORMAP_FREE_COLOR(
	    colormap, &si->bar_led_bg_color
	);
	GDK_COLORMAP_FREE_COLORS(
	    colormap, si->bar_led_low_color, SYSINFO_BAR_LED_COLORS
	);
	GDK_COLORMAP_FREE_COLORS(
	    colormap, si->bar_led_mid_color, SYSINFO_BAR_LED_COLORS
	);
	GDK_COLORMAP_FREE_COLORS(
	    colormap, si->bar_led_high_color, SYSINFO_BAR_LED_COLORS
	);

	GDK_GC_UNREF(si->bar_led_gc);

	GTK_WIDGET_DESTROY(si->menu);
	GTK_WIDGET_DESTROY(si->toplevel);

	GDK_PIXMAP_UNREF(si->bar_pixmap);

	GTK_ACCEL_GROUP_UNREF(si->accelgrp);

	g_free(si);
}
