#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "guiutils.h"
#include "progressdialog.h"


/*
 *	Animated Icon structure:
 */
typedef struct {

	GdkPixmap	*pixmap;
	GdkBitmap	*mask;
	gint		width, height;

} icon_struct;
#define ICON(p)		((icon_struct *)(p))

/*
 *	Progress Dialog structure:
 */
typedef struct {

	gboolean	initialized,
			map_state;
	gint		stop_count;
	GtkWidget	*transient_for;		/* Transient for widget */

	GtkAccelGroup	*accelgrp;

	GtkWidget	*toplevel,
			*main_vbox,
			*icon_vbox,	/* Holds icon fixed and pixmap widgets */
			*icon_fixed,	/* For single icon mode */
			*icon_pm,
			*label,
			*progress_bar,
			*stop_btn,
			*stop_btn_label;

	/* Members for animated progress display */
	GdkGC		*animation_gc;
	GtkWidget	*animation_da;
	GdkPixmap	*animation_buffer;
	guint16		animation_position,
			animation_increment;
	gulong		animation_time_last,
			animation_interval;	/* In ms */

	icon_struct	**start_icon;
	gint		total_start_icons;
	icon_struct	**icon;
	gint		total_icons;
	icon_struct	**end_icon;
	gint		total_end_icons;

} progress_dialog_struct;
#define PROGRESS_DIALOG(p)	((progress_dialog_struct *)(p))
static progress_dialog_struct progress_dialog;


/* Callbacks */
static void ProgressDialogStopCB(GtkWidget *w, gpointer data);
static gint ProgressDialogCloseCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint ProgressDialogAnimationDrawCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
);

/* Utilities */
static gulong ProgressDialogCurrentTime(void);
static void ProgressDialogChangeIcon(
	progress_dialog_struct *pd, const guint8 **icon_data
);
static icon_struct *ProgressDialogIconNew(
	progress_dialog_struct *pd, const guint8 **icon_data
);
static void ProgressDialogIconDelete(icon_struct *icon);
static void ProgressDialogIconDeleteAll(progress_dialog_struct *pd);

/* Front Ends */
gint ProgressDialogInit(void);
void ProgressDialogSetStyle(GtkRcStyle *rc_style);
void ProgressDialogSetTransientFor(GtkWidget *w);
void ProgressDialogSetWMIconData(const guint8 **icon_data);
gboolean ProgressDialogIsQuery(void);
void ProgressDialogBreakQuery(gboolean allow_gtk_iteration);
gint ProgressDialogStopCount(void);
void ProgressDialogMap(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label
);
void ProgressDialogMapAnimation(
	const gchar *title,
	const gchar *label,
	const gchar *stop_btn_label,
	guint8 ***start_icon_data, gint total_start_icon_datas,
	guint8 ***icon_data, gint total_icon_datas,
	guint8 ***end_icon_data, gint total_end_icon_datas,
	gulong animation_interval, guint16 animation_increment
);
void ProgressDialogUpdate(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label,
	gdouble position,		/* 0.0 to 1.0 */
	guint nblocks,			/* 0 for continuous */
	gboolean allow_gtk_iteration
);
void ProgressDialogUpdateUnknown(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label,
	gboolean allow_gtk_iteration
);
void ProgressDialogUnmap(void);
void ProgressDialogShutdown(void);


#define PROGRESS_DIALOG_WIDTH		380
#define PROGRESS_DIALOG_HEIGHT		-1

#define PROGRESS_DIALOG_ANIMATION_DA_WIDTH      270
#define PROGRESS_DIALOG_ANIMATION_DA_HEIGHT	45


/* #define PROGRESS_DIALOG_PROGRESS_HEIGHT	(20 + (2 * 2)) */
#define PROGRESS_DIALOG_PROGRESS_HEIGHT		20


#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)))


/*
 *	Stop button callback.
 */
static void ProgressDialogStopCB(GtkWidget *w, gpointer data)
{
	progress_dialog_struct *pd = PROGRESS_DIALOG(data);
	if(pd == NULL)
	    return;

	if(pd->stop_count < 0)
	    pd->stop_count = 0;

	pd->stop_count++;

	/* Give up on the fifth press? */
	if(pd->stop_count >= 5)
	    ProgressDialogUnmap();
}

/*
 *	Close callback.
 */
static gint ProgressDialogCloseCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	ProgressDialogStopCB(widget, data);

	return(TRUE);
}

/*
 *	Animation redraw and "expose_event" signal callback.
 */
static gint ProgressDialogAnimationDrawCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
	GtkStateType state;
	gint	width,
		height,
		start_icon_width = 0,
		end_icon_width = 0,
		frame,
		total_frames,
		anim_per_frame;
	GdkGC *gc;
	GdkWindow *window;
	GdkPixmap *pixmap;
	GdkDrawable *drawable;
	GtkStyle *style;
	GtkWidget *w;
	progress_dialog_struct *pd = PROGRESS_DIALOG(data);
	if(pd == NULL)
	    return(TRUE);

	w = pd->animation_da;
	if((w == NULL) || !pd->map_state)
	    return(TRUE);

	if(!GTK_WIDGET_VISIBLE(w))
	    return(TRUE);

	state = GTK_WIDGET_STATE(w);
	window = w->window;
	gc = pd->animation_gc;
	style = gtk_widget_get_style(w);
	if((window == NULL) || (gc == NULL) || (style == NULL))
	    return(TRUE);

	width = w->allocation.width;
	height = w->allocation.height;
	if((width <= 0) || (height <= 0))
	    return(TRUE);

	pixmap = pd->animation_buffer;
	drawable = (pixmap != NULL) ? pixmap : window;


	/* Begin drawing */

	/* Draw background */
	gtk_style_apply_default_background(
	    style, drawable, FALSE, state,
	    NULL,
	    0, 0, width, height
	);


	/* Begin drawing icons */

	/* Calculate total frames, this is the number of icons in the
	 * inside animatino frame plus two end point icons (the start and
	 * end icons)
	 */
	total_frames = MAX(pd->total_icons + 2, 2);

	/* Calculate animation frame index */
	frame = (gfloat)pd->animation_position / (gfloat)((guint16)-1) *
	    total_frames;

	/* Calculate number of animation units in one frame */
	if(total_frames > 0)
	    anim_per_frame = (gint)((guint16)-1) / total_frames;
	else
	    anim_per_frame = 0;


	/* Draw start icons */
	if(pd->total_start_icons > 0)
	{
	    icon_struct *icon;

	    /* Check which icon structure to draw based on frame index */
	    if((frame == 0) && (pd->total_start_icons > 0))
	    {
		icon = pd->start_icon[0];
	    }
	    else if((frame == (total_frames - 1)) && (pd->total_start_icons > 2))
	    {
		icon = pd->start_icon[2];
	    }
	    else if(pd->total_start_icons > 1)
	    {
		icon = pd->start_icon[1];
	    }
	    else
	    {
		icon = NULL;
	    }

	    /* Got icon? */
	    if(icon != NULL)
	    {
		gint    icon_width = icon->width,
			icon_height = icon->height,
			x = 0,
			y = height - icon_height - 1;
		GdkBitmap *icon_mask = icon->mask;
		GdkPixmap *icon_pixmap = icon->pixmap;

		/* Record start icon width */
		start_icon_width = icon_width;

		/* Icon pixmap available for drawing? */
		if(icon_pixmap != NULL)
		{
		    /* Set icon mask? */
		    if(icon_mask != NULL)
		    {
			gdk_gc_set_clip_mask(gc, icon_mask);
			gdk_gc_set_clip_origin(gc, x, y);
		    }
		    else
		    {
			gdk_gc_set_clip_mask(gc, NULL);
		    }
		    /* Draw icon */
		    gdk_draw_pixmap(
			drawable, gc, icon_pixmap,
			0, 0,
			x, y,
			icon_width, icon_height
		    );
		}
	    }
	}

	/* Draw end icons */
	if(pd->total_end_icons > 0)
	{
	    icon_struct *icon;

	    /* Check which icon structure to draw based on frame index */
	    if((frame == 0) && (pd->total_end_icons > 0))
	    {
		icon = pd->end_icon[0];
	    }
	    else if((frame == (total_frames - 1)) && (pd->total_end_icons > 2))
	    {
		icon = pd->end_icon[2];
	    }
	    else if(pd->total_end_icons > 1)
	    {
		icon = pd->end_icon[1];
	    }
	    else
	    {
		icon = NULL;
	    }

	    /* Found a valid icon structure to draw? */
	    if(icon != NULL)
	    {
		gint	icon_width = icon->width,
			icon_height = icon->height,
			x = width - icon->width - 1,
			y = height - icon->height - 1;
		GdkBitmap *icon_mask = icon->mask;
		GdkPixmap *icon_pixmap = icon->pixmap;

		/* Record end icon width */
		end_icon_width = icon->width;

		/* Pixmap available for drawing? */
		if(icon_pixmap != NULL)
		{
		    /* Mask available? */
		    if(icon_mask != NULL)
		    {
			gdk_gc_set_clip_mask(gc, icon_mask);
			gdk_gc_set_clip_origin(gc, x, y);
		    }
		    else
		    {
			gdk_gc_set_clip_mask(gc, NULL);
		    }

		    gdk_draw_pixmap(
			drawable, gc, icon_pixmap,
			0, 0,
			x, y,
			icon_width, icon_height
		    );
		}
	    }
	}


	/* Draw intermediate icons */
	if((pd->total_icons > 0) &&
	   (frame > 0) && (frame < (total_frames - 1))
	)
	{
	    gint i, iframe_max;
	    gfloat iframe_coeff;
	    icon_struct *icon = NULL;


	    /* Calculate inside frame coeff */
	    iframe_max = (gint)((guint16)-1) - (2 * anim_per_frame);
	    if(iframe_max > 0)
		iframe_coeff = CLIP(
		    ((gfloat)pd->animation_position - (gfloat)anim_per_frame) /
		    (gfloat)iframe_max, 0.0f, 1.0f
		);
	    else
		iframe_coeff = 0.0f;

	    /* Calculate icon index based on inside frame coeff */
	    i = iframe_coeff * pd->total_icons;

	    /* Check which icon structure to draw based on frame index */
	    if((i >= 0) && (i < pd->total_icons))
		icon = pd->icon[i];
	    else
		icon = NULL;

	    /* Found a valid icon structure to draw? */
	    if(icon != NULL)
	    {
		gint	y_border = 0,
			icon_width = icon->width,
			icon_height = icon->height,
			x, y, range;
		GdkBitmap *icon_mask = icon->mask;
		GdkPixmap *icon_pixmap = icon->pixmap;

		/* Calculate x position based on iframe_coeff and range */
		range = MAX(
		    width - start_icon_width - end_icon_width - icon_width,
		    0
		);
		x = (iframe_coeff * range) + start_icon_width;

		/* Calculate y position based on iframe_coeff and range */
		range = MAX(
		    (height / 2) - (icon_height / 2) - y_border,
		    0
		);
		if(iframe_coeff > 0.5f)
		{
		    gfloat tc = (iframe_coeff - 0.5) / 0.5;
		    y = (tc * tc) * range;
		}
		else
		{
		    gfloat tc = 1.0 - (iframe_coeff / 0.5);
		    y = (tc * tc) * range;
		}
		y += y_border;	/* Add 5 pixels margin */


		/* Pixmap available for drawing? */
		if(icon_pixmap != NULL)
		{
		    /* Mask available? */
		    if(icon_mask != NULL)
		    {
			gdk_gc_set_clip_mask(gc, icon_mask);
			gdk_gc_set_clip_origin(gc, x, y);
		    }
		    else
		    {
			gdk_gc_set_clip_mask(gc, NULL);
		    }

		    gdk_draw_pixmap(
			pixmap, gc, icon_pixmap,
			0, 0,
			x, y,
			icon_width, icon_height
		    );
		}
	    }
	}
	gdk_gc_set_clip_mask(gc, NULL);


	/* Put buffer to window as needed */
	if(drawable != window)
	gdk_window_copy_area(
	    window, gc,
	    0, 0,
	    drawable,
	    0, 0,
	    width, height
	);

	return(TRUE);
}


