/*                                                     -*- linux-c -*-
    Copyright (C) 2004 Tom Szilagyi

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: file_info.c 1158 2011-05-30 12:24:39Z tszilagyi $
*/

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <glib/gstdio.h>

#ifdef HAVE_MOD_INFO
#include <libmodplug/modplug.h>
#endif /* HAVE_MOD_INFO */

#ifdef HAVE_MOD
#include "decoder/dec_mod.h"
#endif /* HAVE_MOD */

#ifdef HAVE_WAVPACK
#include "decoder/dec_wavpack.h"
#endif /* HAVE_WAVPACK */

#include "common.h"
#include "utils.h"
#include "utils_gui.h"
#include "core.h"
#include "cdda.h"
#include "cover.h"
#include "decoder/file_decoder.h"
#include "music_browser.h"
#include "store_file.h"
#include "gui_main.h"
#include "options.h"
#include "trashlist.h"
#include "i18n.h"
#include "metadata_id3v1.h"
#include "metadata_id3v2.h"
#include "file_info.h"


/* requested width of value display/edit widgets */
#define VAL_WIDGET_WIDTH 250


/* import destination codes */
enum {
	IMPORT_DEST_ARTIST,
	IMPORT_DEST_RECORD,
	IMPORT_DEST_TITLE,
	IMPORT_DEST_YEAR,
	IMPORT_DEST_NUMBER,
	IMPORT_DEST_COMMENT,
	IMPORT_DEST_RVA
};


extern options_t options;
extern GtkWidget* gui_stock_label_button(gchar *label, const gchar *stock);

#define FI_MAXPAGES 256

typedef struct {
	int tag;
	GtkWidget * table;
	int n_rows;
	int n_cols;
	GtkWidget * combo;
	GSList * slist; /* list of insertable frames */
} pageidx_t;

typedef struct {
	int is_called_from_browser;
	int bail_out;
	char * filename;
	file_decoder_t * fdec;
	metadata_t * meta;
	trashlist_t * trash;
	GtkTreeModel * model;
	GtkTreeIter track_iter;
	GtkWidget * info_window;
	GtkWidget * event_box;
	GtkWidget * cover_image_area;
	GtkWidget * cover_align;
	GtkWidget * hbox;
	GtkWidget * button_table;
	GtkWidget * save_button;
	GtkWidget * nb;
	GtkWidget * combo;
	int addable_tags; /* META_TAG_*, bitwise or-ed */
	gint n_pages; /* number of notebook pages */
	pageidx_t pageidx[FI_MAXPAGES];
	int dirty; /* whether the dialog has unsaved changes */

	/* for MOD info */
	GtkWidget * smp_instr_list;
	GtkListStore * smp_instr_list_store;
} fi_t;

typedef struct {
	fi_t * fi;
	meta_frame_t * frame;
        int dest_type; /* one of IMPORT_DEST_* */
} import_data_t;



typedef struct {
	fi_t * fi;
	char savefile[MAXLEN];
	unsigned int image_size;
	void * image_data;
	GtkWidget * save_button;
} save_pic_t;

typedef struct {
	fi_t * fi;
	meta_frame_t * frame;
	save_pic_t * save_pic;
} change_pic_t;


/* 'source widget' for APIC frames */
typedef struct {
	GtkWidget * descr_entry;
	GtkWidget * type_combo;
	GtkWidget * mime_label;
	GtkWidget * image;
} apic_source_t;


extern GtkWidget * main_window;
extern GtkTreeStore * music_store;
extern GtkTreeSelection * music_select;
extern GtkWidget * music_tree;


#ifdef HAVE_MOD_INFO
void module_info_fill_page(fi_t * fi, meta_frame_t * frame, GtkWidget * vbox);
#endif /* HAVE_MOD_INFO */


gint fi_save(GtkWidget * widget, gpointer data);
void fi_procframe_ins(fi_t * fi, meta_frame_t * frame);
void fi_procframe_add_tag_page(fi_t * fi, meta_frame_t * frame);


fi_t *
fi_new(void) {

	fi_t * fi;
	int i;

	if ((fi = (fi_t *)calloc(1, sizeof(fi_t))) == NULL) {
		fprintf(stderr, "error: fileinfo_new(): calloc error\n");
		return NULL;
	}

	for (i = 0; i < FI_MAXPAGES; i++) {
		fi->pageidx[i].tag = -1;
	}

	return fi;
}

void
fi_delete(fi_t * fi) {

	int i;

	if (fi == NULL)
		return;

	if (fi->fdec != NULL) {
		file_decoder_delete(fi->fdec);
	}
	free(fi->filename);

	for (i = 0; i < FI_MAXPAGES; i++) {
		g_slist_free(fi->pageidx[i].slist);
		fi->pageidx[i].slist = NULL;
	}
	free(fi);
}


void
fi_mark_changed(fi_t * fi) {

	if (fi->dirty != 0)
		return;

	gtk_window_set_title(GTK_WINDOW(fi->info_window), _("*File info"));
	fi->dirty = 1;
}


void
fi_mark_unchanged(fi_t * fi) {

	if (fi->dirty == 0)
		return;

	gtk_window_set_title(GTK_WINDOW(fi->info_window), _("File info"));
	fi->dirty = 0;
}


import_data_t *
import_data_new(void) {

	import_data_t * data;

	if ((data = (import_data_t *)calloc(1, sizeof(import_data_t))) == NULL) {
		fprintf(stderr, "error: import_data_new(): calloc error\n");
		return NULL;
	}
	return data;
}


int
fi_close_dialog(fi_t * fi) {

	int ret;
	GtkWidget * dialog = gtk_message_dialog_new(GTK_WINDOW(fi->info_window),
						    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
						    GTK_MESSAGE_WARNING,
						    GTK_BUTTONS_NONE,
						    _("There are unsaved changes to the file metadata."));

	gtk_window_set_title(GTK_WINDOW(dialog), _("Warning"));
	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
			       _("Save and close"), 1,
			       _("Discard changes"), 2,
			       _("Do not close"), 0,
			       NULL);
	gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0);

	ret = aqualung_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	return ret;
}


int
fi_can_close(fi_t * fi) {

	if (fi->dirty != 0) {
		switch (fi_close_dialog(fi)) {
		case 1: /* Save and close */
			return fi_save(NULL, fi);
		case 2: /* Discard changes */
			return TRUE;
		default: /* Do not close */
			return FALSE;
		}
	}
	return TRUE;
}


gint
dismiss(GtkWidget * widget, gpointer data) {

	fi_t * fi = (fi_t *)data;

	if (fi_can_close(fi) != TRUE) {
		return TRUE;
	}

	unregister_toplevel_window(fi->info_window);
	gtk_widget_destroy(fi->info_window);
	trashlist_free(fi->trash);

	fi_delete(fi);
	return TRUE;
}


gint
info_window_close(GtkWidget * widget, GdkEventAny * event, gpointer data) {

	fi_t * fi = (fi_t *)data;

	if (fi_can_close(fi) != TRUE) {
		return TRUE;
	}

	unregister_toplevel_window(fi->info_window);
	gtk_widget_destroy(fi->info_window);
	trashlist_free(fi->trash);

	fi_delete(fi);
	return TRUE;
}


gint
info_window_key_pressed(GtkWidget * widget, GdkEventKey * kevent, gpointer data) {

	fi_t * fi = (fi_t *)data;
	int page;

	switch (kevent->keyval) {
	case GDK_Escape:
		dismiss(NULL, data);
		return TRUE;
		break;
	case GDK_Return:
		page = (gtk_notebook_get_current_page(GTK_NOTEBOOK(fi->nb)) + 1) % fi->n_pages;
		gtk_notebook_set_current_page(GTK_NOTEBOOK(fi->nb), page);
		break;
	}
	return FALSE;
}


int
fi_set_frame_from_source(fi_t * fi, meta_frame_t * frame) {

	char * parsefmt = meta_get_field_parsefmt(frame->type);
	char parsefmt_esc[MAXLEN];

	escape_percents(parsefmt, parsefmt_esc);
	if (META_FIELD_TEXT(frame->type)) {
		if (frame->field_val != NULL) {
			free(frame->field_val);
		}
		if (frame->type == META_FIELD_GENRE) {
			frame->field_val = gtk_combo_box_get_active_text(GTK_COMBO_BOX(frame->source));
		} else {
			frame->field_val = strdup(gtk_entry_get_text(GTK_ENTRY(frame->source)));
		}
	} else if (META_FIELD_INT(frame->type)) {
		int val = 0;
		const char * str = gtk_entry_get_text(GTK_ENTRY(frame->source));
		if (sscanf(str, parsefmt, &val) < 1) {
			char msg[MAXLEN];
			snprintf(msg, MAXLEN-1,
				 _("Conversion error in field %s:\n"
				   "'%s' does not conform to format '%s'!"),
				 frame->field_name, str, "%s");
			message_dialog(_("Error"),
				       fi->info_window, GTK_MESSAGE_ERROR,
				       GTK_BUTTONS_OK, NULL, msg, parsefmt_esc);
			return -1;
		} else {
			frame->int_val = val;
		}
	} else if (META_FIELD_FLOAT(frame->type)) {
		float val = 0;
		const char * str = gtk_entry_get_text(GTK_ENTRY(frame->source));
		if (sscanf(str, parsefmt, &val) < 1) {
			char msg[MAXLEN];
			snprintf(msg, MAXLEN-1,
				 _("Conversion error in field %s:\n"
				   "'%s' does not conform to format '%s'!"),
				 frame->field_name, str, "%s");
			message_dialog(_("Error"),
				       fi->info_window, GTK_MESSAGE_ERROR,
				       GTK_BUTTONS_OK, NULL, msg, parsefmt_esc);
			return -1;
		} else {
			frame->float_val = val;
		}
	} else if (META_FIELD_BIN(frame->type)) {
		if (frame->type == META_FIELD_APIC) {
			apic_source_t * source = (apic_source_t *)frame->source;
			if (frame->field_val != NULL) {
				free(frame->field_val);
			}
			frame->field_val = strdup(gtk_entry_get_text(GTK_ENTRY(source->descr_entry)));
			frame->int_val = gtk_combo_box_get_active(GTK_COMBO_BOX(source->type_combo));
			if (frame->length == 0) {
				message_dialog(_("Error"),
					       fi->info_window, GTK_MESSAGE_ERROR,
					       GTK_BUTTONS_OK, NULL,
					       _("Attached Picture frame with no image set!\n"
						 "Please set an image or remove the frame."));
				return -1;
			}
		}
	}
	return 0;
}


