#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "guiutils.h"
#include "clipboard.h"

#ifdef MEMWATCH
# include "memwatch.h"
#endif


#include "images/icon_close_20x20.xpm"
#include "images/icon_clear_20x20.xpm"
#include "images/icon_reload_20x20.xpm"
#include "images/icon_clipboard_16x16.xpm"
#include "images/icon_clipboard_empty_16x16.xpm"
#include "images/icon_clipboard_48x48.xpm"
#include "images/icon_clipboard_empty_48x48.xpm"
#include "images/icon_owned_16x16.xpm"
#include "images/icon_unowned_16x16.xpm"


/*
 *      Clipboard Data Types:
 */
#define CLIPBOARD_DATA_TYPE_ASCII	0
#define CLIPBOARD_DATA_TYPE_BINARY	1


/*
 *      Clipboard:
 */
typedef struct {

	gboolean	initialized,
			map_state;

	gint		busy_count;

	GdkCursor	*busy_cur,
			*pan_cur;

	GtkAccelGroup	*accelgrp;

	GtkWidget	*toplevel,
			*main_vbox,
			*menu_bar,
			*tool_ribbon,
			*notebook,
			*status_bar_dock,
			*status_bar_frame,
			*status_content_icon_fixed,
			*status_content_icon_pm,
			*status_owned_icon_fixed,
			*status_owned_icon_pm,
			*status_size_label,
			*status_mesg_label;

	GtkWidget       *owner_btn;     /* Button widget to own `selections'
					 * it's never shown to the user
					 */
	/* Buttons on tool ribbon */
	GtkWidget       *clear_btn;

	/* Important menu items */
	GtkWidget       *clear_mi,
			*monitor_changes_micheck,
			*view_text_micheck,
			*view_binary_micheck;

	/* Right click menu */
	GtkWidget       *view_menu,
			*view_clear_mi,
			*view_refresh_mi;

	/* Containers and childs on notebook */
	GtkWidget       *view_text_viewport,
			*view_text_event_box,
			*view_text_vbox,        /* Holds childs */

			*view_binary_viewport,
			*view_binary_event_box,
			*view_binary_vbox;      /* Holds childs */

	gint		notebook_cur_page;



	/* Childs in text viewport */
	GtkWidget	**view_text_widget;
	gint		total_view_text_widgets;

	/* Childs in binary viewport */
	GtkWidget	**view_binary_widget;
	gint		total_view_binary_widgets;

	gboolean	own_buffer,		/* Own selection buffer */
			waiting_response;	/* TRUE when waiting for a
						 * buffer request response
						 */

	gint		data_type;	/* One of CLIPBOARD_DATA_TYPE_* */

	guchar		*data;		/* Local and dynamically allocated */
	gint		data_length;

	gboolean	monitor_changes;
	guint		monitor_changes_toid;

} clipboard_browser_struct;
#define CLIPBOARD_BROWSER(p)	((clipboard_browser_struct *)(p))


/* Utilities */
static void ClipboardBrowserSetBusy(clipboard_browser_struct *cb);
static void ClipboardBrowserSetReady(clipboard_browser_struct *cb);

static void ClipboardBrowserSetStatusMesg(
	clipboard_browser_struct *cb,
	const char *mesg
);
static void ClipboardBrowserSetStatusIcon(clipboard_browser_struct *cb);
static void ClipboardBrowserDDEClear(clipboard_browser_struct *cb);
static void ClipboardBrowserRecordWidget(
	GtkWidget ***list, gint *total, GtkWidget *w
);
		    
/* Callbacks */
static void ClipboardBrowserClearMCB(GtkWidget *widget, gpointer data);
static void ClipboardBrowserRefreshMCB(GtkWidget *widget, gpointer data);
static gint ClipboardBrowserCloseCB(
	GtkWidget *widget, GdkEvent *event,
	clipboard_browser_struct *cb
);
static void ClipboardBrowserCloseMCB(GtkWidget *widget, gpointer data);
static void ClipboardBrowserDestroyCB(
	GtkObject *object, clipboard_browser_struct *cb
);
static gint ClipboardBrowserViewEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void ClipboardBrowserSwitchPageCB(
	GtkNotebook *notebook, GtkNotebookPage *page, guint page_num,
	gpointer data
);

static void ClipboardBrowserViewCheckMCB(GtkWidget *widget, gpointer data);
static gint ClipboardMonitorChangesTOCB(gpointer data);

/* Fetching callbacks */
static void ClipboardBufferBufferRecievedCB(
	GtkWidget *widget, GtkSelectionData *selection_data,
	gpointer null_data
);

/* Putting callbacks */
static gint ClipboardClearCB(
	GtkWidget *widget, GdkEventSelection *event, gpointer data
);
static gint ClipboardNotifyCB(
	GtkWidget *widget, GdkEventSelection *event, gpointer data
);
static void ClipboardBufferRequestCB(
	GtkWidget *widget, GtkSelectionData *selection_data,
	guint info, guint time_stamp,
	gpointer data
);

/* Fetch & Put Data */
guint8 *ClipboardFetchBinary(int *length);
static void ClipboardBrowserFetch(clipboard_browser_struct *cb);
gint ClipboardPutBinary(const guint8 *data, gint length);

/* Clipboard browser management */
static void ClipboardBrowserLoadCursors(clipboard_browser_struct *cb);
static void ClipboardBrowserCreateMenuBar(
	clipboard_browser_struct *cb, GtkWidget *parent
);
static void ClipboardBrowserCreateToolRibbon(
	clipboard_browser_struct *cb, GtkWidget *parent
);
gint ClipboardBrowserInit(void);
void ClipboardBrowserReset(
	clipboard_browser_struct *cb, gboolean need_unmap
);
void ClipboardBrowserUpdateMenus(clipboard_browser_struct *cb);
void ClipboardBrowserMap(void);
void ClipboardBrowserUnmap(void);
void ClipboardBrowserShutdown(void);


#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 STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


#define CLIPBOARD_DEF_WIDTH     	640
#define CLIPBOARD_DEF_HEIGHT    	480

#define CLIPBOARD_STATUSBAR_HEIGHT	26


#ifndef GDK_BUTTON1
# define GDK_BUTTON1	1
#endif

#ifndef GDK_BUTTON2
# define GDK_BUTTON2	2
#endif

#ifndef GDK_BUTTON3
# define GDK_BUTTON3	3
#endif


static clipboard_browser_struct clipboard_browser;



/*
 *	Marks the clipboard browser as busy.
 */
static void ClipboardBrowserSetBusy(clipboard_browser_struct *cb)
{
	GtkWidget *w = (cb != NULL) ? cb->toplevel : NULL;
	if(w == NULL)
	    return;

	cb->busy_count++;

	if((w->window != NULL) && (cb->busy_count == 1))
	{
	    gdk_window_set_cursor(w->window, cb->busy_cur);
	    gdk_flush();
	}
}

/*
 *	Marks clipboard browser as ready.
 */
static void ClipboardBrowserSetReady(clipboard_browser_struct *cb)
{
	GtkWidget *w = (cb != NULL) ? cb->toplevel : NULL;
	if(w == NULL)
	    return;

	cb->busy_count--;
	if(cb->busy_count < 0)
	    cb->busy_count = 0;

	if((w->window != NULL) && (cb->busy_count == 0))
	{
	    gdk_window_set_cursor(w->window, NULL);
	    gdk_flush();
	}
}

/*
 *	Updates the status message.
 */
static void ClipboardBrowserSetStatusMesg(
	clipboard_browser_struct *cb, const char *mesg
)
{
	GtkWidget *w = (cb != NULL) ? cb->status_mesg_label : NULL;
	if(w == NULL)
	    return;

	gtk_label_set_text(
	    GTK_LABEL(w),
	    (mesg != NULL) ? mesg : ""
	);
}

/*
 *	Updates the clipboard browser's status bar icon.
 */