/*
 *	Returns the current time in milliseconds.
 */
static gulong ProgressDialogCurrentTime(void)
{
#ifdef __MSW__
	SYSTEMTIME t;   /* Current time of day structure */

	GetSystemTime(&t);
	return(
	    (gulong)(
		(((((t.wHour * 60.0) + t.wMinute) * 60) + t.wSecond) * 1000) +
		t.wMilliseconds
	    )
	);
#else
	struct timeval tv[1];

	if(gettimeofday(tv, NULL) < 0)
	    return(-1);

	return(((tv->tv_sec % 86400) * 1000) + (tv->tv_usec / 1000));
#endif
}



/*
 *	Recreates a new icon for the given progress dialog.
 *
 *	Inputs assumed valid.
 */
static void ProgressDialogChangeIcon(
	progress_dialog_struct *pd, const guint8 **icon_data
)
{
	gint width, height;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	GtkWidget *w, *parent = (pd != NULL) ? pd->icon_fixed : NULL;
	if(parent == NULL)
	    return;

	pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(
	    &mask, (guint8 **)icon_data
	);
	if(pixmap == NULL)
	    return;

	/* Get GtkPixmap widget, create it as needed */
	w = pd->icon_pm;
	if(w == NULL)
	{
	    pd->icon_pm = w = gtk_pixmap_new(pixmap, mask);
	    gtk_fixed_put(GTK_FIXED(parent), w, 0, 0);
	    gtk_widget_show(w);
	}
	else
	{
	    gtk_pixmap_set(GTK_PIXMAP(w), pixmap, mask);
	}

	gtk_widget_shape_combine_mask(parent, mask, 0, 0);

	gdk_window_get_size(pixmap, &width, &height);
	gtk_widget_set_usize(parent, width, height);

	GDK_PIXMAP_UNREF(pixmap)
	GDK_BITMAP_UNREF(mask)

#if 0
	/* Set WM icon for toplevel */
/*
 Do not set toplevel icon, let it be parent relative to the transient
 window.
 */
	GUISetWMIcon(toplevel->window, (guint8 **)icon_data);
#endif
}

/*
 *	Allocates and returns a new icon structure, loading the icon
 *	data from icon_data.
 */
static icon_struct *ProgressDialogIconNew(
	progress_dialog_struct *pd, const guint8 **icon_data
)
{
	gint width, height;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	icon_struct *icon;

	if((pd == NULL) || (icon_data == NULL))
	    return(NULL);

	pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(
	    &mask, (guint8 **)icon_data
	);
	if(pixmap == NULL)
	    return(NULL);

	/* Create a new animated icon */
	icon = ICON(g_malloc0(sizeof(icon_struct)));
	if(icon == NULL)
	{
	    GDK_PIXMAP_UNREF(pixmap)
	    GDK_BITMAP_UNREF(mask)
	    return(NULL);
	}

	icon->pixmap = pixmap;
	icon->mask = mask;

	/* Get size */
	gdk_window_get_size(pixmap, &width, &height);
	icon->width = width;
	icon->height = height;

	return(icon);
}