gint
fi_save(GtkWidget * widget, gpointer data) {

	fi_t * fi = (fi_t *)data;
	metadata_t * meta = fi->meta;
	meta_frame_t * frame = meta->root;
	int status = 0;

	while (frame != NULL) {
		if (fi_set_frame_from_source(fi, frame) < 0) {
			status = -1;
			break;
		}
		frame = frame->next;
	}

	if (status != 0) {
		return FALSE;
	}

	if (fi->fdec->meta_write != NULL) {
		int ret;
		ret = fi->fdec->meta_write(fi->fdec, fi->meta);
		if (ret == META_ERROR_NONE) {
			fi_mark_unchanged(fi);
			return TRUE;
		} else {
			message_dialog(_("Error"),
				       fi->info_window, GTK_MESSAGE_ERROR,
				       GTK_BUTTONS_OK, NULL,
				       _("Failed to write metadata to file.\n"
					 "Reason: %s"),
				       metadata_strerror(ret));
			return FALSE;
		}
	} else {
		fprintf(stderr, "programmer error: fdec->meta_write == NULL\n");
	}

	return FALSE;
}


void
import_button_pressed(GtkWidget * widget, gpointer gptr_data) {

	import_data_t * data = (import_data_t *)gptr_data;
	fi_t * fi = data->fi;
	meta_frame_t * frame = data->frame;
	GtkTreeModel * model = fi->model;
	GtkTreeIter track_iter = fi->track_iter;
	GtkTreeIter record_iter;
	GtkTreeIter artist_iter;
	GtkTreePath * path;
	char tmp[MAXLEN];

	record_data_t * record_data;
	track_data_t * track_data;

	if (fi_set_frame_from_source(fi, frame) < 0) {
		return;
	}

	switch (data->dest_type) {
	case IMPORT_DEST_TITLE:
		gtk_tree_store_set(music_store, &track_iter, MS_COL_NAME, frame->field_val, -1);
		music_store_mark_changed(&track_iter);
		break;
	case IMPORT_DEST_RECORD:
		gtk_tree_model_iter_parent(model, &record_iter, &track_iter);
		gtk_tree_store_set(music_store, &record_iter, MS_COL_NAME, frame->field_val, -1);
		music_store_mark_changed(&record_iter);
		break;
	case IMPORT_DEST_ARTIST:
		gtk_tree_model_iter_parent(model, &record_iter, &track_iter);
		gtk_tree_model_iter_parent(model, &artist_iter, &record_iter);
		gtk_tree_store_set(music_store, &artist_iter, MS_COL_NAME, frame->field_val, -1);
		gtk_tree_store_set(music_store, &artist_iter, MS_COL_SORT, frame->field_val, -1);
		path = gtk_tree_model_get_path(model, &track_iter);
		gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(music_tree), path, NULL, TRUE, 0.5f, 0.0f);
		music_store_mark_changed(&artist_iter);
		break;
	case IMPORT_DEST_YEAR:
		gtk_tree_model_iter_parent(model, &record_iter, &track_iter);
		gtk_tree_model_get(GTK_TREE_MODEL(music_store), &record_iter, MS_COL_DATA, &record_data, -1);
		if (sscanf(frame->field_val, "%d", &record_data->year) < 1) {
			char msg[MAXLEN];
			snprintf(msg, MAXLEN-1,
				 _("Error converting field %s to Year:\n"
				   "'%s' is not an integer number!"),
				 frame->field_name, frame->field_val);
			message_dialog(_("Error"),
				       fi->info_window, GTK_MESSAGE_ERROR,
				       GTK_BUTTONS_OK, NULL, msg);
			return;
		} else {
			music_store_mark_changed(&record_iter);
		}
		break;
	case IMPORT_DEST_NUMBER:
		snprintf(tmp, MAXLEN-1, "%02d", frame->int_val);
		gtk_tree_store_set(music_store, &track_iter, MS_COL_SORT, tmp, -1);
		music_store_mark_changed(&track_iter);
		break;
	case IMPORT_DEST_COMMENT:
		gtk_tree_model_get(model, &track_iter, MS_COL_DATA, &track_data, -1);
		tmp[0] = '\0';
		if (track_data->comment != NULL) {
			strncat(tmp, track_data->comment, MAXLEN-1);
		}
		if ((tmp[strlen(tmp)-1] != '\n') && (tmp[0] != '\0')) {
			strncat(tmp, "\n", MAXLEN-1);
		}
		strncat(tmp, frame->field_val, MAXLEN-1);
		free_strdup(&track_data->comment, tmp);
		music_store_mark_changed(&track_iter);
		break;
	case IMPORT_DEST_RVA:
		gtk_tree_model_get(model, &track_iter, MS_COL_DATA, &track_data, -1);
		track_data->rva = frame->float_val;
		track_data->use_rva = 1;
		music_store_mark_changed(&track_iter);
		break;
	}
}


void
fi_set_page(fi_t * fi) {

	fi->n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(fi->nb));
        if (fi->n_pages > 1) {
                gtk_notebook_set_current_page(GTK_NOTEBOOK(fi->nb), options.tags_tab_first ? 1 : 0);
        }
}


int
lookup_page(fi_t * fi, int tag) {

	int i;
	for (i = 0; i < FI_MAXPAGES; i++) {
		if (fi->pageidx[i].tag == tag)
			return i;
	}
	return -1;
}

int
fi_tabwidth(fi_t * fi, metadata_t * meta) {

	/* tabwidth is 2, +1 if called from Music Store, +1 if editable */
	int tabwidth = 2;
	tabwidth += fi->is_called_from_browser? 1 : 0;
	tabwidth += meta->writable ? 1 : 0;
	return tabwidth;
}


void
save_pic_button_pressed(GtkWidget * widget, gpointer data) {

	save_pic_t * save_pic = (save_pic_t *)data;
	fi_t * fi = save_pic->fi;

	GSList * lfiles = NULL;
	char filename[MAXLEN];
	char * dirname;

	dirname = g_path_get_dirname(options.currdir);
	snprintf(filename, MAXLEN-1, "%s/%s", dirname, save_pic->savefile);
	g_free(dirname);

	lfiles = file_chooser(_("Please specify the file to save the image to."),
			      fi->info_window,
			      GTK_FILE_CHOOSER_ACTION_SAVE,
			      FILE_CHOOSER_FILTER_NONE,
			      FALSE,
			      filename);

	if (lfiles != NULL) {

		FILE * f = fopen(filename, "wb");

		g_slist_free(lfiles);

		if (f == NULL) {
			fprintf(stderr, "error: fopen() failed\n");
			return;
		}
		if (fwrite(save_pic->image_data, 1, save_pic->image_size, f) != save_pic->image_size) {
			fprintf(stderr, "fwrite() error\n");
			return;
		}
		fclose(f);

		strncpy(options.currdir, filename, MAXLEN-1);
	}
}


void
save_pic_update(save_pic_t * save_pic, fi_t * fi, meta_frame_t * frame) {

	int i, r;
	char mtype[20];
	char savefilename[256];

	mtype[0] = '\0';
	strcpy(savefilename, "picture.");
	r = sscanf(frame->field_name, "image/%s", mtype);
	if (r == 0) {
		strncpy(mtype, frame->field_name, 19);
	}
	for (i = 0; mtype[i] != '\0'; i++) {
		mtype[i] = tolower(mtype[i]);
	}
	if (mtype[0] == '\0') {
		strcpy(mtype, "dat");
	}
	strncat(savefilename, mtype, 255);

	save_pic->fi = fi;
	strncpy(save_pic->savefile, savefilename, MAXLEN-1);
	save_pic->image_size = frame->length;
	save_pic->image_data = frame->data;

	if (save_pic->image_size > 0) {
		gtk_widget_set_sensitive(save_pic->save_button, TRUE);
	} else {
		gtk_widget_set_sensitive(save_pic->save_button, FALSE);
	}
}