static void ClipboardBrowserSetStatusIcon(clipboard_browser_struct *cb)
{
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget *w, *parent;


	if(cb == NULL)
	    return;

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

	window = w->window;
	style = gtk_widget_get_style(w);

	/* Set toplevel WM icon depending on if the clipboard has
	 * data or not
	 */
	GUISetWMIcon(
	    window,
	    (guint8 **)((cb->data != NULL) ?
		icon_clipboard_48x48_xpm :
		icon_clipboard_empty_48x48_xpm
	    )
	);

	/* Update status content icon */
	parent = cb->status_content_icon_fixed;
	if(parent != NULL)
	{
	    GdkBitmap *mask;
	    GdkPixmap *pixmap;
	    guint8 **data = (guint8 **)((cb->data != NULL) ?
		icon_clipboard_16x16_xpm :
		icon_clipboard_empty_16x16_xpm
	    );

	    /* Load pixmap */
	    pixmap = gdk_pixmap_create_from_xpm_d(
		window, &mask,
		&style->bg[GTK_STATE_NORMAL],
		(gchar **)data
	    );
	    if(pixmap != NULL)
	    {
		gint width, height;

		gdk_window_get_size(pixmap, &width, &height);

		/* Update pixmap widget, create it as needed */
		w = cb->status_content_icon_pm;
		if(w != NULL)
		{
		    gtk_pixmap_set(GTK_PIXMAP(w), pixmap, mask);
		}
		else
		{
		    cb->status_content_icon_pm = w = gtk_pixmap_new(
			pixmap, mask
		    );
		    gtk_fixed_put(GTK_FIXED(parent), w, 0, 0);
		    gtk_widget_show(w);
		}

		/* Adjust size of fixed widget to fit pixmap */
		gtk_widget_set_usize(parent, width, height);

		gtk_widget_shape_combine_mask(parent, mask, 0, 0);

		/* Unref loaded pixmap and mask */
		gdk_pixmap_unref(pixmap);
		if(mask != NULL)
		    gdk_bitmap_unref(mask);
	    }
	}

	/* Update status owned icon */
	parent = cb->status_owned_icon_fixed;
	if(parent != NULL)
	{
	    GdkBitmap *mask;
	    GdkPixmap *pixmap;
	    guint8 **data = (guint8 **)((cb->own_buffer) ?
		icon_owned_16x16_xpm :
		icon_unowned_16x16_xpm
	    );

	    /* Load pixmap */
	    pixmap = gdk_pixmap_create_from_xpm_d(
		window, &mask,
		&style->bg[GTK_STATE_NORMAL],
		(gchar **)data
	    );
	    if(pixmap != NULL)
	    {
		gint width, height;

		gdk_window_get_size(pixmap, &width, &height);

		/* Update pixmap widget, create it as needed */
		w = cb->status_owned_icon_pm;
		if(w != NULL)
		{
		    gtk_pixmap_set(GTK_PIXMAP(w), pixmap, mask);
		}
		else
		{
		    cb->status_owned_icon_pm = w = gtk_pixmap_new(
			pixmap, mask
		    );
		    gtk_fixed_put(GTK_FIXED(parent), w, 0, 0);
		    gtk_widget_show(w);
		}

		/* Adjust size of fixed widget to fit pixmap */
		gtk_widget_set_usize(parent, width, height);

		gtk_widget_shape_combine_mask(parent, mask, 0, 0);

		/* Unref loaded pixmap and mask */
		gdk_pixmap_unref(pixmap);
		if(mask != NULL)
		    gdk_bitmap_unref(mask);
	    }
	}
}

/*
 *	Forceably deletes the DDE buffer on the clipboard browser and
 *	marks it as not owning the buffer.
 */
static void ClipboardBrowserDDEClear(clipboard_browser_struct *cb)
{
	if(cb == NULL)
	    return;

	g_free(cb->data);
	cb->data = NULL;
	cb->data_length = 0;

	cb->data_type = CLIPBOARD_DATA_TYPE_ASCII;

	cb->own_buffer = FALSE;
}

/*
 *	Records the widget by appending it to the given list.
 */
static void ClipboardBrowserRecordWidget(
	GtkWidget ***list, int *total, GtkWidget *w
)
{
	gint i;

	if((list == NULL) || (total == NULL))
	    return;

	i = MAX(*total, 0);
	(*total) = i + 1;
	(*list) = (GtkWidget **)g_realloc(
	    *list,
	    (*total) * sizeof(GtkWidget *)
	);
	if((*list) == NULL)
	{
	    (*total) = 0;
	    return;
	}

	(*list)[i] = w;
}

/*
 *	Clipboard clear buffer menu (not `selection' buffer) callback.
 */
static void ClipboardBrowserClearMCB(GtkWidget *widget, gpointer data)
{
	gboolean we_owned_buffer;
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if(cb == NULL)
	    return;

	we_owned_buffer = cb->own_buffer;

	/* This will disown the `selection' buffer if we own it and
	 * clear the local buffer but not put anything afterwards.
	 * Menus will also be updated
	 */
	ClipboardPutBinary(NULL, 0);

	ClipboardBrowserSetStatusMesg(
	    cb,
	    (we_owned_buffer) ? "Buffer cleared" : "Display cleared"
	);
}

/*
 *	Refetches any data from the `selection' buffer.
 */
static void ClipboardBrowserRefreshMCB(GtkWidget *widget, gpointer data)
{
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if(cb == NULL)
	    return;

	ClipboardBrowserSetStatusMesg(cb, "Requesting buffer...");
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();

	/* Get `selection' buffer, copying it to the clipboard
	 * structure and updating its menus and views
	 */
	ClipboardBrowserFetch(cb);

	ClipboardBrowserSetStatusMesg(cb, "Buffer refreshed");
}

/*
 *	Clipboard browser close callback.
 */
static gint ClipboardBrowserCloseCB( 
	GtkWidget *widget, GdkEvent *event,
	clipboard_browser_struct *cb 
)
{
	ClipboardBrowserUnmap();
	return(TRUE);
}

/*
 *	Clipboard browser close callback (fur menu item and widget
 *	callback inputs).
 */
static void ClipboardBrowserCloseMCB(
	GtkWidget *widget, gpointer data
)
{
	ClipboardBrowserCloseCB(
	    widget, NULL,
	    CLIPBOARD_BROWSER(data)
	);
}

/*
 *	Destroy callback.
 */
static void ClipboardBrowserDestroyCB(
	GtkObject *object, clipboard_browser_struct *cb
)
{
	return;
}

/*
 *	Clipboard browser view port event callback.
 */
static gint ClipboardBrowserViewEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gboolean status = FALSE;
	GtkAdjustment *adj = NULL;
	GdkCursor *cur = NULL;
	GtkWidget *w;
	gint etype;
	gint xs, ys;
	static gboolean button1, button2;
	static gint xs_last, ys_last;
	gint xsw_min, xsw_max, ysw_min, ysw_max;
	GdkModifierType mask;
	GdkEventConfigure *configure;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((widget == NULL) || (event == NULL) || (cb == NULL))
	    return(status);

/* Updates w to contain the viewport widget depending on widget
 * looked up in cb If matches fail then sets w = widget
 */
#define DO_GET_VIEWPORT_WIDGET	{		\
 if(widget == cb->view_text_event_box)		\
  w = cb->view_text_viewport;			\
 else if(widget == cb->view_binary_event_box)	\
  w = cb->view_binary_viewport;			\
 else						\
  w = widget;					\
}

/* Fetches and calculates bounds; xsw_min, xsw_max, ysw_min,
 * and ysw_max based on w being the scrolled window and uses
 * the adj variable
 */