/*
 *	Deletes the animation icon structure and all its resources.
 */
static void ProgressDialogIconDelete(icon_struct *icon)
{
	if(icon == NULL)
	    return;

	GDK_PIXMAP_UNREF(icon->pixmap)
	GDK_BITMAP_UNREF(icon->mask)
	g_free(icon);
}

/*
 *	Deletes all animation icons on the given progress dialog.
 */
static void ProgressDialogIconDeleteAll(
	progress_dialog_struct *pd
)
{
	gint i;


	if(pd == NULL)
	    return;

	/* Delete all animation start icons */
	for(i = 0; i < pd->total_start_icons; i++)
	    ProgressDialogIconDelete(pd->start_icon[i]);
	g_free(pd->start_icon);
	pd->start_icon = NULL;
	pd->total_start_icons = 0;

	/* Delete all animation intermediate icons */
	for(i = 0; i < pd->total_icons; i++)
	    ProgressDialogIconDelete(pd->icon[i]);
	g_free(pd->icon);
	pd->icon = NULL;
	pd->total_icons = 0;

	/* Delete all animation end icons */
	for(i = 0; i < pd->total_end_icons; i++)
	    ProgressDialogIconDelete(pd->end_icon[i]);
	g_free(pd->end_icon);
	pd->end_icon = NULL;
	pd->total_end_icons = 0;
}

/*
 *	Initializes the Progress Dialog.
 *
 *	Returns non-zero on error.
 */
gint ProgressDialogInit(void)
{
	const gint border_major = 5;
	gint width, height;
	GdkGC *gc;
	GdkWindow *window;
	GtkWidget *w, *parent, *parent2, *parent3;
	GtkAdjustment *adj;
	GtkAccelGroup *accelgrp;
	progress_dialog_struct *pd = &progress_dialog;


	/* Reset globals */
/* None */

	/* Reset values */
	memset(pd, 0x00, sizeof(progress_dialog_struct));

	pd->initialized = TRUE;
	pd->map_state = FALSE;
	pd->stop_count = 0;;
	pd->transient_for = NULL;


	/* Keyboard accelerator group */
	pd->accelgrp = accelgrp = gtk_accel_group_new();


	/* Toplevel */
	pd->toplevel = w = gtk_window_new(GTK_WINDOW_DIALOG);
	gtk_widget_set_usize(w, PROGRESS_DIALOG_WIDTH, PROGRESS_DIALOG_HEIGHT);
	gtk_window_set_title(GTK_WINDOW(w), "Progress");
#ifdef PROG_NAME
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "dialog", PROG_NAME
	);
#endif
	gtk_widget_realize(w);                          
	window = w->window;
	if(window != NULL)
	{
	    GdkGeometry geo;

	    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
	    );

	    geo.min_width = 100;
	    geo.min_height = 70;
	    geo.max_width = gdk_screen_width() - 10;
	    geo.max_height = gdk_screen_height() - 10;
	    geo.base_width = 0;
	    geo.base_height = 0;
	    geo.width_inc = 1;
	    geo.height_inc = 1;
	    geo.min_aspect = 1.3f;
	    geo.max_aspect = 1.3f;
	    gdk_window_set_geometry_hints(
		window, &geo,
		GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE |
		GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC
	    );
	}
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(ProgressDialogCloseCB), pd
	);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
	parent = w;


	/* Main vbox */
	pd->main_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;


	/* Hbox for animated drawing area (double border) */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(
	    GTK_BOX(parent), w,
	    FALSE, FALSE, 2 * border_major
	);
	gtk_widget_show(w);
	parent2 = w;
	/* Animated drawing area */
	width = PROGRESS_DIALOG_ANIMATION_DA_WIDTH;
	height = PROGRESS_DIALOG_ANIMATION_DA_HEIGHT;
	pd->animation_da = w = gtk_drawing_area_new();
	gtk_widget_set_events(
	    w,
	    GDK_EXPOSURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(ProgressDialogAnimationDrawCB), pd
	);
	gtk_drawing_area_size(GTK_DRAWING_AREA(w), width, height);
	/* Pack into parent box with double border */
	gtk_box_pack_start(
	    GTK_BOX(parent2), w,
	    FALSE, FALSE, 2 * border_major
	);
/*	gtk_widget_show(w); */
	/* Animated drawing area buffer */
	pd->animation_buffer = GDK_PIXMAP_NEW(width, height);


	/* Hbox for icon and label */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, border_major);
	gtk_widget_show(w);
	parent2 = w;

	/* Vbox to align icon fixed */
	pd->icon_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, border_major);