GtkWidget *
make_image_from_binary(meta_frame_t * frame) {

	GdkPixbuf * pixbuf;
	GdkPixbuf * pixbuf_scaled;
	GtkWidget * image;
	int width, height;
	int new_width, new_height;

	void * data = frame->data;
	int length = frame->length;

	GdkPixbufLoader * loader;

	if (length == 0) {
		return gtk_label_new(_("(no image)"));
	}

	if (strcmp(frame->field_name, "-->") == 0) {
		/* display URL */
		char * str = meta_id3v2_to_utf8(0x0/*ascii*/, data, length);
		GtkWidget * label = gtk_label_new(str);
		g_free(str);
		return label;
	}

	loader = gdk_pixbuf_loader_new();
	if (gdk_pixbuf_loader_write(loader, frame->data, frame->length, NULL) != TRUE) {
		fprintf(stderr, "make_image_from_binary: failed to load image #1\n");
		g_object_unref(loader);
		return gtk_label_new(_("(error loading image)"));
	}

	if (gdk_pixbuf_loader_close(loader, NULL) != TRUE) {
		fprintf(stderr, "make_image_from_binary: failed to load image #2\n");
		g_object_unref(loader);
		return gtk_label_new(_("(error loading image)"));
	}

	pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
	width = gdk_pixbuf_get_width(pixbuf);
	height = gdk_pixbuf_get_height(pixbuf);
	new_width = (width > VAL_WIDGET_WIDTH) ? VAL_WIDGET_WIDTH : width;
	new_height = ((double)new_width / (double)width) * height;
	pixbuf_scaled = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height, GDK_INTERP_BILINEAR);
	image = gtk_image_new_from_pixbuf(pixbuf_scaled);
	g_object_unref(pixbuf_scaled);
	g_object_unref(loader);
	return image;
}


void
change_pic_button_pressed(GtkWidget * widget, gpointer data) {

	change_pic_t * change_pic = (change_pic_t *)data;
	save_pic_t * save_pic = change_pic->save_pic;
	fi_t * fi = change_pic->fi;
	meta_frame_t * frame = change_pic->frame;
	apic_source_t * source = ((apic_source_t *)(frame->source));
	GSList * lfiles = NULL;

	char str[MAXLEN];
	GdkPixbufFormat * pformat;
	gchar ** mime_types;

	GtkWidget * vbox; /* parent of image */

	lfiles = file_chooser(_("Please specify the file to load the image from."),
			      fi->info_window,
			      GTK_FILE_CHOOSER_ACTION_OPEN,
			      FILE_CHOOSER_FILTER_NONE,
			      FALSE,
			      options.currdir);

	if (lfiles != NULL) {
		g_slist_free(lfiles);
	} else {
		return;
	}

	pformat = gdk_pixbuf_get_file_info(options.currdir, NULL, NULL);
	if (pformat == NULL) {
		char msg[MAXLEN];
		snprintf(msg, MAXLEN-1, _("Could not load image from:\n%s"), options.currdir);
		message_dialog(_("Error"), fi->info_window, GTK_MESSAGE_ERROR,
			       GTK_BUTTONS_OK, NULL, msg);
		return;
	}

	if (frame->data != NULL) {
		free(frame->data);
	}
	if (!g_file_get_contents(options.currdir, ((gchar **)(&frame->data)),
				 ((gsize *)(&frame->length)), NULL)) {
		fprintf(stderr, "g_file_get_contents failed on %s\n", options.currdir);
		return;
	}

	mime_types = gdk_pixbuf_format_get_mime_types(pformat);
	if (mime_types[0] != NULL) {
		if (frame->field_name != NULL) {
			free(frame->field_name);
		}
		frame->field_name = strdup(mime_types[0]);
		g_strfreev(mime_types);
	} else {
		fprintf(stderr, "error: no mime type for image %s\n", options.currdir);
		g_strfreev(mime_types);
		return;
	}

	vbox = gtk_widget_get_parent(source->image);
	gtk_widget_destroy(source->image);
	source->image = make_image_from_binary(frame);
	gtk_widget_show(source->image);
	gtk_container_add(GTK_CONTAINER(vbox), source->image);
	snprintf(str, MAXLEN-1, _("MIME type: %s"), frame->field_name);
	gtk_label_set_text(GTK_LABEL(source->mime_label), str);
	/* update callback data for 'Save picture' */
	save_pic_update(save_pic, fi, frame);

	fi_mark_changed(fi);
}


GtkWidget *
fi_procframe_label_apic(fi_t * fi, meta_frame_t * frame) {

	metadata_t * meta = fi->meta;
	apic_source_t * source;

	char * pic_caption;
	char str[MAXLEN];
	GtkWidget * label_frame;
	GtkWidget * vbox = gtk_vbox_new(FALSE, 0);
	GtkWidget * hbox;
	GtkWidget * label;
	GtkWidget * button;

	change_pic_t * change_pic;

	save_pic_t * save_pic = (save_pic_t *)malloc(sizeof(save_pic_t));
	if (save_pic == NULL)
		return NULL;

	trashlist_add(fi->trash, save_pic);

	change_pic = (change_pic_t *)malloc(sizeof(change_pic_t));
	if (change_pic == NULL)
		return NULL;

	trashlist_add(fi->trash, change_pic);
	change_pic->fi = fi;
	change_pic->frame = frame;
	change_pic->save_pic = save_pic;

	frame->source = calloc(1, sizeof(apic_source_t));
	if (frame->source == NULL)
		return NULL;
	trashlist_add(fi->trash, frame->source);
	source = ((apic_source_t *)(frame->source));

	meta_get_fieldname(META_FIELD_APIC, &pic_caption);
	snprintf(str, MAXLEN-1, "%s:", pic_caption);

	label_frame = gtk_frame_new(pic_caption);
	gtk_container_add(GTK_CONTAINER(label_frame), vbox);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);


	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
	snprintf(str, MAXLEN-1, _("MIME type: %s"), frame->field_name);
	source->mime_label = gtk_label_new(str);
	gtk_box_pack_start(GTK_BOX(hbox), source->mime_label, FALSE, FALSE, 0);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
	label = gtk_label_new(_("Picture type:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

	if (meta->writable) {
		int i = 0;
		char * type_str;
		hbox = gtk_hbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
		source->type_combo = gtk_combo_box_new_text();
		while (1) {
			type_str = meta_id3v2_apic_type_to_string(i);
			if (type_str == NULL) {
				break;
			}
			gtk_combo_box_append_text(GTK_COMBO_BOX(source->type_combo), type_str);
			++i;
		}
		gtk_combo_box_set_active(GTK_COMBO_BOX(source->type_combo), frame->int_val);
		gtk_box_pack_start(GTK_BOX(hbox), source->type_combo, TRUE, TRUE, 0);
	} else {
		hbox = gtk_hbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
		snprintf(str, MAXLEN-1, "%s",
			 meta_id3v2_apic_type_to_string(frame->int_val));
		label = gtk_label_new(str);
		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	}

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
	label = gtk_label_new(_("Description:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

	if (meta->writable) {
		hbox = gtk_hbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
		source->descr_entry = gtk_entry_new();
		gtk_entry_set_text(GTK_ENTRY(source->descr_entry), frame->field_val);
		gtk_box_pack_start(GTK_BOX(hbox), source->descr_entry, TRUE, TRUE, 0);
	} else {
		hbox = gtk_hbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
		label = gtk_label_new((frame->field_val[0] == '\0') ?
				      _("(no description)") : frame->field_val);
		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	}


	hbox = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);

	button = gui_stock_label_button(_("Change"), GTK_STOCK_OPEN);
	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 3);
	if (meta->writable) {
		g_signal_connect(G_OBJECT(button), "clicked",
				 G_CALLBACK(change_pic_button_pressed),
				 (gpointer)change_pic);
	} else {
		gtk_widget_set_sensitive(button, FALSE);
	}

	save_pic->save_button = gui_stock_label_button(_("Save"), GTK_STOCK_SAVE);
	gtk_box_pack_start(GTK_BOX(hbox), save_pic->save_button, TRUE, TRUE, 3);
	g_signal_connect(G_OBJECT(save_pic->save_button), "clicked",
			 G_CALLBACK(save_pic_button_pressed),
			 (gpointer)save_pic);

	save_pic_update(save_pic, fi, frame);

	return label_frame;
}


GtkWidget *
fi_procframe_label(fi_t * fi, meta_frame_t * frame) {

	if (frame->type == META_FIELD_APIC) {
		return fi_procframe_label_apic(fi, frame);
	} else {
		char str[MAXLEN];
		GtkWidget * hbox = gtk_hbox_new(FALSE, 0);
		GtkWidget * label;
		snprintf(str, MAXLEN-1, "%s:", frame->field_name);
		label = gtk_label_new(str);
		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
		return hbox;
	}
}


void
fi_entry_changed_cb(GtkEntry * entry, gpointer data) {

	fi_t * fi = (fi_t *)data;
	fi_mark_changed(fi);
}


gint
genre_cmp(gconstpointer a, gconstpointer b) {

	return strcmp(id3v1_genre_str_from_code(GPOINTER_TO_INT(a)),
		      id3v1_genre_str_from_code(GPOINTER_TO_INT(b)));
}


void
make_genre_combo(meta_frame_t * frame, GtkWidget ** widget, GtkWidget ** entry) {

	int i;
	GtkWidget * combo = gtk_combo_box_entry_new_text();
	GSList * list = NULL;
	GSList * _list;

	for (i = 0; i < 256; i++) {
		char * genre = id3v1_genre_str_from_code(i);
		if (genre == NULL) {
			break;
		}
		list = g_slist_append(list, GINT_TO_POINTER(i));
	}

	list = g_slist_sort(list, genre_cmp);
	_list = list;
	while (_list != NULL) {
		char * genre = id3v1_genre_str_from_code(GPOINTER_TO_INT(_list->data));
		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), genre);
		_list = g_slist_next(_list);
	}
	g_slist_free(list);

	gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 4);

	*widget = combo;
	*entry = GTK_WIDGET(gtk_bin_get_child(GTK_BIN(combo)));

	if (frame->field_val[0] == '\0') { /* set default genre if none present */
		gtk_entry_set_text(GTK_ENTRY(*entry), id3v1_genre_str_from_code(0));
	} else {
		gtk_entry_set_text(GTK_ENTRY(*entry), frame->field_val);
	}

	/* for ID3v1, only predefined genres can be selected, no editing allowed */
	if (frame->tag == META_TAG_ID3v1) {
		GTK_WIDGET_UNSET_FLAGS(*entry, GTK_CAN_FOCUS);
		gtk_editable_set_editable(GTK_EDITABLE(*entry), FALSE);
	}
}