#define DO_GET_SCROLL_WINDOW_BOUNDS	{	\
 if(GTK_IS_SCROLLED_WINDOW(w))			\
 {						\
  adj = gtk_scrolled_window_get_hadjustment(	\
   GTK_SCROLLED_WINDOW(w)			\
  );						\
  if(adj != NULL)				\
  {						\
   xsw_min = adj->value;			\
   xsw_max = xsw_min + w->allocation.width;	\
  }						\
  adj = gtk_scrolled_window_get_vadjustment(	\
   GTK_SCROLLED_WINDOW(w)			\
  );						\
  if(adj != NULL)				\
  {						\
   ysw_min = adj->value;			\
   ysw_max = ysw_min + w->allocation.height;	\
  }						\
/* Need to take into account scroll bars */	\
 }						\
 else						\
 {						\
  xsw_min = 0;					\
  ysw_min = 0;					\
  xsw_max = w->allocation.width;		\
  ysw_max = w->allocation.height;		\
 }						\
}


	etype = (gint)event->type;
	switch(etype)
	{
	  case GDK_EXPOSE:
	    break;

	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    break;

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    DO_GET_VIEWPORT_WIDGET
	    xs = button->x;
	    ys = button->y;
	    DO_GET_SCROLL_WINDOW_BOUNDS
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		button1 = TRUE;
		cur = cb->pan_cur;
		break;

	      case GDK_BUTTON2:
		button2 = TRUE;
		cur = cb->pan_cur;
		break;

	      case GDK_BUTTON3:
		w = cb->view_menu;
		if(w != NULL)
		    gtk_menu_popup(
			GTK_MENU(w),
			NULL, NULL, NULL, NULL,
			button->button, button->time
		    );
		status = TRUE;
		return(status);
		break;
	    }
	    if(!GTK_WIDGET_NO_WINDOW(widget) && (cur != NULL))
		gdk_window_set_cursor(widget->window, cur);
	    xs_last = xs;
	    ys_last = ys;
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    DO_GET_VIEWPORT_WIDGET
	    xs = button->x;
	    ys = button->y;
	    DO_GET_SCROLL_WINDOW_BOUNDS
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		button1 = FALSE;
		break;

	      case GDK_BUTTON2:
		button2 = FALSE;
		break;

	      default:
		break;
	    }
	    if(!GTK_WIDGET_NO_WINDOW(widget))
		gdk_window_set_cursor(widget->window, NULL);
	    xs_last = xs;
	    ys_last = ys;
	    status = TRUE;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    DO_GET_VIEWPORT_WIDGET
	    /* Fetch pointer positions */
	    if(motion->is_hint)
	    {
		gdk_window_get_pointer(
		    motion->window, &xs, &ys, &mask
		);
	    }
	    else
	    {
		xs = motion->x;
		ys = motion->y;
		mask = motion->state;
	    }
	    /* Scroll adjustments */
	    if(GTK_IS_SCROLLED_WINDOW(w) &&
	       (button1 || button2)
	    )
	    {
		gfloat	dxsw = xs_last - xs,
			dysw = ys_last - ys;

		/* Adjust scroll adjustments */
		adj = gtk_scrolled_window_get_hadjustment(
		    GTK_SCROLLED_WINDOW(w)
		);
		if(adj != NULL)
		{
		    gfloat v = adj->value + dxsw;
		    if(v > (adj->upper - adj->page_size))
			v = (adj->upper - adj->page_size);
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		adj = gtk_scrolled_window_get_vadjustment(
		    GTK_SCROLLED_WINDOW(w)
		);
		if(adj != NULL)
		{
		    gfloat v = adj->value + dysw;
		    if(v > (adj->upper - adj->page_size))
			v = (adj->upper - adj->page_size);
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}

		/* Since whole page moving, need to update current
		 * pointed positions relative to scroll
		 */
		xs += dxsw;
		ys += dysw;
	    }
	    xs_last = xs;
	    ys_last = ys;
	    status = TRUE;
	    break;
	}

	return(status);
#undef DO_GET_VIEWPORT_WIDGET
#undef DO_GET_SCROLL_WINDOW_BOUNDS
}

/*
 *	Notebook switch page callback.
 */
static void ClipboardBrowserSwitchPageCB(
	GtkNotebook *notebook, GtkNotebookPage *page, guint page_num,  
	gpointer data
)
{
	static gboolean reenterant = FALSE;
	GtkWidget *w;
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((notebook == NULL) || (cb == NULL))
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	/* Main notebook? */
	if(cb->notebook == GTK_WIDGET(notebook))
	{
	    w = cb->notebook;
/*	    gtk_notebook_set_page(notebook, page_num); */
	    cb->notebook_cur_page = page_num;
	    ClipboardBrowserUpdateMenus(cb);
	}

	reenterant = FALSE;
	return;
}

/*
 *	Clipboard browser view menu check item callback.
 */
static void ClipboardBrowserViewCheckMCB(GtkWidget *widget, gpointer data)
{
	static gboolean reenterant = FALSE;
	GtkWidget *w;
	GtkNotebook *notebook;
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((widget == NULL) || (cb == NULL))
	    return;

	w = cb->notebook;
	if(w != NULL)
	    notebook = GTK_NOTEBOOK(w);
	else
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

	/* Text? */
	if(cb->view_text_micheck == widget)
	{
	    gtk_notebook_set_page(notebook, 0);
	}
	/* Binary? */
	if(cb->view_binary_micheck == widget)
	{
	    gtk_notebook_set_page(notebook, 1);
	}

	/* Update menus */
	ClipboardBrowserUpdateMenus(cb);

	reenterant = FALSE;
	return;
}

/*
 *	Checks if we own the `selection' buffer, if not then
 *	it will check for changes.
 */
static gint ClipboardMonitorChangesTOCB(gpointer data)
{
	static GdkAtom targets_atom = GDK_NONE;
	guint8 *old_buf_ptr;
	GtkWidget *owner;
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if(cb == NULL)
	    return(FALSE);

/* Let's not do this, since it's all async and this can mess things up
 * with too many requests and if one request occures during another
 * request it can throw things off.
 */
return(TRUE);

	/* Set type (target) atom */
	if(targets_atom == GDK_NONE)
	    targets_atom = gdk_atom_intern("STRING", FALSE);

	if(!cb->initialized)
	    return(FALSE);

	owner = cb->owner_btn;
	if(owner == NULL) 
	    return(FALSE);
	if(GTK_WIDGET_NO_WINDOW(owner))
	    return(FALSE);  


	/* Skip if we own the selection buffer */
	if(cb->own_buffer)
	    return(TRUE);

	/* Is mapped? */
	if(!cb->map_state)
	    return(TRUE);

	/* Record old pointer */
	old_buf_ptr = cb->data;

	/* Request selection */
	gtk_selection_convert(
	    owner,
	    GDK_SELECTION_PRIMARY,
	    targets_atom,
	    GDK_CURRENT_TIME
	);

	/* Enter gui blocking loop, keep looping till we recieve
	 * a response for our requested `selection' buffer.
	 * This loop will be broken when
	 * ClipboardBufferBufferRecievedCB() is called.
	 */
	gtk_main();

	/* Any changes? */
	if(old_buf_ptr != cb->data)
	{
	    /* Update menus */
	    ClipboardBrowserUpdateMenus(cb);
	}

	return(TRUE);
}

/*
 *	A `selection' buffer we requested has arrived, fetch
 *	it and break out of the blocking loop that we entered
 *	when we first made the request and copy arrived buffer
 *	to clipboard browser's local data.
 */
static void ClipboardBufferBufferRecievedCB(
	GtkWidget *widget, GtkSelectionData *selection_data,
	gpointer null_data
)
{
	gboolean data_is_different = FALSE;
	clipboard_browser_struct *cb = &clipboard_browser;

	if((widget == NULL) || (selection_data == NULL))
	{
	    cb->waiting_response = FALSE;
	    return;
	}

	/* Compare if data is different */
	if((selection_data->length > 0) &&
	   (selection_data->length == cb->data_length) &&
	   (selection_data->data != NULL) &&
	   (selection_data->data != cb->data) &&
	   (cb->data != NULL)
	)
	{
	    /* Length is the same, check contents */
	    gint i, total;
	    guint8 *src_ptr, *tar_ptr;

	    for(i = 0, total = selection_data->length,
		src_ptr = cb->data,
		tar_ptr = (guint8 *)selection_data->data;
		i < total;
		i++, src_ptr++, tar_ptr++
	    )
	    {
		if((*src_ptr) != (*tar_ptr))
		{
		    data_is_different = TRUE;
		    break;
		}
	    }
	}
	else
	{
	    /* Lengths are different, definatly changed */
	    data_is_different = TRUE;
	}
	/* Was data different? */
	if(!data_is_different)
	{
	    cb->waiting_response = FALSE;
	    return;
	}


	/* Free current local buffer if any */
	g_free(cb->data);
	cb->data = NULL;
	cb->data_length = 0;

	/* Did we recieve any data? */
	if(selection_data->length <= 0)
	{
	    cb->waiting_response = FALSE;
	    return;
	}

	/* Handle by selection type */
	if(selection_data->type == GDK_SELECTION_TYPE_STRING)
	{
	    /* Set new data length (exclude null terminating byte)
	     * and allocate new local buffer data
	     */

	    cb->data_length = selection_data->length;
	    cb->data = (guchar *)g_malloc(
		(selection_data->length + 1) * sizeof(guchar)
	    );
	    if(cb->data == NULL)
	    {
		cb->data_length = 0;
		cb->waiting_response = FALSE;
		return;
	    }

	    memcpy(
		cb->data, selection_data->data,
		selection_data->length * sizeof(guchar)
	    );
	    cb->data[cb->data_length] = '\0';

	    ClipboardBrowserSetStatusMesg(cb, "Buffer received");
	}


	/* Data type (cb->data_type) will be updated outside of the
	 * blocking loop
	 */

	/* Break out of blocked gui loop */
	cb->waiting_response = FALSE;
}