/*	gtk_widget_show(w); */
	parent3 = w;

	/* Icon fixed */
	pd->icon_fixed = w = gtk_fixed_new();
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_realize(w);
	gtk_widget_show(w);

	pd->icon_pm = NULL;	/* No icon GtkPixmap at startup */

	/* Vbox to align label */
	w = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, border_major);
	gtk_widget_show(w);
	parent3 = w;
	/* Hbox to align label to left */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;
	/* Label */
	pd->label = w = gtk_label_new("Processing...");
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Hbox to hold progress bar and stop button */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, border_major);
	gtk_widget_show(w);
	parent2 = w;

	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, border_major);
	gtk_widget_show(w);
	parent2 = w;


	/* Vbox for progress bar */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* Progress bar */
	adj = (GtkAdjustment *)gtk_adjustment_new(0, 1, 100, 0, 5, 5);
	pd->progress_bar = w = gtk_progress_bar_new_with_adjustment(adj);
	gtk_widget_set_usize(w, -1, PROGRESS_DIALOG_PROGRESS_HEIGHT);
	gtk_progress_bar_set_orientation(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_LEFT_TO_RIGHT
	);
	gtk_progress_bar_set_bar_style(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_CONTINUOUS
	);
	gtk_progress_set_activity_mode(  
	    GTK_PROGRESS(w), FALSE
	);
#if 0
/* This was to make progress bar vertically aligned with the stop
   button homogeniously. We don't want that.
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
 */
#endif
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Stop button */
	pd->stop_btn = w = gtk_button_new();
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_widget_set_usize(
	    w,
	    GUI_BUTTON_HLABEL_WIDTH_DEF, GUI_BUTTON_HLABEL_HEIGHT_DEF
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ProgressDialogStopCB),
	    (gpointer)pd
	);
	gtk_accel_group_add(
	    accelgrp, GDK_Escape, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_widget_show(w);
	parent3 = w;
	/* Stop button label */
	pd->stop_btn_label = w = gtk_label_new("Stop");
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);



	/* Create graphic context for animating icons */
	pd->animation_gc = gc = GDK_GC_NEW();


	return(0);
}

/*
 *      Sets the Dialog's style.
 */
void ProgressDialogSetStyle(GtkRcStyle *rc_style)
{
	GtkWidget *w;
	progress_dialog_struct *pd = &progress_dialog;

	if(!pd->initialized)
	    return;

	w = pd->toplevel;
	if(w != NULL)
	{
	    if(rc_style != NULL)
	    {
		gtk_widget_modify_style(w, rc_style);
	    }
	    else
	    {
		rc_style = gtk_rc_style_new();
		gtk_widget_modify_style_recursive(w, rc_style);
		GTK_RC_STYLE_UNREF(rc_style)
	    }
	}
}

/*
 *	Sets the Dialog to be a transient for the given GtkWindow w.
 *
 *	If w is NULL then transient for will be unset.
 */
void ProgressDialogSetTransientFor(GtkWidget *w)
{
	progress_dialog_struct *pd = &progress_dialog;


	if(!pd->initialized)
	    return;

	if(pd->toplevel != NULL)
	{
	    if(w != NULL)
	    {
		if(!GTK_IS_WINDOW(GTK_OBJECT(w)))
		    return;

		if(GTK_WINDOW(w)->modal)
		    gtk_window_set_modal(GTK_WINDOW(w), FALSE);

		gtk_window_set_modal(
		    GTK_WINDOW(pd->toplevel), TRUE
		);
		gtk_window_set_transient_for(
		    GTK_WINDOW(pd->toplevel), GTK_WINDOW(w)
		);
		pd->transient_for = w;
	    }
	    else
	    {
		gtk_window_set_modal(
		    GTK_WINDOW(pd->toplevel), FALSE
		);
		gtk_window_set_transient_for(
		    GTK_WINDOW(pd->toplevel), NULL
		);
		pd->transient_for = NULL;
	    }
	}
}

/*
 *	Explicitly sets the progress dialog's toplevel WM icon.
 */
void ProgressDialogSetWMIconData(const guint8 **icon_data)
{
	GtkWidget *w;
	progress_dialog_struct *pd = &progress_dialog;


	if(!pd->initialized)
	    return;

	w = pd->toplevel;
	if(w == NULL)
	    return;

	/* Set WM icon for toplevel */
	GUISetWMIcon(w->window, (guint8 **)icon_data);
}

/*
 *	Returns TRUE if dialog is mapped.
 */
gboolean ProgressDialogIsQuery(void)
{
	progress_dialog_struct *pd = &progress_dialog;

	if(!pd->initialized)
	    return(FALSE);
	else
	    return(pd->map_state);
}

/*
 *      Just unmaps the dialog.
 */
void ProgressDialogBreakQuery(gboolean allow_gtk_iteration)
{
	progress_dialog_struct *pd = &progress_dialog;


	if(allow_gtk_iteration)
	{
	    while((gtk_events_pending() > 0) && pd->initialized)
		gtk_main_iteration();
	}
	gdk_flush();
	ProgressDialogUnmap();
	gdk_flush();
	if(allow_gtk_iteration)
	{
	    while((gtk_events_pending() > 0) && pd->initialized)
		gtk_main_iteration();
	}
}