void
make_apic_widget(meta_frame_t * frame, GtkWidget ** widget, GtkWidget ** entry) {

	GtkWidget * apic_frame = gtk_frame_new(NULL);
	GtkWidget * image = make_image_from_binary(frame);
	GtkWidget * vbox = gtk_vbox_new(FALSE, 0);

	gtk_frame_set_shadow_type(GTK_FRAME(apic_frame), GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(apic_frame), vbox);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
	gtk_box_pack_start(GTK_BOX(vbox), image, TRUE, TRUE, 0);

	*widget = apic_frame;
	*entry = apic_frame;
	((apic_source_t *)(frame->source))->image = image;
}


GtkWidget *
fi_procframe_entry(fi_t * fi, meta_frame_t * frame) {

	metadata_t * meta = fi->meta;
	GtkWidget * widget = NULL;
	GtkWidget * entry = NULL;
	if (META_FIELD_BIN(frame->type)) {
		/* TODO create image/whatever widget, etc. */
		if (frame->type == META_FIELD_APIC) {
			make_apic_widget(frame, &widget, &entry);
		}
	} else if (META_FIELD_INT(frame->type)) {
		char str[MAXLEN];
		char * format = meta_get_field_renderfmt(frame->type);
		widget = entry = gtk_entry_new();
		snprintf(str, MAXLEN-1, format, frame->int_val);
		gtk_entry_set_text(GTK_ENTRY(entry), str);
	} else if (META_FIELD_FLOAT(frame->type)) {
		char str[MAXLEN];
		char * format = meta_get_field_renderfmt(frame->type);
		widget = entry = gtk_entry_new();
		snprintf(str, MAXLEN-1, format, frame->float_val);
		gtk_entry_set_text(GTK_ENTRY(entry), str);
	} else {
		if (meta->writable && (frame->type == META_FIELD_GENRE)) {
			make_genre_combo(frame, &widget, &entry);
		} else {
			widget = entry = gtk_entry_new();
			gtk_entry_set_text(GTK_ENTRY(entry), frame->field_val);
		}
	}

	if ((META_FIELD_TEXT(frame->type)) ||
	    (META_FIELD_INT(frame->type)) ||
	    (META_FIELD_FLOAT(frame->type))) {
		if (!meta->writable) {
			GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
			gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
		} else {
			g_signal_connect(G_OBJECT(widget), "changed",
					 G_CALLBACK(fi_entry_changed_cb), (gpointer)fi);
		}
		gtk_widget_set_size_request(widget, VAL_WIDGET_WIDTH, -1);
	}
	return widget;
}


import_data_t *
make_import_data_from_frame(fi_t * fi, meta_frame_t * frame, char * label) {

	import_data_t * data = import_data_new();
	trashlist_add(fi->trash, data);
	data->fi = fi;
	data->frame = frame;

	switch (frame->type) {
	case META_FIELD_TITLE:
		data->dest_type = IMPORT_DEST_TITLE;
		strcpy(label, _("Import as Title"));
		break;
	case META_FIELD_ARTIST:
		data->dest_type = IMPORT_DEST_ARTIST;
		strcpy(label, _("Import as Artist"));
		break;
	case META_FIELD_ALBUM:
		data->dest_type = IMPORT_DEST_RECORD;
		strcpy(label, _("Import as Record"));
		break;
	case META_FIELD_DATE:
		data->dest_type = IMPORT_DEST_YEAR;
		strcpy(label, _("Import as Year"));
		break;
	case META_FIELD_TRACKNO:
		data->dest_type = IMPORT_DEST_NUMBER;
		strcpy(label, _("Import as Track No."));
		break;
	case META_FIELD_RG_TRACK_GAIN:
	case META_FIELD_RG_ALBUM_GAIN:
	case META_FIELD_RVA2:
		data->dest_type = IMPORT_DEST_RVA;
		strcpy(label, _("Import as RVA"));
		break;
	default:
		data->dest_type = IMPORT_DEST_COMMENT;
		strcpy(label, _("Add to Comments"));
		break;
	}

	return data;
}


GtkWidget *
fi_procframe_importbtn(fi_t * fi, meta_frame_t * frame) {

	char label[MAXLEN];
	GtkWidget * button = gtk_button_new();
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(import_button_pressed),
			 (gpointer)make_import_data_from_frame(fi, frame, label));
	gtk_button_set_label(GTK_BUTTON(button), label);
	return button;
}


void
fi_fill_tagcombo(GtkComboBox * combo, int addable_tags) {

	int i;
	int tag = 1;

	if (!GTK_IS_COMBO_BOX(combo))
		return;

	for (i = 0; i < 100; i++) {
		gtk_combo_box_remove_text(combo, 0);
	}

	if (addable_tags == 0) {
		gtk_widget_set_sensitive(GTK_WIDGET(combo), FALSE);
		return;
	}

	gtk_widget_set_sensitive(GTK_WIDGET(combo), TRUE);

	while (tag <= META_TAG_MAX) {
		if (addable_tags & tag) {
			gtk_combo_box_append_text(combo, meta_get_tagname(tag));
		}
		tag <<= 1;
	}

	gtk_combo_box_set_active(combo, 0);
}

gint
fi_fill_combo_cmp(gconstpointer a, gconstpointer b) {

	int field1 = GPOINTER_TO_INT(a);
	int field2 = GPOINTER_TO_INT(b);
	char * str1;
	char * str2;

	if (!meta_get_fieldname(field1, &str1)) {
		fprintf(stderr, "fi_fill_combo_cmp: programmer error #1\n");
	}
	if (!meta_get_fieldname(field2, &str2)) {
		fprintf(stderr, "fi_fill_combo_cmp: programmer error #2\n");
	}

	return strcmp(str1, str2);
}


void
fi_fill_combo_foreach(gpointer data, gpointer user_data) {

	int field = GPOINTER_TO_INT(data);
	char * str;
	GtkComboBox * combo = (GtkComboBox *)user_data;
	if (!meta_get_fieldname(field, &str)) {
		fprintf(stderr, "fi_fill_combo_foreach: programmer error\n");
	}
	gtk_combo_box_append_text(combo, str);
}

void
fi_fill_combo(GtkComboBox * combo, GSList * slist) {

	int i;
	GSList * sorted_list = g_slist_copy(slist);
	for (i = 0; i < 100; i++) {
		gtk_combo_box_remove_text(combo, 0);
	}
	sorted_list = g_slist_sort(sorted_list, fi_fill_combo_cmp);
	g_slist_foreach(sorted_list, fi_fill_combo_foreach, combo);
	g_slist_free(sorted_list);
	gtk_combo_box_set_active(combo, 0);
}

typedef struct {
	fi_t * fi;
	meta_frame_t * frame;
	GtkWidget * label;
	GtkWidget * entry;
	GtkWidget * importbtn;
	GtkWidget * delbtn;
} fi_del_t;

void
fi_del_button_pressed(GtkWidget * widget, gpointer data) {

	fi_del_t * fi_del = (fi_del_t *)data;
	fi_t * fi = fi_del->fi;
	metadata_t * meta = fi_del->fi->meta;
	meta_frame_t * frame = fi_del->frame;

	if (frame->flags & META_FIELD_UNIQUE) {
		int page = lookup_page(fi, frame->tag);
		GtkWidget * combo = fi->pageidx[page].combo;
		GSList * slist = fi->pageidx[page].slist;
		slist = g_slist_append(slist, GINT_TO_POINTER(frame->type));
		fi->pageidx[page].slist = slist;
		fi_fill_combo(GTK_COMBO_BOX(combo), slist);
	}

	metadata_remove_frame(meta, frame);
	meta_frame_free(frame);

	gtk_widget_destroy(fi_del->label);
	gtk_widget_destroy(fi_del->entry);
	if (fi_del->importbtn != NULL) {
		gtk_widget_destroy(fi_del->importbtn);
	}
	gtk_widget_destroy(fi_del->delbtn);
	fi_mark_changed(fi);
}

GtkWidget *
fi_procframe_delbtn(fi_t * fi, meta_frame_t * frame,
		    GtkWidget * label, GtkWidget * entry, GtkWidget * importbtn) {

	fi_del_t * fi_del;
	GtkWidget * button = gtk_button_new();
	gtk_button_set_image(GTK_BUTTON(button),
			     gtk_image_new_from_stock(GTK_STOCK_DELETE,
						      GTK_ICON_SIZE_MENU));

	if (frame->flags & META_FIELD_MANDATORY) {
		gtk_widget_set_sensitive(button, FALSE);
		return button;
	}

	fi_del = calloc(1, sizeof(fi_del_t));
	fi_del->fi = fi;
	fi_del->frame = frame;
	fi_del->label = label;
	fi_del->entry = entry;
	fi_del->importbtn = importbtn;
	fi_del->delbtn = button;
	trashlist_add(fi->trash, fi_del);
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(fi_del_button_pressed),
			 (gpointer)fi_del);
	return button;
}