/*
 *	`Selection' buffer clear callback, this function is called
 *	whenever another client has taken ownership of the primary
 *	`selection' buffer or our buffer has been disowned.
 *
 *	The clipboard browser is to mark itself as no longer owning
 *	the primary `selection' buffer and deallocate its local
 *	buffer since it won't be asked for anymore.
 */
static gint ClipboardClearCB(
	GtkWidget *widget, GdkEventSelection *event, gpointer data
)
{
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((widget == NULL) || (event == NULL) || (cb == NULL))
	    return(FALSE);

	/* Delete local buffers and mark as not owning buffer */
	ClipboardBrowserDDEClear(cb);

	/* Update menus */
	ClipboardBrowserUpdateMenus(cb);

	return(TRUE);
}

/*
 *      Not sure what this is for.
 */
static gint ClipboardNotifyCB( 
	GtkWidget *widget, GdkEventSelection *event, gpointer data
)
{
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((widget == NULL) || (event == NULL) || (cb == NULL))
	    return(FALSE);

#if 0
g_print("Window: 0x%.8x (0x%.8x) | Widgets 0x%.8x 0x%.8x\n",
 (guint32)event->window,
 (guint32)((cb->owner_btn == NULL) ? 0 : cb->owner_btn->window),
 (guint32)widget,
 (guint32)cb->owner_btn
);
g_print("Selection: %s\n", gdk_atom_name(event->selection));
g_print("Requestor: %i\n", event->requestor);

	/* Update menus */
	ClipboardBrowserUpdateMenus(cb);
#endif

	return(TRUE);
}

/*
 *	Handles the request when this or another application requests
 *	our clipboard `selection' buffer.
 */
static void ClipboardBufferRequestCB(
	GtkWidget *widget, GtkSelectionData *selection_data,
	guint info, guint time_stamp,
	gpointer data
)
{
	clipboard_browser_struct *cb = CLIPBOARD_BROWSER(data);
	if((widget == NULL) || (selection_data == NULL) || (cb == NULL))
	    return;

	/* Got a request for our data even though we don't own
	 * the primary `selection' buffer?
	 */
	if(!cb->own_buffer)
	{
	    g_printerr(
"ClipboardBufferRequestCB(): Warning:\
 Got request for buffer contents while not owning primary buffer.\n"
	    );
	}

	ClipboardBrowserSetStatusMesg(cb, "Processing buffer request...");
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();

	/* When we return a single string, it should not be null
	 * terminated
	 *
	 * That will be done for us
	 */
	gtk_selection_data_set(
	    selection_data, GDK_SELECTION_TYPE_STRING,
	    8,		/* 8 bits per character */
	    cb->data, cb->data_length
	);

	ClipboardBrowserSetStatusMesg(cb, "Buffer sent");
}


/*
 *	Fetches the primary `selection' buffer (if any) and returns
 *	a dynamically allocated coppied data which can be NULL.
 */
guint8 *ClipboardFetchBinary(gint *length)
{
	guint8 *buf = NULL;
	gint buf_len = 0;
	GtkWidget *owner;
	static GdkAtom targets_atom = GDK_NONE;
	clipboard_browser_struct *cb = &clipboard_browser;


	/* Set type (target) atom */
	if(targets_atom == GDK_NONE)
	    targets_atom = gdk_atom_intern("STRING", FALSE);

	/* Reset returned buffer length */
	if(length != NULL)
	    *length = buf_len;

	owner = cb->owner_btn;
	if(owner == NULL)
	    return(buf);
	if(GTK_WIDGET_NO_WINDOW(owner))
	    return(buf);

	/* Do we own the `selection' buffer? */
	if(!cb->own_buffer)
	{
	    /* No we do not, request `selection' buffer */
	    ClipboardBrowserSetBusy(cb);

	    /* Request selection */
	    cb->waiting_response = TRUE;
	    gtk_selection_convert(
		owner,
		GDK_SELECTION_PRIMARY,
		targets_atom,
		GDK_CURRENT_TIME
	    );

	    /* Enter gui blocking loop, keep looping till we recieve
	     * a response for our requested `selection' buffer
	     *
	     * This loop will be broken when
	     * ClipboardBufferBufferRecievedCB() is called
	     */
	    while(cb->waiting_response)
		gtk_main_iteration();
	}

	/* Check if we got anything */
	if((cb->data != NULL) && (cb->data_length > 0) && (buf == NULL))
	{
	    /* Allocate and set up return data */
	    buf = (guint8 *)g_malloc(cb->data_length * sizeof(guint8));
	    if(buf != NULL)
	    {
		buf_len = cb->data_length;
		memcpy(buf, cb->data, cb->data_length * sizeof(guint8));

		/* Update returned buffer length */
		if(length != NULL)
		    *length = buf_len;
	    }
	}

	/* Update type */
	cb->data_type = CLIPBOARD_DATA_TYPE_BINARY;

	/* Update menus */
	ClipboardBrowserUpdateMenus(cb);

	ClipboardBrowserSetReady(cb);

	return(buf);
}

/*
 *	Same as ClipboardFetchBinary() except it only updates the data
 *	on the clipboard browser structure and does not allocate and
 *	copy that for return.
 */
static void ClipboardBrowserFetch(clipboard_browser_struct *cb)
{
	GtkWidget *owner;
	static GdkAtom targets_atom = GDK_NONE;


	/* Set type (target) atom */
	if(targets_atom == GDK_NONE)
	    targets_atom = gdk_atom_intern("STRING", FALSE);

	if(cb == NULL)
	    return;

	owner = cb->owner_btn;
	if(owner == NULL)
	    return;
	if(GTK_WIDGET_NO_WINDOW(owner))
	    return;

	/* Do we own the `selection' buffer? */
	if(!cb->own_buffer)
	{
	    /* No we do not, request `selection' buffer */
	    ClipboardBrowserSetBusy(cb);

	    /* Request selection */
	    cb->waiting_response = TRUE;
	    gtk_selection_convert(
		owner,
		GDK_SELECTION_PRIMARY,     
		targets_atom,
		GDK_CURRENT_TIME
	    );

	    /* Enter gui blocking loop, keep looping till we recieve
	     * a response for our requested `selection' buffer.
	     * This loop will be broken when
	     * ClipboardBufferBufferRecievedCB() is called.
	     */
	    while(cb->waiting_response)
		gtk_main_iteration();
	}

	/* Update type */   
	cb->data_type = CLIPBOARD_DATA_TYPE_BINARY;

	/* Update menus */
	ClipboardBrowserUpdateMenus(cb);

	ClipboardBrowserSetReady(cb);
}

/*
 *	Coppies the specified binary data (if any) to the clipboard.
 *	If data is NULL or length is <= 0 then the current (last) buffer
 *	(if any) owned by this application will be disowned and cleared.
 *
 *	Returns the actual number of bytes put, can return
 *	-1 on error.
 */