/*
 *	Returns the number of stop counts.
 */
gint ProgressDialogStopCount(void)
{
	progress_dialog_struct *pd = &progress_dialog;
		    
	if(!pd->initialized)
	    return(0);
	else
	    return(pd->stop_count);
}

/*
 *	Maps the progress dialog for standard progress display.
 *
 *	The icon_data specifies the icon xpm data to load for the progress
 *	icon. The size of the icon should be 32x32.
 */
void ProgressDialogMap(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label
)
{
	progress_dialog_struct *pd = &progress_dialog;
	GtkWidget *w;


	if(!pd->initialized)
	    return;

	/* Reset stop count */
	pd->stop_count = 0;

	/* Set new title? */
	if(title != NULL)
	{
	    w = pd->toplevel;
	    if(w != NULL)
		gtk_window_set_title(GTK_WINDOW(w), title);
	}

	/* Set new label? */
	if(label != NULL)
	{
	    w = pd->label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), label);
	}


	/* Map icon vbox that holds icon fixed and pixmap widgets */
	if(pd->icon_vbox != NULL)
	    gtk_widget_show(pd->icon_vbox);

	/* Unmap the animation drawing area */
	if(pd->animation_da != NULL)
	    gtk_widget_hide(pd->animation_da);


	/* Change icon? */
	if(icon_data != NULL)
  	    ProgressDialogChangeIcon(pd, icon_data);
	
	/* Change stop button label? */
	if(stop_btn_label != NULL)
	{
	    w = pd->stop_btn_label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), stop_btn_label);
	}

	/* Set stop button to be the default button */
	w = pd->stop_btn;
	if(w != NULL)
	{
	    gtk_widget_grab_focus(w);
	    gtk_widget_grab_default(w);
	}

	/* Map progress dialog (as needed) */
	if(!pd->map_state)
	{
	    w = pd->toplevel;
	    gtk_widget_show_raise(w);
	    pd->map_state = TRUE;
	}
}

/*
 *      Maps the progress dialog in animation mode and loads the given
 *	set of animation icon xpm data.
 *
 *	The animation_interval specifies the maximum lapse of time
 *	per increment in ms (typical value would be 500 ms).
 *
 *	The animation_increment specifies a positive value up to
 *	(guint16)-1. Smaller values produce slower and finer animation
 *	while larger values produce faster and rougher animation.
 */
void ProgressDialogMapAnimation(
	const gchar *title,
	const gchar *label,
	const gchar *stop_btn_label,
	guint8 ***start_icon_data, gint total_start_icon_datas,
	guint8 ***icon_data, gint total_icon_datas,
	guint8 ***end_icon_data, gint total_end_icon_datas,
	gulong animation_interval, guint16 animation_increment
)
{
	gint i;
	progress_dialog_struct *pd = &progress_dialog;
	GtkWidget *w;


	if(!pd->initialized)
	    return;

	/* Reset stop count */
	pd->stop_count = 0;

	/* Set new title? */
	if(title != NULL)
	{
	    w = pd->toplevel;
	    if(w != NULL)
		gtk_window_set_title(GTK_WINDOW(w), title);
	}

	/* Set new label? */
	if(label != NULL)
	{
	    w = pd->label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), label);
	}


	/* Unmap icon vbox that holds icon fixed and pixmap widgets */
	if(pd->icon_vbox != NULL)
	    gtk_widget_hide(pd->icon_vbox);

	/* Map the animation drawing area */
	if(pd->animation_da != NULL)
	    gtk_widget_show(pd->animation_da);


	/* Change stop button label? */
	if(stop_btn_label != NULL)
	{
	    w = pd->stop_btn_label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), stop_btn_label);
	}


	/* Begin setting new animation icons */

	/* Deallocate all existing animation icons (if any) */
	ProgressDialogIconDeleteAll(pd);

	/* Start icons */
	pd->total_start_icons = total_start_icon_datas;
	if(pd->total_start_icons > 0)
	{
	    pd->start_icon = (icon_struct **)g_realloc(
		pd->start_icon,
		pd->total_start_icons * sizeof(icon_struct *)
	    );
	    if(pd->start_icon == NULL)
		pd->total_start_icons = 0;
	    for(i = 0; i < pd->total_start_icons; i++)
		pd->start_icon[i] = ProgressDialogIconNew(
		    pd, (const guint8 **)start_icon_data[i]
		);
	}
	/* Icons */
	pd->total_icons = total_icon_datas;
	if(pd->total_icons > 0)
	{
	    pd->icon = (icon_struct **)g_realloc(
		pd->icon,
		pd->total_icons * sizeof(icon_struct *)
	    );
	    if(pd->icon == NULL)
		pd->total_icons = 0;
	    for(i = 0; i < pd->total_icons; i++)
		pd->icon[i] = ProgressDialogIconNew(
		    pd, (const guint8 **)icon_data[i]
		);
	}
	/* End icons */
	pd->total_end_icons = total_end_icon_datas;
	if(pd->total_end_icons > 0)
	{
	    pd->end_icon = (icon_struct **)g_realloc(
		pd->end_icon,
		pd->total_end_icons * sizeof(icon_struct *)
	    );
	    if(pd->end_icon == NULL)
		pd->total_end_icons = 0;
	    for(i = 0; i < pd->total_end_icons; i++)
		pd->end_icon[i] = ProgressDialogIconNew(
		    pd, (const guint8 **)end_icon_data[i]
		);
	}

	/* Reset animation values */
	pd->animation_time_last = ProgressDialogCurrentTime();
	pd->animation_interval = animation_interval;
	pd->animation_position = 0;
	pd->animation_increment = animation_increment;


	/* Set stop button to be the default button */
	w = pd->stop_btn;
	if(w != NULL)
	{
	    gtk_widget_grab_focus(w);
	    gtk_widget_grab_default(w);
	}

	/* Map progress dialog (as needed) */
	if(!pd->map_state)
	{
	    w = pd->toplevel;
	    gtk_widget_show_raise(w);
	    pd->map_state = TRUE;
	}
}