typedef struct {
	fi_t * fi;
	int tag;
} fi_add_t;


void
fi_add_button_pressed(GtkWidget * widget, gpointer data) {

	fi_add_t * fi_add = (fi_add_t *)data;
	fi_t * fi = fi_add->fi;
	metadata_t * meta = fi_add->fi->meta;
	int page = lookup_page(fi, fi_add->tag);
	GtkWidget * combo = fi->pageidx[page].combo;
	GSList * slist = fi->pageidx[page].slist;

	meta_frame_t * frame = meta_frame_new();
	int type;
	char * str;

	/* Create new frame */
	char * combo_entry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
	if (combo_entry == NULL) {
		return;
	}

	type = meta_frame_type_from_name(combo_entry);
	g_free(combo_entry);

	frame->tag = fi_add->tag;
	frame->type = type;
	frame->flags = meta_get_default_flags(fi_add->tag, type);
	if (meta_get_fieldname(type, &str)) {
		frame->field_name = strdup(str);
	} else {
		fprintf(stderr, "fi_add_button_pressed: programmer error\n");
	}
	if (options.metaedit_auto_clone) {
		metadata_clone_frame(meta, frame);
	}
	if (frame->field_val == NULL) {
		frame->field_val = strdup("");
	}
	frame->next = NULL;

	metadata_add_frame(meta, frame);

	if (frame->flags & META_FIELD_UNIQUE) {
		slist = g_slist_remove(slist, GINT_TO_POINTER(frame->type));
		fi->pageidx[page].slist = slist;
		fi_fill_combo(GTK_COMBO_BOX(combo), slist);
	}

	fi_procframe_ins(fi, frame);
	fi_mark_changed(fi);
}

GtkWidget *
fi_procframe_addbtn(fi_t * fi, int tag) {

	fi_add_t * fi_add;
	GtkWidget * button = gui_stock_label_button(_("Add"), GTK_STOCK_ADD);

	fi_add = calloc(1, sizeof(fi_add_t));
	fi_add->fi = fi;
	fi_add->tag = tag;
	trashlist_add(fi->trash, fi_add);
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(fi_add_button_pressed),
			 (gpointer)fi_add);
	return button;
}

void
fi_addtag_button_pressed(GtkWidget * widget, gpointer data) {

	fi_t * fi = (fi_t *)data;
	metadata_t * meta = fi->meta;
	meta_frame_t * frame;
	int tag;
	char * combo_entry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(fi->combo));
	if (combo_entry == NULL) {
		return;
	}
	frame = meta_frame_new();
	tag = frame->tag = meta_tag_from_name(combo_entry);
	g_free(combo_entry);

	metadata_add_mandatory_frames(meta, frame->tag);

	fi_procframe_add_tag_page(fi, frame);
	fi->addable_tags &= ~frame->tag;
	fi_fill_tagcombo(GTK_COMBO_BOX(fi->combo), fi->addable_tags);
	meta_frame_free(frame);

	/* display fields, if any */
	frame = metadata_get_frame_by_tag(meta, tag, NULL);
	while (frame != NULL) {
		fi_procframe_ins(fi, frame);
		frame = metadata_get_frame_by_tag(meta, tag, frame);
	}
	fi_mark_changed(fi);
}

GtkWidget *
fi_procframe_addtagbtn(fi_t * fi) {

	GtkWidget * button = gui_stock_label_button(_("Add"), GTK_STOCK_ADD);
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(fi_addtag_button_pressed),
			 (gpointer)fi);
	return button;
}


#ifdef HAVE_MOD_INFO
void
fi_procframe_ins_modinfo(fi_t * fi, meta_frame_t * frame) {

	int page = lookup_page(fi, frame->tag);
	GtkWidget * table = fi->pageidx[page].table;
	GtkWidget * vbox = gtk_vbox_new(FALSE, 0);

	module_info_fill_page(fi, frame, vbox);
	gtk_table_attach(GTK_TABLE(table), vbox, 0, 1, 0, 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show_all(fi->nb);
}
#endif /* HAVE_MOD_INFO */


void
fi_procframe_ins(fi_t * fi, meta_frame_t * frame) {

	metadata_t * meta = fi->meta;
	int page = lookup_page(fi, frame->tag);
	GtkWidget * table = fi->pageidx[page].table;
	int n_rows = fi->pageidx[page].n_rows;
	int n_cols = fi->pageidx[page].n_cols;
	int col = 0;

	GtkWidget * label = NULL;
	GtkWidget * entry = NULL;
	GtkWidget * importbtn = NULL;
	GtkWidget * delbtn = NULL;

	if (frame->type == META_FIELD_HIDDEN) {
		return;
	}

#ifdef HAVE_MOD_INFO
	if (frame->type == META_FIELD_MODINFO) {
		fi_procframe_ins_modinfo(fi, frame);
		return;
	}
#endif /* HAVE_MOD_INFO */

	if (frame->type == META_FIELD_APIC) {
		/* only display first APIC found */
		meta_frame_t * first_apic = metadata_get_frame_by_type(fi->meta, META_FIELD_APIC, NULL);
		if (frame == first_apic && (find_cover_filename(fi->filename) == NULL || !options.use_external_cover_first)) {
			display_cover_from_binary(fi->cover_image_area, fi->event_box, fi->cover_align,
						  48, 48, frame->data, frame->length, FALSE, TRUE);
		} else {
			display_cover(fi->cover_image_area, fi->event_box, fi->cover_align,
				      48, 48, fi->filename, FALSE, TRUE);
		}
	}

	++n_rows;
	fi->pageidx[page].n_rows = n_rows;
	gtk_table_resize(GTK_TABLE(table), n_rows, n_cols);

	/* Field label */
	label = fi_procframe_label(fi, frame);
	gtk_table_attach(GTK_TABLE(table), label, col, col+1, n_rows-1, n_rows,
			 GTK_FILL, GTK_FILL, 5, 3);
	++col;

	/* Field entry */
	entry = fi_procframe_entry(fi, frame);
	gtk_table_attach(GTK_TABLE(table), entry, col, col+1, n_rows-1, n_rows,
			 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 3);
	if (frame->type != META_FIELD_APIC) {
		frame->source = entry;
	}
	++col;

	/* Import button */
	if (fi->is_called_from_browser) {
		importbtn = fi_procframe_importbtn(fi, frame);
		gtk_table_attach(GTK_TABLE(table), importbtn, col, col+1, n_rows-1, n_rows,
				 GTK_FILL, GTK_FILL, 5, 3);
		++col;
	}

	/* Delete button */
	if (meta->writable) {
		delbtn = fi_procframe_delbtn(fi, frame,
					     label, entry, importbtn);
		gtk_table_attach(GTK_TABLE(table), delbtn, col, col+1, n_rows-1, n_rows,
				 GTK_FILL, GTK_FILL, 5, 3);
		++col;
	}

	if (meta->writable && (frame->flags & META_FIELD_UNIQUE)) {
		GtkWidget * combo = fi->pageidx[page].combo;
		GSList * slist = fi->pageidx[page].slist;
		slist = g_slist_remove(slist, GINT_TO_POINTER(frame->type));
		fi->pageidx[page].slist = slist;
		fi_fill_combo(GTK_COMBO_BOX(combo), slist);
	}

	gtk_widget_show_all(fi->nb);
}

typedef struct {
	fi_t * fi;
	int tag;
} fi_deltag_t;

void
fi_deltag_button_pressed(GtkWidget * widget, gpointer data) {

	fi_deltag_t * fi_deltag = (fi_deltag_t *)data;
	fi_t * fi = fi_deltag->fi;
	int tag = fi_deltag->tag;
	metadata_t * meta = fi->meta;
	meta_frame_t * frame;
	int i;

	/* Remove all frames with frame->tag == tag */
	frame = meta->root;
	while (frame != NULL) {
		if (frame->tag == tag) {
			meta_frame_t * f = frame->next;
			metadata_remove_frame(meta, frame);
			meta_frame_free(frame);
			frame = f;
		} else {
			frame = frame->next;
		}
	}

	/* Remove notebook page and update fi->pageidx */
	for (i = 0; i < FI_MAXPAGES; i++) {
		if (fi->pageidx[i].tag == tag) {
			int k;
			for (k = i; k < FI_MAXPAGES-1; k++) {
				fi->pageidx[k] = fi->pageidx[k+1];
			}
			fi->pageidx[FI_MAXPAGES-1].tag = -1;
			gtk_notebook_remove_page(GTK_NOTEBOOK(fi->nb), i);
			--fi->n_pages;
			break;
		}
	}

	fi->addable_tags |= tag;
	fi_fill_tagcombo(GTK_COMBO_BOX(fi->combo), fi->addable_tags);
	fi_mark_changed(fi);
}

GtkWidget *
fi_procframe_deltagbtn(fi_t * fi, int tag) {

	fi_deltag_t * fi_deltag;
	GtkWidget * button = gtk_button_new();
	gtk_button_set_image(GTK_BUTTON(button),
			     gtk_image_new_from_stock(GTK_STOCK_DELETE,
						      GTK_ICON_SIZE_MENU));

	fi_deltag = calloc(1, sizeof(fi_deltag_t));
	fi_deltag->fi = fi;
	fi_deltag->tag = tag;
	trashlist_add(fi->trash, fi_deltag);
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(fi_deltag_button_pressed),
			 (gpointer)fi_deltag);
	return button;
}