gint ClipboardPutBinary(const guint8 *data, gint length)
{
	GtkWidget *owner;
	clipboard_browser_struct *cb = &clipboard_browser;

	owner = cb->owner_btn;
	if(owner == NULL)
	    return(-1);
	if(GTK_WIDGET_NO_WINDOW(owner))
	    return(-1);

	/* Do we own the primary `selection' buffer? If so then
	 * disown it first
	 */
	if(cb->own_buffer)
	{
	    /* Double check if we really own the prumary
	     * `selection' buffer
	     */
	    if(gdk_selection_owner_get(GDK_SELECTION_PRIMARY) ==
		owner->window
	    )
	    {
		/* Disown it */
		gtk_selection_owner_set(
		    NULL,
		    GDK_SELECTION_PRIMARY,
		    GDK_CURRENT_TIME
		);

		/* Wait untill disowned, we're waiting for
		 * ClipboardClearCB() to be called when a buffer we own
		 * has been disowned
		 */
		while(cb->own_buffer)
		    gtk_main_iteration();
	    }
	    else
	    {
		cb->own_buffer = FALSE;
	    }
	}


	/* Forceably deallocate current buffer on clipboard browser,
	 * incase disowning it above did not call the clear
	 * `selection' buffer callback
	 */
	ClipboardBrowserDDEClear(cb);


	/* Do not go any further if we were given NULL or no length */
	if((data == NULL) || (length <= 0))
	{
	    ClipboardBrowserUpdateMenus(cb);
	    return(0);
	}

	/* Chown ownership of primary `selection' buffer */
	cb->own_buffer = gtk_selection_owner_set(
	    owner,
	    GDK_SELECTION_PRIMARY,
	    GDK_CURRENT_TIME
	);
	/* If claiming the selection failed, we return error */
	if(!cb->own_buffer)
	    return(-1);


	/* Warn if previous buffer was not disowned and deleted */
	if(cb->data != NULL)
	    g_printerr(
"ClipboardPutBinary(): Warning: Previous buffer not disowned.\n"
	    );

	/* Create new buffer and copy new data to it */
	cb->data = (guchar *)g_malloc(length * sizeof(guchar));
	if(cb->data != NULL)
	{
	    cb->data_type = CLIPBOARD_DATA_TYPE_BINARY;
	    memcpy(cb->data, data, length * sizeof(guchar));
	    cb->data_length = length;
	}
	else
	{
	    cb->data_type = CLIPBOARD_DATA_TYPE_BINARY;
	    cb->data_length = 0;
	}

	ClipboardBrowserUpdateMenus(cb);

	return(cb->data_length);
}


/*
 *	Loads cursors used by the clipboard browser.
 */
static void ClipboardBrowserLoadCursors(clipboard_browser_struct *cb)
{
	cb->busy_cur = gdk_cursor_new(GDK_WATCH);
	cb->pan_cur = gdk_cursor_new(GDK_FLEUR);
}

/*
 *	Build menu bar for clipboard browser.
 */
static void ClipboardBrowserCreateMenuBar(
	clipboard_browser_struct *cb, GtkWidget *parent
)
{
        guint8 **icon;
        const gchar *label;
	guint accel_key, accel_mods;
	GtkAccelGroup *accelgrp = cb->accelgrp;
	GtkWidget *menu_bar, *menu, *w;
	gpointer client_data = cb;
	void (*func_cb)(GtkWidget *w, gpointer);


	/* Create menu bar */
	cb->menu_bar = menu_bar = GUIMenuBarCreate(NULL);
	gtk_container_add(GTK_CONTAINER(parent), menu_bar);
	gtk_widget_show(menu_bar);

#define DO_ADD_MENU_ITEM_LABEL	{			\
 w = GUIMenuItemCreate(					\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,		\
  icon, label, accel_key, accel_mods, NULL,		\
  client_data, func_cb					\
 );							\
}
#define DO_ADD_MENU_ITEM_CHECK	{			\
 w = GUIMenuItemCreate(					\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,		\
  icon, label, accel_key, accel_mods, NULL,		\
  client_data, func_cb					\
 );							\
}

#define DO_ADD_MENU_SEP		{			\
 w = GUIMenuItemCreate(					\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,		\
  NULL, NULL, 0, 0, NULL,				\
  NULL, NULL						\
 );							\
}

	/* Create file menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Claro"
#elif defined(PROG_LANGUAGE_FRENCH)
"Clair"
#elif defined(PROG_LANGUAGE_GERMAN)
"Klar"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Chiaro"
#elif defined(PROG_LANGUAGE_DUTCH)
"Helder"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Claro"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Klar"
#else
"Clear"
#endif
	    ;
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ClipboardBrowserClearMCB;
	    DO_ADD_MENU_ITEM_LABEL
	    cb->clear_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_close_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Cierre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fin"
#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 = ClipboardBrowserCloseMCB;
	    DO_ADD_MENU_ITEM_LABEL
	}
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
#if defined(PROG_LANGUAGE_SPANISH)
"Archivo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fichier"
#elif defined(PROG_LANGUAGE_GERMAN)
"Akte"
#elif defined(PROG_LANGUAGE_ITALIAN)
"File"
#elif defined(PROG_LANGUAGE_DUTCH)
"Dossier"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquivo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Arkiv"
#else
"File"
#endif
	    , GUI_MENU_BAR_ALIGN_LEFT
	);

	/* Create view menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = NULL;
	    label = "as Text";
	    accel_key = 0; 
	    accel_mods = 0;
	    func_cb = ClipboardBrowserViewCheckMCB;
	    DO_ADD_MENU_ITEM_CHECK
	    cb->view_text_micheck = w;

	    icon = NULL;
	    label = "as Binary";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ClipboardBrowserViewCheckMCB;
	    DO_ADD_MENU_ITEM_CHECK
	    cb->view_binary_micheck = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_reload_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Refresque"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rafraîchir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erfrischen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinfrescare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verfris"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refresque-se"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forfrisk"
#else
"Refresh"
#endif
	    ;
	    accel_key = GDK_F5;
	    accel_mods = 0;
	    func_cb = ClipboardBrowserRefreshMCB;
	    DO_ADD_MENU_ITEM_LABEL
	}
	GUIMenuAddToMenuBar(
	    menu_bar, menu,
#if defined(PROG_LANGUAGE_SPANISH)
"Vista"
#elif defined(PROG_LANGUAGE_FRENCH)
"Vue"
#elif defined(PROG_LANGUAGE_GERMAN)
"Blick"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Veduta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Overzicht"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Vista"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Sikt"
#else
"View"
#endif
	    , GUI_MENU_BAR_ALIGN_LEFT
	);

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP

}


/*
 *	Creates tool ribbon.
 */
static void ClipboardBrowserCreateToolRibbon(
	clipboard_browser_struct *cb, GtkWidget *parent
)
{
	GtkWidget *w, *parent2, *parent3;
	gint border_major = 5;
	gint bw = 60, bh = 50;
	gint sw = 5, sh = -1;


	/* Hbox tool ribbon to hold buttons */
	cb->tool_ribbon = w = gtk_hbox_new(FALSE, 0);
	gtk_container_border_width(GTK_CONTAINER(w), 2);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent2 = w;

#define DO_ADD_SEPARATOR	{			\
 w = gtk_vbox_new(TRUE, 0);				\
 gtk_widget_set_usize(w, sw, sh);			\
 gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0); \
 gtk_widget_show(w);					\
 parent3 = w;						\
 w = gtk_vseparator_new();				\
 gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, border_major); \
 gtk_widget_show(w);					\
}

	/* Refresh */
	w = GUIButtonPixmapLabelV(
	    (guint8 **)icon_reload_20x20_xpm, "Refresh", NULL
	);
	gtk_widget_set_usize(w, bw, bh);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ClipboardBrowserRefreshMCB),
	    cb
	);
	gtk_widget_show(w);

	DO_ADD_SEPARATOR

	/* Clear */
	cb->clear_btn = w = GUIButtonPixmapLabelV(
	    (guint8 **)icon_clear_20x20_xpm, "Clear", NULL
	);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_widget_set_usize(w, bw, bh);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ClipboardBrowserClearMCB),
	    cb
	);
	gtk_widget_show(w);

#undef DO_ADD_SEPARATOR
}

/*
 *	Initializes the clipboard browser.
 */
