/*
** 1998-08-15 -	A small utility module that deals with icons. Serves two main purposes:
**		1) Provides name-to-pixmap lookups, and thus a form of caching, and
**		2) Allows the search paths used to locate icon pixmaps to be abstracted
**		   somewhat. Nice.
**		This module is mainly used by e.g. dpformat, when it needs to render
**		icon pixmaps.
** 1998-08-30 -	Added function to seek out all icons in a path. Also added a built-in
**		inline pixmap which is used when a named pixmap can't be found.
*/

#include "gentoo.h"

#include <stdlib.h>

#include "strutil.h"
#include "fileutil.h"
#include "iconutil.h"

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

/* This name is reserved to mean "a blank icon". It is very easy to think of it as specifying
** that NO icon should be shown.
*/
#define	ICON_NONE	"(None)"

#include "graphics/icon_empty.xpm"
#include "graphics/icon_failed.xpm"

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

struct _IconInfo {
	GHashTable	*icon;			/* Provides name-to-pixmap lookup. */
	const gchar	*path;			/* A local copy of the PTID_ICONS path. */
	GdkPixmap	*empty_pixmap;		/* An empty icon. It's just blank! */
	GdkBitmap	*empty_mask;		/* The very empty mask. */
	GdkPixmap	*fail_pixmap;		/* This is returned if a named icon isn't found. */
	GdkBitmap	*fail_mask;		/* Mask for unable-to-load case. */
};

typedef struct {
	char		name[PATH_MAX];			/* This is real big. */
	GdkPixmap	*pixmap;
	GdkBitmap	*mask;
} IconGfx;

typedef struct {
	GHashTable	*icon;
	GList		*free_list;
	int		count;
} FlushInfo;

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

/* 1998-08-15 -	IconGfx comparison function for hash table. */
static gint cmp_icon(gconstpointer a, gconstpointer b)
{
	return strcmp(((IconGfx *) a)->name, ((IconGfx *) b)->name) == 0;
}

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

/* 1999-03-31 -	Initialize icon utility module. */
IconInfo * ico_initialize(MainInfo *min)
{
	IconInfo	*ico;
	GtkWidget	*win;		/* Um, this is fairly ugly. */

	ico = g_malloc(sizeof *ico);

	ico->icon = g_hash_table_new(g_str_hash, cmp_icon);

	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);	/* This is extremely ugly. Sue me. */
	gtk_widget_realize(win);
	ico->empty_pixmap = gdk_pixmap_create_from_xpm_d(win->window, &ico->empty_mask, NULL, icon_empty_xpm);
	ico->fail_pixmap  = gdk_pixmap_create_from_xpm_d(win->window, &ico->fail_mask,  NULL, icon_failed_xpm);
	gtk_widget_destroy(win);

	ico->path = min->cfg.path.path[PTID_ICON]->str;

	return ico;
}

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

/* 1998-08-30 -	Filter out stuff not ending in ".xpm".
** 1998-09-02 -	Eased up the implementation, using the string utility module. I like one-liners.
*/
static gboolean icon_filter(const gchar *path, const gchar *name)
{
	return str_has_suffix(name, ".xpm");
}

/* 1998-08-30 -	This returns a (possibly giant) list of all icon names in the given
**		icon <path>. If <path> is NULL, the current icon path (PTID_ICON) is used.
**		All path components are scanned for files ending in ".xpm"; these are assumed
**		to be icon pixmaps. The returned list has each element's <data> pointer pointing
**		at a dynamically allocated string which is the icon name. This memory tied up
**		by these strings is reclaimed by calling ico_free_all() on the list once you're
**		done with it.
*/
GList * ico_get_all(MainInfo *min, const gchar *path)
{
	GList	*list = NULL;
	gchar	*head;

	if(path == NULL)
		path = min->cfg.path.path[PTID_ICON]->str;

	if(path != NULL)
	{
		list = fut_scan_path(path, icon_filter, FALSE);
		if((head = g_strdup(ICON_NONE)) != NULL)
			list = g_list_prepend(list, head);
	}
	return list;
}

/* 1998-08-30 -	Free a list of icon names as returned by ico_get_all() above. */
void ico_free_all(GList *list)
{
	if(list != NULL)
		fut_free_path(list);
}

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