void
fi_procframe_add_tag_page(fi_t * fi, meta_frame_t * frame) {

	metadata_t * meta = fi->meta;
	GtkWidget * vbox = gtk_vbox_new(FALSE, 4);
	GtkWidget * vbox_padding = gtk_vbox_new(FALSE, 0);
	GtkWidget * scrwin = gtk_scrolled_window_new(NULL, NULL);
	GtkWidget * table = gtk_table_new(0, fi_tabwidth(fi, meta), FALSE);
	GtkWidget * label = gtk_label_new(meta_get_tagname(frame->tag));
	GtkWidget * hbox = gtk_hbox_new(FALSE, 0);
	GtkWidget * combo = gtk_combo_box_new_text();
	GtkWidget * addbtn, * delbtn;
	GSList * slist = meta_get_possible_fields(frame->tag);

	gtk_box_pack_start(GTK_BOX(vbox_padding), table, TRUE, TRUE, 7);
	gtk_box_pack_start(GTK_BOX(vbox), scrwin, TRUE, TRUE, 0);
	gtk_widget_set_size_request(scrwin, -1, 275);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin),
				       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin),
					    GTK_SHADOW_NONE);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrwin), vbox_padding);

	gtk_notebook_append_page(GTK_NOTEBOOK(fi->nb), vbox, label);
	fi->n_pages++;
	fi->pageidx[fi->n_pages-1].tag = frame->tag;
	fi->pageidx[fi->n_pages-1].table = table;
	fi->pageidx[fi->n_pages-1].combo = combo;
	fi->pageidx[fi->n_pages-1].slist = slist;
	fi->pageidx[fi->n_pages-1].n_rows = 0;
	fi->pageidx[fi->n_pages-1].n_cols = fi_tabwidth(fi, meta);

	if (meta->writable) {
		addbtn = fi_procframe_addbtn(fi, frame->tag);
		gtk_box_pack_start(GTK_BOX(hbox), addbtn, FALSE, FALSE, 5);

		label = gtk_label_new(_("field:"));
		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

		fi_fill_combo(GTK_COMBO_BOX(combo), slist);
		gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 5);

		delbtn = fi_procframe_deltagbtn(fi, frame->tag);
		gtk_box_pack_end(GTK_BOX(hbox), delbtn, FALSE, FALSE, 5);

		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);

		fi->addable_tags &= ~frame->tag;
		fi_fill_tagcombo(GTK_COMBO_BOX(fi->combo), fi->addable_tags);
	}
	gtk_widget_show_all(fi->nb);
}

void
fi_procframe(fi_t * fi, meta_frame_t * frame) {

	int page = lookup_page(fi, frame->tag);
	if (page == -1) {
		fi_procframe_add_tag_page(fi, frame);
	}
	fi_procframe_ins(fi, frame);
}

typedef struct {
	metadata_t * meta;
	void * data;
} fi_procmeta_wait_data_t;

void fi_procmeta(metadata_t * meta, void * data);

gboolean
fi_procmeta_wait(gpointer cb_data) {

	fi_procmeta_wait_data_t * wd = (fi_procmeta_wait_data_t *)cb_data;
	fi_procmeta(wd->meta, wd->data);
	free(wd);
	return FALSE;
}

void
fi_procmeta(metadata_t * meta, void * data) {

	fi_t * fi = (fi_t *)data;
	meta_frame_t * frame;
	int i;

	if (fi->bail_out) {
		/* this condition signals that file_decoder_open in show_file_info has failed */
		fi_delete(fi);
		return;
	}

	if ((fi->nb == NULL) || !GTK_IS_NOTEBOOK(fi->nb)) {
		fi_procmeta_wait_data_t * fi_procmeta_wait_data =
			calloc(1, sizeof(fi_procmeta_wait_data_t));
		fi_procmeta_wait_data->meta = meta;
		fi_procmeta_wait_data->data = data;
		aqualung_timeout_add(100, fi_procmeta_wait, (gpointer)fi_procmeta_wait_data);
		return;
	}

	if (fi->meta == meta) {
		return;
	}

	/* remove possible previous metadata pages from notebook */
	for (i = 0; i < FI_MAXPAGES; i++) {
		if (fi->pageidx[i].tag != -1) {
			gtk_notebook_remove_page(GTK_NOTEBOOK(fi->nb), i);
			fi->pageidx[i].tag = -1;
		}
	}

	if (fi->meta != NULL) {
		metadata_free(fi->meta);
	}
	fi->meta = meta;
	fi->addable_tags = meta->valid_tags;
	frame = meta->root;

	while (frame != NULL) {
		fi_procframe(fi, frame);
		frame = frame->next;
	}

	if (fi->meta->writable && (fi->save_button == NULL)) {
		fi->save_button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
		g_signal_connect(fi->save_button, "clicked",
				 G_CALLBACK(fi_save), (gpointer)fi);
		gtk_table_attach(GTK_TABLE(fi->button_table), fi->save_button,
				 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 3);
		gtk_widget_show_all(fi->info_window);
	}

	if (fi->meta->writable && (fi->combo == NULL)) {

		GtkWidget * addbtn, * label;

		addbtn = fi_procframe_addtagbtn(fi);
		gtk_box_pack_start(GTK_BOX(fi->hbox), addbtn, FALSE, FALSE, 5);

		label = gtk_label_new(_("tag:"));
		gtk_box_pack_start(GTK_BOX(fi->hbox), label, FALSE, FALSE, 5);

		fi->combo = gtk_combo_box_new_text();
		fi_fill_tagcombo(GTK_COMBO_BOX(fi->combo), fi->addable_tags);
		gtk_box_pack_start(GTK_BOX(fi->hbox), fi->combo, FALSE, FALSE, 5);

		gtk_widget_show_all(fi->info_window);
	}

	fi_set_page(fi);
}


gboolean
fi_cover_press_button_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {

	fi_t * fi = (fi_t *)data;

	if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
		meta_frame_t * frame;
		frame = metadata_get_frame_by_type(fi->meta, META_FIELD_APIC, NULL);
		if (frame != NULL && (find_cover_filename(fi->filename) == NULL || !options.use_external_cover_first)) {
			display_zoomed_cover_from_binary(fi->info_window, fi->event_box, frame->data, frame->length);
		} else {
			display_zoomed_cover(fi->info_window, fi->event_box, (gchar *)fi->filename);
		}
        }
        return TRUE;
}