gint ClipboardBrowserInit(void)
{
	const gint border_minor = 2;
	GtkAccelGroup *accelgrp;
	GtkWidget *w, *parent, *parent2, *parent3, *parent4;
	GtkWidget *handle_box, *menu;
	guint accel_key, accel_mods;
	gpointer client_data;
	guint8 **icon;
	const gchar *label = NULL;
	void (*func_cb)(GtkWidget *w, gpointer);
	clipboard_browser_struct *cb = &clipboard_browser;


	/* Set callback client data */
	client_data = cb;


	/* Reset values */
	cb->initialized = TRUE; 
	cb->map_state = FALSE;
	cb->busy_count = 0;
	cb->accelgrp = accelgrp = gtk_accel_group_new();
	cb->notebook_cur_page = 0;
	cb->waiting_response = FALSE;
	cb->monitor_changes_toid = (guint)(-1);


	/* Load cursors */
	ClipboardBrowserLoadCursors(cb);


	/* Toplevel */
	cb->toplevel = w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_widget_set_usize(w, CLIPBOARD_DEF_WIDTH, CLIPBOARD_DEF_HEIGHT);
	gtk_widget_realize(w);
	gtk_window_set_title(GTK_WINDOW(w), "ClipBoard Browser");
	GUISetWMIcon(w->window, (guint8 **)icon_clipboard_48x48_xpm);
	gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, FALSE);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserCloseCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "destroy",
	    GTK_SIGNAL_FUNC(ClipboardBrowserDestroyCB),
	    cb
	);
	gtk_accel_group_attach(accelgrp, GTK_OBJECT(w));
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	parent = w;


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


	/* Handle and menu bar */
	handle_box = gtk_handle_box_new();
	gtk_box_pack_start(GTK_BOX(parent), handle_box, FALSE, FALSE, 0);
	gtk_widget_show(handle_box);
	ClipboardBrowserCreateMenuBar(cb, handle_box);

	/* Handle and tool ribbon */
	handle_box = gtk_handle_box_new();
	gtk_box_pack_start(GTK_BOX(parent), handle_box, FALSE, FALSE, 0);
	gtk_widget_show(handle_box);
	ClipboardBrowserCreateToolRibbon(cb, handle_box);


	/* Push button for owning clipboard `selections', this widget
	 * is never shown to the user.
	 */
	w = gtk_button_new();
	/* Fetch: requested buffer has arrived for fetching signal */
	gtk_signal_connect(
	    GTK_OBJECT(w), "selection_received",
	    GTK_SIGNAL_FUNC(ClipboardBufferBufferRecievedCB), cb
	);
	/* Put: `selection' buffer cleared or chowned to other signal */ 
	gtk_signal_connect(
	    GTK_OBJECT(w), "selection_clear_event",
	    GTK_SIGNAL_FUNC(ClipboardClearCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "selection_notify_event",
	    GTK_SIGNAL_FUNC(ClipboardNotifyCB), cb
	);
	/* Put: request for our buffer came in signal */
	gtk_selection_add_target(
	    w,
	    GDK_SELECTION_PRIMARY,
	    GDK_SELECTION_TYPE_STRING,
	    1
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "selection_get",
	    GTK_SIGNAL_FUNC(ClipboardBufferRequestCB), cb
	);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	cb->owner_btn = w;


	/* Note book */
	cb->notebook = w = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(w), GTK_POS_TOP);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_notebook_set_scrollable(GTK_NOTEBOOK(w), TRUE);
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(w), TRUE);
	gtk_notebook_set_show_border(GTK_NOTEBOOK(w), TRUE);
/*	gtk_notebook_set_page(GTK_NOTEBOOK(w), 0); */
	gtk_signal_connect(
	    GTK_OBJECT(w), "switch_page",
	    GTK_SIGNAL_FUNC(ClipboardBrowserSwitchPageCB), cb
	);
	gtk_widget_show(w);
	parent2 = w;


	/* View text viewport */
	w = gtk_scrolled_window_new(NULL, NULL);
	cb->view_text_viewport = w;
	gtk_notebook_append_page(
	    GTK_NOTEBOOK(parent2),
	    w,
	    gtk_label_new("Text")
	);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC 
	);
	gtk_widget_show(w);
	parent3 = w;

	/* Create event box as first child in view port */
	w = gtk_event_box_new();
	cb->view_text_event_box = w;
	gtk_widget_add_events(
	    w,
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
	    GDK_LEAVE_NOTIFY_MASK
	);   
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_scrolled_window_add_with_viewport(
	    GTK_SCROLLED_WINDOW(parent3),   
	    w
	);
	gtk_widget_show(w);
	
	/* Leave hbox to hold childs NULL */
	cb->view_text_vbox = NULL;


	/* View binary viewport */
	w = gtk_scrolled_window_new(NULL, NULL);
	cb->view_binary_viewport = w;
	gtk_notebook_append_page(
	    GTK_NOTEBOOK(parent2),
	    w,
	    gtk_label_new("Binary")
	);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC
	);
	gtk_widget_show(w);
	parent3 = w;

	/* Create event box as first child in view port */
	cb->view_binary_event_box = w = gtk_event_box_new();
	gtk_widget_add_events(w,
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
	    GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(ClipboardBrowserViewEventCB), cb
	);
	gtk_scrolled_window_add_with_viewport(
	    GTK_SCROLLED_WINDOW(parent3),
	    w
	);
	gtk_widget_show(w);

	/* Leave hbox to hold childs NULL */
	cb->view_binary_vbox = NULL;



	/* Main status bar dock */
	w = gtk_handle_box_new();
	cb->status_bar_dock = w;
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Main status bar frame */
	w = gtk_frame_new(NULL);
	cb->status_bar_frame = w;
	gtk_widget_set_usize(w, -1, CLIPBOARD_STATUSBAR_HEIGHT);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_widget_show(w);
	parent2 = w;

	/* Hbox inside main status bar frame */
	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;


	/* Frame to hold contents icon */
	w = gtk_frame_new(NULL);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
/*	gtk_container_border_width(GTK_CONTAINER(w), 1); */
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_widget_show(w);
	parent3 = w;

	/* Contents icon fixed */
	w = gtk_fixed_new();
	cb->status_content_icon_fixed = w;
	gtk_container_add(GTK_CONTAINER(parent3), w);
/*	gtk_widget_set_usize(w, 20, 20); */
	gtk_widget_show(w);
	parent4 = w;

	/* Set contents icon pixmap to NULL */
	cb->status_content_icon_pm = NULL;


	/* Frame to hold owned icon */
	w = gtk_frame_new(NULL);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_container_border_width(GTK_CONTAINER(w), 1);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_widget_show(w);
	parent3 = w;

	/* Owned icon fixed */
	w = gtk_fixed_new();
	cb->status_owned_icon_fixed = w;
	gtk_container_add(GTK_CONTAINER(parent3), w);
/*	gtk_widget_set_usize(w, 20, 20); */
	gtk_widget_show(w);
	parent4 = w;

	/* Set contents icon pixmap to NULL */
	cb->status_owned_icon_pm = NULL;


	/* Frame to hold status size label */
	w = gtk_frame_new(NULL);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_container_border_width(GTK_CONTAINER(w), 1);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_widget_show(w);
	parent3 = w;

	/* Status size hbox, and label */
	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent4 = w;

	w = gtk_label_new("");
	cb->status_size_label = w;
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);   
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_set_usize(w, 100, -1);
	gtk_widget_show(w);


	/* Frame to hold status message label */
	w = gtk_frame_new(NULL);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_container_border_width(GTK_CONTAINER(w), 1);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_widget_show(w);
	parent3 = w;

	/* Status message hboxes (two of them), and label */
	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent4 = w;

	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, border_minor);
	gtk_widget_show(w);   
	parent4 = w;

	w = gtk_label_new("");
	cb->status_mesg_label = w;
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);



	/* Right click menu */

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

	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = NULL;
	    label = "Clear";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ClipboardBrowserClearMCB;
	    DO_ADD_MENU_ITEM_LABEL
	    cb->view_clear_mi = w;

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_reload_20x20_xpm;
	    label = "Refresh";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ClipboardBrowserRefreshMCB;
	    DO_ADD_MENU_ITEM_LABEL
	    cb->view_refresh_mi = w;
	}   
	cb->view_menu = menu;

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_SEP

	/* Set monitor changes timeout callback */
	cb->monitor_changes_toid = gtk_timeout_add(
	    1000,
	    (GtkFunction)ClipboardMonitorChangesTOCB,
	    cb
	);



	/* Reset values */
	ClipboardBrowserReset(cb, FALSE);


	return(0);
}

/*
 *	Resets values on clipboard browser to defaults.
 */