/* 1998-08-15 -	Internal icon loader. Uses the (new) file utility function 'fut_locate' to
**		search the icon path. A configurable icon path is on the agenda...
*/
static IconGfx * load(IconInfo *ico, GdkWindow *win, const gchar *name)
{
	char	*fname;
	IconGfx	*ig;

	ig = g_malloc(sizeof *ig);
	str_strncpy(ig->name, name, sizeof ig->name);
	if(strcmp(name, ICON_NONE) == 0)		/* Is it the special, reserved, blank icon? */
	{
		ig->pixmap = ico->empty_pixmap;
		ig->mask   = ico->empty_mask;
		return ig;
	}
	if((fname = fut_locate(ico->path, name)) != NULL)
	{
		if((ig->pixmap = gdk_pixmap_create_from_xpm(win, &ig->mask, NULL, fname)) != NULL)
		{
			gdk_pixmap_ref(ig->pixmap);
			gdk_bitmap_ref(ig->mask);
			return ig;
		}
	}
	g_free(ig);

	return NULL;
}

/* 1998-08-15 -	Internal icon retrieval function. Does a lookup in the hash table, loads
**		from disk if not found. The <win> parameter is annoying but required.
*/
static GdkPixmap * get_or_load(IconInfo *ico, GdkWindow *win, const gchar *name, GdkBitmap **mask)
{
	IconGfx	*ig;

	if(ico->icon == NULL)
		ico->icon = g_hash_table_new(g_str_hash, (GCompareFunc) cmp_icon);
	if(ico->icon != NULL)
	{
		if((ig = (IconGfx *) g_hash_table_lookup(ico->icon, (gpointer) name)) != NULL)
		{
			*mask = ig->mask;
			return ig->pixmap;
		}
		else if((ig = load(ico, win, name)) != NULL)
		{
			g_hash_table_insert(ico->icon, (gpointer) ig->name, (gpointer) ig);
			*mask = ig->mask;
			return ig->pixmap;
		}
		else
		{
			*mask = ico->fail_mask;
			return ico->fail_pixmap;
		}
	}
	return NULL;
}

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

/* 1998-08-15 -	Retrieve a pixmap corresponding to the given <name>, which is typically
**		an (unqualified) file name, such as "Makefile.xpm". Returns a pointer to
**		a GDK pixmap, and fills in the <mask> pointer with a pointer to the mask
**		needed when rendering this pixmap. Returns NULL on failure.
*/
GdkPixmap * ico_icon_get(MainInfo *min, GdkWindow *win, const gchar *name, GdkBitmap **mask)
{
	if((name == NULL) || (*name == '\0') || (mask == NULL))
		return NULL;

	min->ico->path = min->cfg.path.path[PTID_ICON]->str;	/* Make sure we have a fresh path. */

	return get_or_load(min->ico, win, name, mask);
}

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

/* 1998-09-02 -	Hash iterator callback. Notice extremely ugly mess: each item is inserted
**		into a list, and that list is then used to actually free the items. This
**		crap is required, since you can't call g_hash_table_remove() in the iterator. :(
*/
static void kill_icon(gpointer key, gpointer value, gpointer user)
{
	IconGfx		*icon = (IconGfx *) value;
	FlushInfo	*fli = (FlushInfo *) user;

	gdk_pixmap_unref(icon->pixmap);
	gdk_bitmap_unref(icon->mask);
	fli->free_list = g_list_append(fli->free_list, value);
	fli->count++;
}

/* 1998-09-02 -	Flush all loaded icons out, thus making the next reference a load for sure. */
void ico_flush(MainInfo *min)
{
	FlushInfo	fli = { NULL, NULL, 0};
	GList		*here, *next;

	if((fli.icon = min->ico->icon) != NULL)
	{
		g_hash_table_foreach(fli.icon, kill_icon, &fli);
		for(here = fli.free_list; here != NULL; here = next)
		{
			next = g_list_next(here);
			g_hash_table_remove(fli.icon, ((IconGfx *) here->data)->name);
			free(here->data);
		}
		g_list_free(fli.free_list);
	}
}