void
show_file_info(char * name, char * file, int is_called_from_browser,
	       GtkTreeModel * model, GtkTreeIter track_iter, gboolean cover_flag) {

        char str[MAXLEN];
	gchar * file_display;

	GtkWidget * vbox;
	GtkWidget * hbox_t;
	GtkWidget * table;
	GtkWidget * hbox_name;
	GtkWidget * label_name;
	GtkWidget * entry_name;
	GtkWidget * hbox_path;
	GtkWidget * label_path;
	GtkWidget * entry_path;
	GtkWidget * dismiss_btn;

	GtkWidget * vbox_file;
	GtkWidget * label_file;
	GtkWidget * table_file;
	GtkWidget * hbox;
	GtkWidget * label;
	GtkWidget * entry;


	decoder_t * dec = NULL;

	int is_cdda = 0;
	fileinfo_t fileinfo;

	fi_t * fi = fi_new();
	if (fi == NULL) {
		return;
	}

#ifdef HAVE_CDDA
        if ((strlen(file) > 4) &&
            (file[0] == 'C') &&
            (file[1] == 'D') &&
            (file[2] == 'D') &&
            (file[3] == 'A')) {

                char device_path[CDDA_MAXLEN];
                long hash;
                int track;
                cdda_drive_t * drive;

                if (sscanf(file, "CDDA %s %lX %u", device_path, &hash, &track) < 3) {
                        return;
                }

                drive = cdda_get_drive_by_device_path(device_path);
                if ((drive == NULL) || (drive->disc.hash == 0L)) {
			return;
		}

		is_cdda = 1;

		fileinfo.format_str = _("Audio CD");
		fileinfo.sample_rate = 44100;
		fileinfo.is_mono = 0;
		fileinfo.format_flags = 0;
		fileinfo.bps = 2*16*44100;
		fileinfo.total_samples = (drive->disc.toc[track] - drive->disc.toc[track-1]) * 588;
        }
#endif /* HAVE_CDDA */

	if (!is_cdda) {
		fi->fdec = file_decoder_new();
		if (fi->fdec == NULL) {
			fi_delete(fi);
			return;
		}

		file_decoder_set_meta_cb(fi->fdec, fi_procmeta, fi);
		if (file_decoder_open(fi->fdec, file) != 0) {
			fi->bail_out = 1;
			return;
		}

		file_decoder_send_metadata(fi->fdec);

		dec = (decoder_t *)fi->fdec->pdec;

		fileinfo.format_str = dec->format_str;
		fileinfo.sample_rate = fi->fdec->fileinfo.sample_rate;
		fileinfo.is_mono = fi->fdec->fileinfo.is_mono;
		fileinfo.format_flags = fi->fdec->fileinfo.format_flags;
		fileinfo.bps = fi->fdec->fileinfo.bps;
		fileinfo.total_samples = fi->fdec->fileinfo.total_samples;
	}

	fi->is_called_from_browser = is_called_from_browser;
	fi->model = model;
	fi->track_iter = track_iter;
	fi->filename = strdup(file);
	fi->trash = trashlist_new();

	fi->info_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	register_toplevel_window(fi->info_window, TOP_WIN_SKIN | TOP_WIN_TRAY);
        gtk_window_set_title(GTK_WINDOW(fi->info_window), _("File info"));
	gtk_window_set_modal(GTK_WINDOW(fi->info_window), TRUE);
	gtk_window_set_transient_for(GTK_WINDOW(fi->info_window), GTK_WINDOW(main_window));
	gtk_window_set_position(GTK_WINDOW(fi->info_window), GTK_WIN_POS_CENTER);
        gtk_window_set_resizable(GTK_WINDOW(fi->info_window), TRUE);
	g_signal_connect(G_OBJECT(fi->info_window), "delete_event",
			 G_CALLBACK(info_window_close), (gpointer)fi);
        g_signal_connect(G_OBJECT(fi->info_window), "key_press_event",
			 G_CALLBACK(info_window_key_pressed), (gpointer)fi);
	gtk_container_set_border_width(GTK_CONTAINER(fi->info_window), 5);

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(fi->info_window), vbox);

	hbox_t = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox_t, FALSE, FALSE, 5);

	table = gtk_table_new(2, 2, FALSE);
	gtk_box_pack_start(GTK_BOX(hbox_t), table, TRUE, TRUE, 4);

        hbox_name = gtk_hbox_new(FALSE, 0);
	label_name = gtk_label_new(_("Track:"));
	gtk_box_pack_start(GTK_BOX(hbox_name), label_name, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table), hbox_name, 0, 1, 0, 1,
			 GTK_FILL, GTK_FILL, 5, 2);

	entry_name = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry_name, GTK_CAN_FOCUS);
	gtk_entry_set_text(GTK_ENTRY(entry_name), name);
	gtk_editable_set_editable(GTK_EDITABLE(entry_name), FALSE);
	gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, 0, 1,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 2);

	hbox_path = gtk_hbox_new(FALSE, 0);
	label_path = gtk_label_new(_("File:"));
	gtk_box_pack_start(GTK_BOX(hbox_path), label_path, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table), hbox_path, 0, 1, 1, 2,
			 GTK_FILL, GTK_FILL, 5, 2);

	file_display = g_filename_display_name(file);
	entry_path = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry_path, GTK_CAN_FOCUS);
	gtk_entry_set_text(GTK_ENTRY(entry_path), file_display);
	g_free(file_display);
	gtk_editable_set_editable(GTK_EDITABLE(entry_path), FALSE);
	gtk_table_attach(GTK_TABLE(table), entry_path, 1, 2, 1, 2,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 2);

	fi->cover_align = gtk_alignment_new(0.5f, 0.5f, 0.0f, 0.0f);
	gtk_box_pack_start(GTK_BOX(hbox_t), fi->cover_align, FALSE, FALSE, 0);
        fi->cover_image_area = gtk_image_new();
        fi->event_box = gtk_event_box_new ();
	gtk_container_add(GTK_CONTAINER(fi->cover_align), fi->event_box);
        gtk_container_add (GTK_CONTAINER (fi->event_box), fi->cover_image_area);
        g_signal_connect(G_OBJECT(fi->event_box), "button_press_event",
                         G_CALLBACK(fi_cover_press_button_cb), (gpointer)fi);

        if (options.show_cover_for_ms_tracks_only == TRUE) {
                if (cover_flag == TRUE) {
                        display_cover(fi->cover_image_area, fi->event_box, fi->cover_align,
                                      48, 48, file, FALSE, TRUE);
                } else {
                        hide_cover_thumbnail();
                }
        } else {
                display_cover(fi->cover_image_area, fi->event_box, fi->cover_align,
                              48, 48, file, FALSE, TRUE);
        }

	fi->hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_end(GTK_BOX(vbox), fi->hbox, FALSE, FALSE, 5);

	fi->nb = gtk_notebook_new();
	gtk_box_pack_start(GTK_BOX(vbox), fi->nb, TRUE, TRUE, 5);

	/* Audio data notebook page */

	vbox_file = gtk_vbox_new(FALSE, 4);
	table_file = gtk_table_new(6, 2, FALSE);
	gtk_box_pack_start(GTK_BOX(vbox_file), table_file, TRUE, TRUE, 10);
	label_file = gtk_label_new(_("Audio data"));
	fi->pageidx[0].tag = -1;
	gtk_notebook_append_page(GTK_NOTEBOOK(fi->nb), vbox_file, label_file);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Format:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
	gtk_widget_set_size_request(entry, 350, -1);
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	gtk_entry_set_text(GTK_ENTRY(entry), fileinfo.format_str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 0, 1,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Length:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	if (fileinfo.total_samples == 0) {
		strcpy(str, "N/A");
	} else {
		sample2time(fileinfo.sample_rate, fileinfo.total_samples, str, 0);
	}
	gtk_entry_set_text(GTK_ENTRY(entry), str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 1, 2,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Samplerate:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	sprintf(str, _("%ld Hz"), fileinfo.sample_rate);
	gtk_entry_set_text(GTK_ENTRY(entry), str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 2, 3,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Channel count:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 3, 4, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	if (fileinfo.is_mono)
		strcpy(str, _("MONO"));
	else
		strcpy(str, _("STEREO"));
	gtk_entry_set_text(GTK_ENTRY(entry), str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 3, 4,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Bandwidth:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 4, 5, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	if (fileinfo.bps == 0) {
		strcpy(str, "N/A kbit/s");
	} else {
		format_bps_label(fileinfo.bps, fileinfo.format_flags, str);
	}
	gtk_entry_set_text(GTK_ENTRY(entry), str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 4, 5,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new(_("Total samples:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 5, 6, GTK_FILL, GTK_FILL, 5, 3);
	entry = gtk_entry_new();
        GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
	if (fileinfo.total_samples == 0) {
		strcpy(str, "N/A");
	} else {
		sprintf(str, "%lld", fileinfo.total_samples);
	}
	gtk_entry_set_text(GTK_ENTRY(entry), str);
	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
	gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 5, 6,
			 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);

#ifdef HAVE_WAVPACK
	if (!is_cdda && fi->fdec->file_lib == WAVPACK_LIB) {
		wavpack_pdata_t * pd = (wavpack_pdata_t *)dec->pdata;
		int mode = WavpackGetMode(pd->wpc);

		hbox = gtk_hbox_new(FALSE, 0);
		label = gtk_label_new(_("Mode:"));
		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
		gtk_table_attach(GTK_TABLE(table_file), hbox, 0, 1, 7, 8, GTK_FILL, GTK_FILL, 5, 3);
		entry = gtk_entry_new();
		GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);

		if ((mode & MODE_LOSSLESS) && (mode & MODE_WVC)) {
			strncpy(str, "Hybrid Lossless", MAXLEN-1);
		} else if (mode & MODE_LOSSLESS) {
			strncpy(str, "Lossless", MAXLEN-1);
		} else {
			strncpy(str, "Hybrid Lossy", MAXLEN-1);
		}
		cut_trailing_whitespace(str);
		gtk_entry_set_text(GTK_ENTRY(entry), str);
		gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
		gtk_table_attach(GTK_TABLE(table_file), entry, 1, 2, 7, 8,
				(GtkAttachOptions)(GTK_EXPAND | GTK_FILL), GTK_FILL, 5, 3);
	}
#endif /* HAVE_WAVPACK */


	/* end of notebook stuff */

	gtk_widget_grab_focus(fi->nb);

	fi->button_table = gtk_table_new(1, 2, TRUE);
	gtk_box_pack_end(GTK_BOX(fi->hbox), fi->button_table, FALSE, TRUE, 3);

        dismiss_btn = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
	g_signal_connect(dismiss_btn, "clicked", G_CALLBACK(dismiss), (gpointer)fi);
	gtk_table_attach(GTK_TABLE(fi->button_table), dismiss_btn, 1, 2, 0, 1,
			 GTK_FILL, GTK_FILL, 5, 3);

	gtk_widget_show_all(fi->info_window);

	fi_set_page(fi);

	/* fi_t object is freed in info_window destroy handlers */
}


#ifdef HAVE_MOD_INFO
/*
 * type = 0 for sample list
 * type != 0 for instrument list
 */
void
show_list(fi_t * fi, gint type) {

	GtkTreeIter iter;
	gint i, len;
	gchar temp[MAXLEN], number[MAXLEN];
	decoder_t * md_dec;
	mod_pdata_t * md_pd;

        md_dec = (decoder_t *)(fi->fdec->pdec);
        md_pd = (mod_pdata_t *)md_dec->pdata;

        if (type) {
                len = ModPlug_NumInstruments(md_pd->mpf);
        } else {
                len = ModPlug_NumSamples(md_pd->mpf);
        }

        if (len) {
                gtk_list_store_clear(fi->smp_instr_list_store);
                for(i = 0; i < len; i++) {
                        memset(temp, 0, MAXLEN-1);

                        if (type) {
                                ModPlug_InstrumentName(md_pd->mpf, i, temp);
                        } else {
                                ModPlug_SampleName(md_pd->mpf, i, temp);
                        }

                        sprintf(number, "%2d", i);
                        gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(fi->smp_instr_list_store), &iter, NULL, i);
                        gtk_list_store_append(fi->smp_instr_list_store, &iter);
                        gtk_list_store_set(fi->smp_instr_list_store, &iter, 0, number, 1, temp, -1);
                }
        }
}

void
set_first_row(fi_t * fi) {

	GtkTreeIter iter;
	GtkTreePath * visible_path;

        gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(fi->smp_instr_list_store), &iter, NULL, 0);
        visible_path = gtk_tree_model_get_path(GTK_TREE_MODEL(fi->smp_instr_list_store), &iter);
        gtk_tree_view_set_cursor(GTK_TREE_VIEW(fi->smp_instr_list), visible_path, NULL, TRUE);
        gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fi->smp_instr_list), visible_path,
				     NULL, TRUE, 1.0, 0.0);
        gtk_widget_grab_focus(GTK_WIDGET(fi->smp_instr_list));
}