void ClipboardBrowserReset(
	clipboard_browser_struct *cb, gboolean need_unmap
)
{
	GtkWidget *w;

	if(cb == NULL)
	    return;

	cb->waiting_response = FALSE;

	ClipboardBrowserDDEClear(cb);

	ClipboardBrowserSetStatusMesg(cb, "");

	cb->monitor_changes = TRUE;

	/* Force unmap as needed */
	if(need_unmap)
	{
	    w = cb->toplevel;
	    if(w != NULL)
		gtk_widget_hide(w);

	    cb->map_state = FALSE;
	}
}

/*
 *      Update menu values on the clipboard browser, also updates 
 *      (recreates) the view widgets' childs.
 */
void ClipboardBrowserUpdateMenus(clipboard_browser_struct *cb)
{
	static gboolean reenterant = FALSE;
	gint page_num;
	gboolean sensitivity, state;
	GtkWidget *w, *parent;

	if(cb == NULL)
	    return;

	if(reenterant)
	    return;
	else
	    reenterant = TRUE;

#define SET_WIDGET_SENSITIVITY	\
{ if(w != NULL) { gtk_widget_set_sensitive(w, sensitivity); } }

#define UPDATE_CHECK_MENU_ITEM_STATE	\
{ GUIMenuItemSetCheck(w, state, TRUE); }


	/* Menu items */
	w = cb->clear_mi;
	sensitivity = (cb->data != NULL) ? TRUE : FALSE;
	SET_WIDGET_SENSITIVITY

	/* View page checks */
	page_num = cb->notebook_cur_page;
	w = cb->view_text_micheck;
	state = ((page_num == 0) ? TRUE : FALSE);
	UPDATE_CHECK_MENU_ITEM_STATE

	w = cb->view_binary_micheck;
	state = ((page_num == 1) ? TRUE : FALSE);
	UPDATE_CHECK_MENU_ITEM_STATE

	/* Right click menu */
	w = cb->view_clear_mi;
	sensitivity = (cb->data != NULL) ? TRUE : FALSE;
	SET_WIDGET_SENSITIVITY



	/* Buttons on tool ribbon */
	w = cb->clear_btn;  
	sensitivity = (cb->data != NULL) ? TRUE : FALSE;
	SET_WIDGET_SENSITIVITY


#undef UPDATE_CHECK_MENU_ITEM_STATE
#undef SET_WIDGET_SENSITIVITY 


	/* Update status bar icons */
	ClipboardBrowserSetStatusIcon(cb);

	/* Update status size label */
	w = cb->status_size_label;
	if(w != NULL)
	{
	    gchar text[80];

	    if(cb->data_length <= 0)
		strcpy(text, "");
	    else
		g_snprintf(
		    text, sizeof(text),
		    "%i bytes", cb->data_length
		);
	    gtk_label_set_text(GTK_LABEL(w), text);
	}


	/* Recreate view child widgets and update to contents of
	 * local buffer
	 */
	if(TRUE)
	{
	    GtkWidget **w;

#define DO_DESTROY_WIDGET	\
{ if(*w != NULL) { gtk_widget_destroy(*w); *w = NULL; } }

	    /* First destroy view childs */
	    w = &cb->view_text_vbox; 
	    DO_DESTROY_WIDGET

	    g_free(cb->view_text_widget);
	    cb->view_text_widget = NULL;
	    cb->total_view_text_widgets = 0;

	    w = &cb->view_binary_vbox;
	    DO_DESTROY_WIDGET

	    g_free(cb->view_binary_widget);
	    cb->view_binary_widget = NULL;
	    cb->total_view_binary_widgets = 0;

#undef DO_DESTROY_WIDGET
	}

	/* Create view text widgets */
	parent = cb->view_text_event_box;
	if(parent != NULL)
	{
	    w = gtk_vbox_new(FALSE, 0);
	    cb->view_text_vbox = w;
	    gtk_container_add(GTK_CONTAINER(parent), w);
	    gtk_widget_show(w);
	    parent = w;

	    w = gtk_hbox_new(FALSE, 0);
	    ClipboardBrowserRecordWidget(
		&cb->view_text_widget, &cb->total_view_text_widgets, w
	    );
	    gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 2);
	    gtk_widget_show(w);
	    parent = w;

	    if((cb->data != NULL) && (cb->data_length > 0))
	    {
		gchar *tmp_text = (gchar *)g_malloc(
		    (cb->data_length + 1) * sizeof(gchar)
		);
		if(tmp_text != NULL)
		{
		    strncpy(tmp_text, cb->data, cb->data_length);
		    tmp_text[cb->data_length] = '\0';

		    w = gtk_label_new(tmp_text);
		    ClipboardBrowserRecordWidget(
			&cb->view_text_widget, &cb->total_view_text_widgets,
			w
		    );
		    gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 2);
		    gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
		    gtk_widget_show(w);
		}
		g_free(tmp_text);
	    }
	}

	/* Create view binary widgets */
	parent = cb->view_binary_event_box;
	if(parent != NULL)
	{
	    gint inc;
	    gint src_i, src_len = cb->data_length;
	    gint tar_i, tar_column, tar_hex_columns = 16;
	    gchar *tar_ptr, *tar_text;
	    gint tar_text_len;
	    gchar *text_address, *text_hex, *text_ascii;


	    /* Allocate address column */
	    tar_text = NULL;
	    tar_text_len = 0;
	    for(src_i = 0, tar_i = 0, inc = 11;
		src_i < src_len;
		src_i += tar_hex_columns
	    )
	    {
		if((tar_i + inc) >= tar_text_len)
		{
		    tar_text_len = tar_i + inc + 1;
		    tar_text = (gchar *)g_realloc(
			tar_text, tar_text_len * sizeof(gchar)
		    );
		    if(tar_text == NULL)
		    {
			tar_text_len = 0;
			break;
		    }
		}
		tar_ptr = &(tar_text[tar_i]);
		if((src_i + tar_hex_columns) < src_len)
		    sprintf(tar_ptr, "0x%.8X\n", (guint)src_i);
		else
		    sprintf(tar_ptr, "0x%.8X", (guint)src_i);
		tar_i += inc;
	    }
	    text_address = tar_text;	/* Transfer tar_text to text_address */

	    /* Allocate target text for hex column */
	    tar_text = NULL;
	    tar_text_len = 0;
	    for(src_i = 0, tar_i = 0, tar_column = 0;
		src_i < src_len;
	        src_i++
	    )
	    {
		/* Hex value */
		inc = 4;
		if((tar_i + inc) >= tar_text_len)
		{
		    tar_text_len = tar_i + inc + 1;
		    tar_text = (gchar *)g_realloc(
			tar_text, tar_text_len * sizeof(gchar)
		    );
		    if(tar_text == NULL)
		    {
			tar_text_len = 0;
			break;
		    }
		}
		tar_ptr = &(tar_text[tar_i]);
		sprintf(tar_ptr, "0x%.2X", (guint8)cb->data[src_i]);
		tar_i += inc;

		/* Column increment and deliminator */
		tar_column++;
		if(tar_column == (tar_hex_columns / 2))
		{
		    inc = 2;
		    if((tar_i + inc) >= tar_text_len)
		    {
			tar_text_len = tar_i + inc + 1;
			tar_text = (gchar *)g_realloc(
			    tar_text, tar_text_len * sizeof(gchar)
			);
			if(tar_text == NULL)
			{
			    tar_text_len = 0;
			    break;
			}
		    }
		    tar_ptr = &(tar_text[tar_i]);
		    strcpy(tar_ptr, "  ");
		    tar_i += inc;
		}
		else if(tar_column >= tar_hex_columns)
		{
		    inc = 1;
		    if((tar_i + inc) >= tar_text_len)
		    {
			tar_text_len = tar_i + inc + 1; 
			tar_text = (gchar *)g_realloc(
			    tar_text, tar_text_len * sizeof(gchar)
			);
			if(tar_text == NULL)
			{
			    tar_text_len = 0;
			    break;
			}
		    }
		    tar_ptr = &(tar_text[tar_i]);
		    strcpy(tar_ptr, "\n");
		    tar_i += inc;

		    tar_column = 0;
		}
		else
		{
		    inc = 1;
		    if((tar_i + inc) >= tar_text_len)
		    {
			tar_text_len = tar_i + inc + 1;
			tar_text = (gchar *)g_realloc(
			    tar_text, tar_text_len * sizeof(gchar)
			);
			if(tar_text == NULL)
			{
			    tar_text_len = 0;
			    break;
			}
		    }
		    tar_ptr = &(tar_text[tar_i]);
		    strcpy(tar_ptr, " ");
		    tar_i += inc;
		}
	    }
	    text_hex = tar_text;	/* Transfer tar_text to text_hex */

	    /* Allocate ascii representation column */
	    tar_text = NULL;
	    tar_text_len = 0;
	    for(src_i = 0, tar_i = 0, tar_column = 0;
		src_i < src_len;
		src_i++
	    )
	    {
		/* Hex value */
		inc = 1;
		if((tar_i + inc) >= tar_text_len)
		{
		    tar_text_len = tar_i + inc + 1;
		    tar_text = (gchar *)g_realloc(
			tar_text, tar_text_len * sizeof(gchar)
		    );
		    if(tar_text == NULL)
		    {
			tar_text_len = 0;
			break;
		    }
		}
		tar_ptr = &(tar_text[tar_i]);
		if(isprint((gint)cb->data[src_i]))
		    sprintf(tar_ptr, "%c", (guint8)cb->data[src_i]);
		else
		    strcpy(tar_ptr, " ");
		tar_i += inc;

		/* Column increment and deliminator */
		tar_column++;
		if(tar_column == (tar_hex_columns / 2))
		{
		    inc = 1;
		    if((tar_i + inc) >= tar_text_len)
		    {
			tar_text_len = tar_i + inc + 1;
			tar_text = (gchar *)g_realloc(
			    tar_text, tar_text_len * sizeof(gchar)
			);
			if(tar_text == NULL)
			{
			    tar_text_len = 0;
			    break;
			}
		    }
		    tar_ptr = &(tar_text[tar_i]);
		    strcpy(tar_ptr, " ");
		    tar_i += inc;
		}
		else if(tar_column >= tar_hex_columns)
		{
		    inc = 1;
		    if((tar_i + inc) >= tar_text_len)
		    {
			tar_text_len = tar_i + inc + 1;
			tar_text = (gchar *)g_realloc(
			    tar_text, tar_text_len * sizeof(gchar)
			);
			if(tar_text == NULL)
			{
			    tar_text_len = 0;
			    break;
			}
		    }
		    tar_ptr = &(tar_text[tar_i]);
		    strcpy(tar_ptr, "\n");
		    tar_i += inc;

		    tar_column = 0;
		}
	    }
	    text_ascii = tar_text;	/* Transfer tar_text to text_ascii */


	    /* Begin creating widgets for the binary view */
	    w = gtk_vbox_new(FALSE, 0);
	    cb->view_binary_vbox = w;
	    gtk_container_add(GTK_CONTAINER(parent), w);
	    gtk_widget_show(w);
	    parent = w;

	    w = gtk_hbox_new(FALSE, 0);
	    gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 2);
	    gtk_widget_show(w);
	    parent = w;


	    /* Address column label */
	    if(text_address != NULL)
	    {
		GtkRcStyle *rcstyle = gtk_rc_style_new();
		g_free(rcstyle->font_name);
		rcstyle->font_name = g_strdup(
		    "-misc-fixed-medium-*-*-*-13-*-*-*-*-*-*-*"
		);

		w = gtk_label_new(text_address);
		gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 5);
		gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
		gtk_widget_modify_style(w, rcstyle);
		gtk_widget_show(w);

		gtk_rc_style_unref(rcstyle);
	    }

	    /* Hex column label */
	    if(text_hex != NULL)
	    {
		GtkRcStyle *rcstyle = gtk_rc_style_new();
		g_free(rcstyle->font_name);
		rcstyle->font_name = g_strdup(
		    "-misc-fixed-medium-*-*-*-13-*-*-*-*-*-*-*"
		);

		w = gtk_label_new(text_hex);
		gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 5);
		gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
		gtk_widget_modify_style(w, rcstyle);
		gtk_widget_show(w);

		gtk_rc_style_unref(rcstyle);
	    }

	    /* Ascii representation column label */
	    if(text_ascii != NULL)
	    {
		GtkRcStyle *rcstyle = gtk_rc_style_new();
		g_free(rcstyle->font_name);
		rcstyle->font_name = g_strdup(
		    "-misc-fixed-medium-*-*-*-13-*-*-*-*-*-*-*"
		);

		w = gtk_label_new(text_ascii);
		gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 5);
		gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
		gtk_widget_modify_style(w, rcstyle);
		gtk_widget_show(w);

		gtk_rc_style_unref(rcstyle);
	    }


	    /* Delete texts */
	    g_free(text_hex);
	    g_free(text_address);
	    g_free(text_ascii);
	}

	reenterant = FALSE;
}