/*
 *	Updates the progress dialog with the given values.
 *
 *	This function has no affect if ProgressDialogMap() was not called
 *	before to map the progress dialog.
 *
 *	If allow_gtk_iteration is TRUE then gtk_main_iteration()
 *	may be called one or more times during this call (note possible
 *	reenterant issue).
 */
void ProgressDialogUpdate(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label,
	gdouble position,               /* 0.0 to 1.0 */
	guint nblocks,			/* 0 for continuous */
	gboolean allow_gtk_iteration
)
{
	progress_dialog_struct *pd = &progress_dialog;
	GtkWidget *w;


	if(!pd->initialized || !pd->map_state)
	    return;

	/* Change title? */
	if(title != NULL)
	{
	    w = pd->toplevel;
	    if(w != NULL)
		gtk_window_set_title(GTK_WINDOW(w), title);
	}

	/* Change label? */
	if(label != NULL)
	{
	    w = pd->label;
	    if(w != NULL)  
		gtk_label_set_text(GTK_LABEL(w), label);
	}

	/* Change icon? */
	if(icon_data != NULL)
	    ProgressDialogChangeIcon(pd, icon_data);

	/* Change stop button label? */
	if(stop_btn_label != NULL)
	{
	    w = pd->stop_btn_label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), stop_btn_label);
	}

	/* Begin updating progress bar position */
	w = pd->progress_bar;
	if(w != NULL)
	{
	    GtkProgress *pr = GTK_PROGRESS(w);
	    GtkProgressBar *pb = GTK_PROGRESS_BAR(w);


	    if(position > 1.0)
		position = 1.0;
	    else if(position < 0.0)
		position = 0.0;

	    gtk_progress_set_activity_mode(pr, FALSE);
/*
	    gtk_progress_set_format_string(pr, "%p%%");
	    gtk_progress_set_show_text(pr, FALSE);
 */
	    if(nblocks > 0)
	    {
		gtk_progress_bar_set_bar_style(
		    pb, GTK_PROGRESS_DISCRETE
		);
		gtk_progress_bar_set_discrete_blocks(pb, nblocks);
	    }
	    else
	    {
		gtk_progress_bar_set_bar_style(
		    pb, GTK_PROGRESS_CONTINUOUS
		);
	    }
	    gtk_progress_bar_update(pb, position);
	}

	/* Check if animation drawing area widget is mapped */
	w = pd->animation_da;
	if((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE)
	{
#if 0
/* For syncing animation position with progress */
	    /* Set animation position to match progress position */
	    pd->animation_position = (guint16)(
		position * (guint16)-1
	    );

	    /* Redraw animation */
	    ProgressDialogAnimationDrawCB(w, NULL, pd);
#endif

	    gulong cur_time = ProgressDialogCurrentTime();
	    gulong dt;
	    gfloat tc;


	    /* Calculate delta time from last time update */
	    if(cur_time >= pd->animation_time_last)
		dt = cur_time - pd->animation_time_last;
	    else
		dt = 0;
	    if(dt > pd->animation_interval)
		dt = pd->animation_interval;

	    /* Calculate time coefficient */
	    if(pd->animation_interval > 0)
		tc = (gfloat)dt / (gfloat)pd->animation_interval;
	    else
		tc = 0.0;

	    pd->animation_position += (guint16)(
		pd->animation_increment * tc
	    );


	    /* Redraw animation */
	    ProgressDialogAnimationDrawCB(w, NULL, pd);

	    /* Record animation handled time */
	    pd->animation_time_last = cur_time;
	}

	/* Allow gtk main iteration to be called so that updated
	 * values get enacted within this call?
	 */
	if(allow_gtk_iteration)
	{
	    while((gtk_events_pending() > 0) && pd->initialized)
		gtk_main_iteration();
	}
}