void
radio_buttons_cb(GtkToggleButton * toggle_button, gpointer data) {

	fi_t * fi = (fi_t *)data;

	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button))) {
                show_list(fi, 0);
        } else {
                show_list(fi, 1);
        }
        set_first_row(fi);
}

void
module_info_fill_page(fi_t * fi, meta_frame_t * frame, GtkWidget * vbox) {

	mod_info * mdi = (mod_info *)frame->data;

	gchar *a_type[] = {
		"None", "MOD", "S3M", "XM", "MED", "MTM", "IT", "669",
		"ULT", "STM", "FAR", "WAV", "AMF", "AMS", "DSM", "MDL",
		"OKT", "MID", "DMF", "PTM", "DBM", "MT2", "AMF0", "PSM",
		"J2B", "UMX"
	};

	gint i, n;
	gchar temp[MAXLEN];
	GtkWidget *table;
	GtkWidget *label;
	GtkWidget *mod_type_label;
	GtkWidget *mod_channels_label;
	GtkWidget *mod_patterns_label;
	GtkWidget *mod_samples_label;
	GtkWidget *mod_instruments_label;
	GtkWidget *vseparator;
	GtkWidget *hbox2;
	GtkWidget *vbox2;
	GtkWidget *vbox3;
	GtkWidget *samples_radiobutton = NULL;
	GtkWidget *instruments_radiobutton = NULL;
	GtkWidget *scrolledwindow;

	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;


        hbox2 = gtk_hbox_new (FALSE, 0);
        gtk_widget_show (hbox2);
        gtk_box_pack_start (GTK_BOX (vbox), hbox2, TRUE, TRUE, 0);

        vbox2 = gtk_vbox_new (FALSE, 0);
        gtk_widget_show (vbox2);
        gtk_box_pack_start (GTK_BOX (hbox2), vbox2, FALSE, FALSE, 0);

        if (mdi->instruments) {

                table = gtk_table_new (5, 2, FALSE);
                gtk_widget_show (table);
                gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
                gtk_container_set_border_width (GTK_CONTAINER (table), 8);
                gtk_table_set_row_spacings (GTK_TABLE (table), 6);
                gtk_table_set_col_spacings (GTK_TABLE (table), 4);

                label = gtk_label_new (_("Type:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_type_label = gtk_label_new ("");
                gtk_widget_show (mod_type_label);
                gtk_table_attach (GTK_TABLE (table), mod_type_label, 1, 2, 0, 1,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Channels:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_channels_label = gtk_label_new ("");
                gtk_widget_show (mod_channels_label);
                gtk_table_attach (GTK_TABLE (table), mod_channels_label, 1, 2, 1, 2,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Patterns:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_patterns_label = gtk_label_new ("");
                gtk_widget_show (mod_patterns_label);
                gtk_table_attach (GTK_TABLE (table), mod_patterns_label, 1, 2, 2, 3,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Samples:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_samples_label = gtk_label_new ("");
                gtk_widget_show (mod_samples_label);
                gtk_table_attach (GTK_TABLE (table), mod_samples_label, 1, 2, 3, 4,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Instruments:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_instruments_label = gtk_label_new ("");
                gtk_widget_show (mod_instruments_label);
                gtk_table_attach (GTK_TABLE (table), mod_instruments_label, 1, 2, 4, 5,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                sprintf(temp, "%d", mdi->instruments);
                gtk_label_set_text (GTK_LABEL(mod_instruments_label), temp);


                table = gtk_table_new (2, 1, FALSE);
                gtk_widget_show (table);
                gtk_box_pack_end (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
                gtk_container_set_border_width (GTK_CONTAINER (table), 8);
                gtk_table_set_row_spacings (GTK_TABLE (table), 4);

                samples_radiobutton = gtk_radio_button_new_with_mnemonic (NULL, _("Samples"));
                gtk_widget_show (samples_radiobutton);
                gtk_table_attach (GTK_TABLE (table), samples_radiobutton, 0, 1, 0, 1,
                                  (GtkAttachOptions) (GTK_FILL | GTK_EXPAND),
                                  (GtkAttachOptions) (0), 0, 0);
		g_signal_connect(G_OBJECT(samples_radiobutton), "toggled",
				 G_CALLBACK(radio_buttons_cb), (gpointer)fi);

                instruments_radiobutton = gtk_radio_button_new_with_mnemonic_from_widget (GTK_RADIO_BUTTON(samples_radiobutton), _("Instruments"));
                gtk_widget_show (instruments_radiobutton);
                gtk_table_attach (GTK_TABLE (table), instruments_radiobutton, 0, 1, 1, 2,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);


        } else {

                table = gtk_table_new (4, 2, FALSE);
                gtk_widget_show (table);
                gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
                gtk_container_set_border_width (GTK_CONTAINER (table), 8);
                gtk_table_set_row_spacings (GTK_TABLE (table), 4);
                gtk_table_set_col_spacings (GTK_TABLE (table), 4);

                label = gtk_label_new (_("Type:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_type_label = gtk_label_new ("");
                gtk_widget_show (mod_type_label);
                gtk_table_attach (GTK_TABLE (table), mod_type_label, 1, 2, 0, 1,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Channels:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_channels_label = gtk_label_new ("");
                gtk_widget_show (mod_channels_label);
                gtk_table_attach (GTK_TABLE (table), mod_channels_label, 1, 2, 1, 2,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Patterns:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_patterns_label = gtk_label_new ("");
                gtk_widget_show (mod_patterns_label);
                gtk_table_attach (GTK_TABLE (table), mod_patterns_label, 1, 2, 2, 3,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);

                label = gtk_label_new (_("Samples:"));
                gtk_widget_show (label);
                gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
                gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5);

                mod_samples_label = gtk_label_new ("");
                gtk_widget_show (mod_samples_label);
                gtk_table_attach (GTK_TABLE (table), mod_samples_label, 1, 2, 3, 4,
                                  (GtkAttachOptions) (GTK_FILL),
                                  (GtkAttachOptions) (0), 0, 0);
        }

        n = mdi->type;
        i = 0;

        while (n > 0) {         /* calculate module type index */
                n >>= 1;
                i++;
        }

        gtk_label_set_text (GTK_LABEL(mod_type_label), a_type[i]);
        sprintf(temp, "%d", mdi->channels);
        gtk_label_set_text (GTK_LABEL(mod_channels_label), temp);
        sprintf(temp, "%d", mdi->patterns);
        gtk_label_set_text (GTK_LABEL(mod_patterns_label), temp);
        sprintf(temp, "%d", mdi->samples);
        gtk_label_set_text (GTK_LABEL(mod_samples_label), temp);

        vseparator = gtk_vseparator_new ();
        gtk_widget_show (vseparator);
        gtk_box_pack_start (GTK_BOX (hbox2), vseparator, FALSE, FALSE, 4);

        vbox3 = gtk_vbox_new (FALSE, 0);
        gtk_widget_show (vbox3);
        gtk_box_pack_start (GTK_BOX (hbox2), vbox3, TRUE, TRUE, 0);

        scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
        gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow), 4);
        gtk_widget_show (scrolledwindow);
        gtk_widget_set_size_request (scrolledwindow, -1, 220);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        gtk_box_pack_end (GTK_BOX (vbox3), scrolledwindow, TRUE, TRUE, 0);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_SHADOW_IN);

        fi->smp_instr_list_store = gtk_list_store_new(2,
						      G_TYPE_STRING,
						      G_TYPE_STRING);
        fi->smp_instr_list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(fi->smp_instr_list_store));
	gtk_widget_set_name(fi->smp_instr_list, "samples_instruments_list");
        gtk_widget_show (fi->smp_instr_list);
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("No."), renderer, "text", 0, NULL);
        gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
                                        GTK_TREE_VIEW_COLUMN_FIXED);
        gtk_tree_view_column_set_spacing(GTK_TREE_VIEW_COLUMN(column), 3);
        gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), FALSE);
        gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 40);
	gtk_tree_view_append_column(GTK_TREE_VIEW(fi->smp_instr_list), column);
	column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", 1, NULL);
        gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
                                        GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_append_column(GTK_TREE_VIEW(fi->smp_instr_list), column);
        gtk_tree_view_column_set_spacing(GTK_TREE_VIEW_COLUMN(column), 3);
        gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), FALSE);
        gtk_container_add (GTK_CONTAINER (scrolledwindow), fi->smp_instr_list);

        if (mdi->instruments && mdi->type == 0x4) { /* if XM module go to instrument page */
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(instruments_radiobutton), TRUE);
        } else {
                show_list(fi, 0);
                set_first_row(fi);
        }
}
#endif /* HAVE_MOD_INFO */


// vim: shiftwidth=8:tabstop=8:softtabstop=8 :