/*
 *	Maps the clipboard browser and updates its menus.
 */
void ClipboardBrowserMap(void)
{
	GtkWidget *w;
	clipboard_browser_struct *cb = &clipboard_browser;

	if(cb == NULL)
	    return;

	w = cb->toplevel;
	gtk_widget_show_raise(w);
	cb->map_state = TRUE;

	ClipboardBrowserUpdateMenus(cb);
}

/*
 *	Unmaps the clipboard browser.
 */
void ClipboardBrowserUnmap(void)
{
	GtkWidget *w;
	clipboard_browser_struct *cb = &clipboard_browser;

	if(cb == NULL)
	    return;

	if(cb->map_state)
	{   
	    w = cb->toplevel;
	    if(w != NULL)
		gtk_widget_hide(w);

	    cb->map_state = FALSE;
	}
}

/*
 *	Deallocates and destroys the clipboard browser and all
 *	of its allocated resources.
 */
void ClipboardBrowserShutdown(void)
{
	GtkWidget **w;
	GdkCursor **cur;
	clipboard_browser_struct *cb = &clipboard_browser;


	if(cb->initialized)
	{
#define DO_UNREF_CURSOR		\
{ if(*cur != NULL) { gdk_cursor_destroy(*cur); *cur = NULL; } }

#define DO_DESTROY_WIDGET	\
{ if(*w != NULL) { gtk_widget_destroy(*w); *w = NULL; } }

	    cb->waiting_response = FALSE;

	    if(cb->monitor_changes_toid != (guint)(-1))
	    {
		gtk_timeout_remove(cb->monitor_changes_toid);
		cb->monitor_changes_toid = (guint)(-1);
	    }

	    ClipboardBrowserDDEClear(cb);


	    /* Begin destroying widgets */
	    /* View menu (right click) */
	    w = &cb->view_menu;
	    DO_DESTROY_WIDGET

	    /* Icon pixmaps */
	    w = &cb->status_content_icon_pm;
	    DO_DESTROY_WIDGET
	    w = &cb->status_owned_icon_pm;
	    DO_DESTROY_WIDGET

	    /* Views */
	    w = &cb->view_text_vbox;
	    DO_DESTROY_WIDGET
	    g_free(cb->view_text_widget);
	    cb->view_text_widget = NULL;
	    cb->total_view_text_widgets = 0;

	    w = &cb->view_binary_vbox;
	    DO_DESTROY_WIDGET
	    g_free(cb->view_binary_widget);
	    cb->view_binary_widget = NULL;
	    cb->total_view_binary_widgets = 0;

	    /* Notebook */
	    w = &cb->notebook;
	    DO_DESTROY_WIDGET


	    /* Toplevel */
	    w = &cb->toplevel;
	    cb->clear_mi = NULL;
	    cb->monitor_changes_micheck = NULL;
	    cb->view_text_micheck = NULL;
	    cb->view_binary_micheck = NULL;
	    DO_DESTROY_WIDGET

	    if(cb->accelgrp != NULL)
	    {
		gtk_accel_group_unref(cb->accelgrp);
		cb->accelgrp = NULL;
	    }

	    /* Cursors */
	    cur = &cb->busy_cur;
	    DO_UNREF_CURSOR
	    cur = &cb->pan_cur;
	    DO_UNREF_CURSOR

#undef DO_UNREF_CURSOR
#undef DO_DESTROY_WIDGET
	}

	/* Clear clipboard structure */
	memset(cb, 0x00, sizeof(clipboard_browser_struct));
}