/*
 *      Updates the progress dialog with the given values.
 *
 *      This function has no affect if ProgressDialogMap() was not called
 *      before to map the progress dialog.
 *
 *      If allow_gtk_iteration is TRUE then gtk_main_iteration()
 *      may be called one or more times during this call (note possible
 *      reenterant issue).
 */
void ProgressDialogUpdateUnknown(
	const gchar *title,
	const gchar *label,
	const guint8 **icon_data,
	const gchar *stop_btn_label,
	gboolean allow_gtk_iteration
)
{
	progress_dialog_struct *pd = &progress_dialog;
	GtkWidget *w;
  

	if(!pd->initialized || !pd->map_state)
	    return;

	/* Set new title? */
	if(title != NULL)
	{
	    w = pd->toplevel;
	    if(w != NULL)
		gtk_window_set_title(GTK_WINDOW(w), title);
	}

	/* Set new label? */
	if(label != NULL)
	{
	    w = pd->label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), label);
	}

	/* Change icon? */   
	if(icon_data != NULL)
	    ProgressDialogChangeIcon(pd, icon_data);

	/* Change stop button label? */
	if(stop_btn_label != NULL)
	{
	    w = pd->stop_btn_label;
	    if(w != NULL)
		gtk_label_set_text(GTK_LABEL(w), stop_btn_label);
	}
 
	/* Begin updating progress bar position in activity mode */
	w = pd->progress_bar;
	if(w != NULL)
	{
	    GtkProgress *pr = GTK_PROGRESS(w);
	    GtkProgressBar *pb = GTK_PROGRESS_BAR(w);
	    GtkAdjustment *adj;
	    gdouble position;


	    adj = pr->adjustment;
	    position = gtk_progress_get_value(pr) + 1.0;
	    if(adj != NULL)
	    {
		if(position > adj->upper)
		    position = adj->lower;
	    }

	    gtk_progress_set_activity_mode(pr, TRUE);
	    gtk_progress_set_show_text(pr, FALSE);
	    gtk_progress_bar_set_bar_style(
		pb, GTK_PROGRESS_CONTINUOUS
	    );
	    gtk_progress_set_value(pr, position);
	}

	/* Check if animation drawing area widget is mapped */
	w = pd->animation_da;
	if((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE)
	{
	    gulong cur_time = ProgressDialogCurrentTime();
	    gulong dt;
	    gfloat tc;


	    /* Calculate delta time from last time update */
	    if(cur_time >= pd->animation_time_last)
		dt = cur_time - pd->animation_time_last;
	    else
		dt = 0;
	    if(dt > pd->animation_interval)
		dt = pd->animation_interval;

	    /* Calculate time coefficient */
	    if(pd->animation_interval > 0)
		tc = (gfloat)dt / (gfloat)pd->animation_interval;
	    else
		tc = 0.0;

	    pd->animation_position += (guint16)(
		pd->animation_increment * tc
	    );


	    /* Redraw animation */
	    ProgressDialogAnimationDrawCB(w, NULL, pd);

	    /* Record animation handled time */
	    pd->animation_time_last = cur_time;
	}

	/* Allow gtk main iteration to be called so that updated
	 * values get enacted within this call?
	 */
	if(allow_gtk_iteration)
	{
	    while((gtk_events_pending() > 0) && pd->initialized)
		gtk_main_iteration();
	}
}

/*
 *	Unmaps the progress dialog.
 */
void ProgressDialogUnmap(void)
{
	progress_dialog_struct *pd = &progress_dialog;
	GtkWidget *w;


	if(!pd->initialized)
	    return;

	/* Delete all animation icons (if any) */
	ProgressDialogIconDeleteAll(pd);

/* Do not check current map state */

	w = pd->toplevel;
	if(w != NULL)
	{
	    gtk_widget_hide(w);
	    gtk_widget_unmap(w);
	    pd->map_state = FALSE;
	}
}

/*
 *	Deallocates all resources on the progress dialog structure.
 */
void ProgressDialogShutdown(void)
{
	progress_dialog_struct *pd = &progress_dialog;

	if(pd->initialized)
	{
	    /* Unset transient for widget as needed */
	    if(pd->transient_for != NULL)
		ProgressDialogSetTransientFor(NULL);

	    /* Delete all animation icons */
	    ProgressDialogIconDeleteAll(pd);

	    /* Delete animation resources */
	    GTK_WIDGET_DESTROY(pd->animation_da)
	    pd->animation_da = NULL;

	    GDK_PIXMAP_UNREF(pd->animation_buffer)
	    pd->animation_buffer = NULL;

	    GDK_GC_UNREF(pd->animation_gc)
	    pd->animation_gc = NULL;

	    /* Destroy widgets */
	    GTK_WIDGET_DESTROY(pd->icon_pm)
	    pd->icon_pm = NULL;
	    GTK_WIDGET_DESTROY(pd->toplevel)
	    pd->toplevel = NULL;

	    GTK_ACCEL_GROUP_UNREF(pd->accelgrp)
	    pd->accelgrp = NULL;
	}

	/* Clear progress dialog structure */
	memset(pd, 0x00, sizeof(progress_dialog_struct));
}
