/* 
 * Copyright (C) 2005 Sasha Vasko <sasha at aftercode.net>
 *
 * 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.
 *
 */

#define LOCAL_DEBUG
#include "../configure.h"

#include <unistd.h>

#include "../libAfterStep/asapp.h"
#include "../libAfterStep/screen.h"
#include "../libAfterImage/afterimage.h"


#include "asgtk.h"
#include "asgtkai.h"
#include "asgtkxmleditor.h"

/*  local function prototypes  */
static void asgtk_xml_view_class_init (ASGtkXMLViewClass * klass);
static void asgtk_xml_view_init (ASGtkXMLView * iv);
static void asgtk_xml_view_dispose (GObject * object);
static void asgtk_xml_view_finalize (GObject * object);
static void asgtk_xml_view_style_set (GtkWidget * widget,
																			GtkStyle * prev_style);
static void check_save_changes (ASGtkXMLView * xe);
static void asgtk_xml_view_tag_activate (GtkTreeView * tree_view,
																				 GtkTreePath * path,
																				 GtkTreeViewColumn * column,
																				 gpointer user_data);
static void asgtk_xml_view_tag_selected (GtkTreeSelection * selection,
																				 gpointer user_data);


static void asgtk_xml_editor_class_init (ASGtkXMLEditorClass * klass);
static void asgtk_xml_editor_init (ASGtkXMLEditor * iv);
static void asgtk_xml_editor_dispose (GObject * object);
static void asgtk_xml_editor_finalize (GObject * object);
static void asgtk_xml_editor_style_set (GtkWidget * widget,
																				GtkStyle * prev_style);

/*  private variables  */
static GtkVBoxClass *view_parent_class = NULL;
static GtkWindowClass *editor_parent_class = NULL;

GType asgtk_xml_view_get_type (void)
{
	static GType id_type = 0;

	if (!id_type) {
		static const GTypeInfo id_info = {
			sizeof (ASGtkXMLViewClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) asgtk_xml_view_class_init,
			NULL,											/* class_finalize */
			NULL,											/* class_data     */
			sizeof (ASGtkXMLView),
			0,												/* n_preallocs    */
			(GInstanceInitFunc) asgtk_xml_view_init,
		};

		id_type =
				g_type_register_static (GTK_TYPE_VBOX, "ASGtkXMLView", &id_info,
																0);
	}

	return id_type;
}

static void asgtk_xml_view_class_init (ASGtkXMLViewClass * klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	view_parent_class = g_type_class_peek_parent (klass);

	object_class->dispose = asgtk_xml_view_dispose;
	object_class->finalize = asgtk_xml_view_finalize;

	widget_class->style_set = asgtk_xml_view_style_set;
}

static void asgtk_xml_view_init (ASGtkXMLView * xv)
{
	xv->entry = NULL;
	xv->color_sel = NULL;
}

static void asgtk_xml_view_dispose (GObject * object)
{
	ASGtkXMLView *xv = ASGTK_XML_VIEW (object);

	if (xv->entry) {
		check_save_changes (xv);

		LOCAL_DEBUG_OUT (" entry ref_count = %d", xv->entry->ref_count);
		if (unref_asimage_list_entry (xv->entry))
			LOCAL_DEBUG_OUT (" entry ref_count = %d", xv->entry->ref_count);
		xv->entry = NULL;
		if (xv->color_sel) {
			gtk_widget_destroy (GTK_WIDGET (xv->color_sel));
			xv->color_sel = NULL;
		}
	}
	G_OBJECT_CLASS (view_parent_class)->dispose (object);
}

static void asgtk_xml_view_finalize (GObject * object)
{
	G_OBJECT_CLASS (view_parent_class)->finalize (object);
}

static void
asgtk_xml_view_style_set (GtkWidget * widget, GtkStyle * prev_style)
{
	/* ASGtkXMLEditor *id = ASGTK_XML_EDITOR (widget); */

	GTK_WIDGET_CLASS (view_parent_class)->style_set (widget, prev_style);
}

GType asgtk_xml_editor_get_type (void)
{
	static GType id_type = 0;

	if (!id_type) {
		static const GTypeInfo id_info = {
			sizeof (ASGtkXMLEditorClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) asgtk_xml_editor_class_init,
			NULL,											/* class_finalize */
			NULL,											/* class_data     */
			sizeof (ASGtkXMLEditor),
			0,												/* n_preallocs    */
			(GInstanceInitFunc) asgtk_xml_editor_init,
		};

		id_type =
				g_type_register_static (GTK_TYPE_WINDOW, "ASGtkXMLEditor",
																&id_info, 0);
	}

	return id_type;
}

static void asgtk_xml_editor_class_init (ASGtkXMLEditorClass * klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	editor_parent_class = g_type_class_peek_parent (klass);

	object_class->dispose = asgtk_xml_editor_dispose;
	object_class->finalize = asgtk_xml_editor_finalize;

	widget_class->style_set = asgtk_xml_editor_style_set;

}

static void asgtk_xml_editor_init (ASGtkXMLEditor * xe)
{
	xe->xml_view = NULL;
}

static void asgtk_xml_editor_dispose (GObject * object)
{
	/* ASGtkXMLEditor *xe = ASGTK_XML_EDITOR (object);
	   if( xe->xml_view ) 
	   {
	   gtk_widget_destroy( GTK_WIDGET(xe->xml_view) );
	   xe->xml_view = NULL ;
	   } */
	G_OBJECT_CLASS (editor_parent_class)->dispose (object);
}

static void asgtk_xml_editor_finalize (GObject * object)
{
	G_OBJECT_CLASS (editor_parent_class)->finalize (object);
}

static void
asgtk_xml_editor_style_set (GtkWidget * widget, GtkStyle * prev_style)
{
	/* ASGtkXMLEditor *id = ASGTK_XML_EDITOR (widget); */

	GTK_WIDGET_CLASS (editor_parent_class)->style_set (widget, prev_style);
}


/* actuall implementation : */
static char *get_xml_view_text (ASGtkXMLView * xe, Bool selection_only)
{
	GtkTextBuffer *buffer =
			gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->text_view));
	GtkTextIter start, end;

	if (selection_only)
		selection_only =
				gtk_text_buffer_get_selection_bounds (buffer, &start, &end);

	if (!selection_only) {
		gtk_text_buffer_get_start_iter (buffer, &start);
		gtk_text_buffer_get_end_iter (buffer, &end);
	}
	return gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
}

static char *get_validated_text (ASGtkXMLView * xe, Bool selection,
																 Bool report_success)
{
	ASXmlBuffer xb;
	int char_count = 0, tags_count = 0, last_separator = 0;
	char *text = get_xml_view_text (xe, selection);
	Bool success = False;
	GtkTextBuffer *buffer =
			gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->text_view));
	GtkTextIter start, end;

	memset (&xb, 0x00, sizeof (xb));
	do {
		reset_xml_buffer (&xb);
		while (text[char_count] != '\0') {
			char cc = text[char_count];

			while (xb.state >= 0 && spool_xml_tag (&xb, &cc, 1) <= 0) {
				LOCAL_DEBUG_OUT
						("[%c] : state=%d, tags_count=%d, level = %d, tag_type = %d",
						 cc, xb.state, xb.tags_count, xb.level, xb.tag_type);
			}
			LOCAL_DEBUG_OUT
					("[%c] : state=%d, tags_count=%d, level = %d, tag_type = %d", cc,
					 xb.state, xb.tags_count, xb.level, xb.tag_type);

			if (xb.state >= 0) {
				if (isspace (text[char_count]) ||
						text[char_count] == '<' ||
						text[char_count] == '>' ||
						text[char_count] == '=' || text[char_count] == '\"'
						|| text[char_count] == '/')
					last_separator = char_count;
			}
			++char_count;
			if ((xb.state == ASXML_Start && xb.tags_count > 0 && xb.level == 0)
					|| xb.state < 0)
				break;
		}
		tags_count += xb.tags_count;
		if (xb.state < 0) {
			switch (xb.state) {
			case ASXML_BadStart:
				asgtk_warning2 (GTK_WIDGET (xe),
												"Text encountered before opening tag bracket - not XML format",
												NULL, NULL);
				break;
			case ASXML_BadTagName:
				asgtk_warning2 (GTK_WIDGET (xe), "Invalid characters in tag name",
												NULL, NULL);
				break;
			case ASXML_UnexpectedSlash:
				asgtk_warning2 (GTK_WIDGET (xe), "Unexpected '/' encountered",
												NULL, NULL);
				break;
			case ASXML_UnmatchedClose:
				asgtk_warning2 (GTK_WIDGET (xe),
												"Closing tag encountered without opening tag",
												NULL, NULL);
				break;
			case ASXML_BadAttrName:
				asgtk_warning2 (GTK_WIDGET (xe),
												"Invalid characters in attribute name", NULL,
												NULL);
				break;
			case ASXML_MissingAttrEq:
				asgtk_warning2 (GTK_WIDGET (xe),
												"Attribute name not followed by '=' character",
												NULL, NULL);
				break;
			default:
				asgtk_warning2 (GTK_WIDGET (xe), "Premature end of the input",
												NULL, NULL);
				break;
			}
			if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) {
				int offset = gtk_text_iter_get_offset (&start);

				char_count += offset;
				last_separator += offset;
			}
			if (char_count > last_separator) {
				gtk_text_buffer_get_iter_at_offset (buffer, &start,
																						last_separator);
				gtk_text_buffer_get_iter_at_offset (buffer, &end, char_count);
			} else {
				gtk_text_buffer_get_iter_at_offset (buffer, &start,
																						char_count - 1);
				gtk_text_buffer_get_iter_at_offset (buffer, &end, char_count);
			}
			gtk_text_buffer_select_range (buffer, &start, &end);
			break;
		} else if (text[char_count] == '\0') {
			if (xb.state == ASXML_Start && tags_count == 0) {
				asgtk_warning2 (GTK_WIDGET (xe),
												"Script contains no valid XML tags", NULL, NULL);
			} else if (xb.state >= 0 && xb.state != ASXML_Start) {
				asgtk_warning2 (GTK_WIDGET (xe),
												"XML Script is not properly terminated", NULL,
												NULL);
			} else if (xb.state == ASXML_Start && xb.level > 0) {
				asgtk_warning2 (GTK_WIDGET (xe),
												"XML Script contains unterminated XML tags", NULL,
												NULL);
			} else {
				if (report_success) {
					if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
						asgtk_warning2 (GTK_WIDGET (xe),
														"Selected XML seems to be syntactically valid",
														NULL, NULL);
					else
						asgtk_warning2 (GTK_WIDGET (xe),
														"XML Script seems to be syntactically valid",
														NULL, NULL);
				}
				success = True;
			}
		}
	}
	while (text[char_count] != '\0');
	reset_xml_buffer (&xb);
	if (!success) {
		g_free (text);
		text = NULL;
	}
	return text;
}


static void on_refresh_clicked (GtkButton * button, gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);
	ASVisual *asv = get_screen_visual (NULL);

	if (xe->entry) {
		char *text =
				get_validated_text (xe,
														(button ==
														 GTK_BUTTON (xe->render_selection_btn)),
														False);
		ASImage *im;

		if (text == NULL)
			return;
		im = compose_asimage_xml (asv, get_screen_image_manager (NULL),
															get_screen_font_manager (NULL), text,
															ASFLAGS_EVERYTHING, False, None, NULL);
		g_free (text);
		if (im) {
			if (xe->entry->preview)
				safe_asimage_destroy (xe->entry->preview);
			xe->entry->preview = im;
			asgtk_image_view_refresh (xe->image_view, False);
		} else {
			asgtk_warning2 (GTK_WIDGET (xe),
											"Failed to render image from changed xml.", NULL,
											NULL);
		}
	}
	gtk_widget_set_sensitive (xe->refresh_btn,
														(button ==
														 GTK_BUTTON (xe->render_selection_btn)));
}

static void on_validate_clicked (GtkButton * button, gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);
	char *text = get_validated_text (xe, True, True);

	if (text)
		g_free (text);
}


static Bool
save_text_buffer_to_file (ASGtkXMLView * xe, const char *filename)
{
	char *text = get_xml_view_text (xe, FALSE);
	FILE *fp = fopen (filename, "wb");
	Bool result = False;

	if (fp) {
		int len = strlen (text);

		if (fwrite (text, 1, len, fp) == len)
			result = True;
		else
			asgtk_warning2 (GTK_WIDGET (xe),
											"Failed to write to file \"%s\" : %s.", filename,
											g_strerror (errno));
		fclose (fp);
		result = True;
	} else {
		asgtk_warning2 (GTK_WIDGET (xe), "Failed to open file \"%s\" : %s.",
										filename, g_strerror (errno));
	}
	free (text);
	return result;
}


static void on_save_clicked (GtkButton * button, gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);
	char *filename = xe->entry->fullfilename;
	Bool signal_file_change = False;
	Bool new_file = False;

	if (button == GTK_BUTTON (xe->save_as_btn)) {
		GtkWidget *filesel = gtk_file_selection_new ("Save as : ");

		gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
																		 filename);
		if (gtk_dialog_run (GTK_DIALOG (filesel)) == GTK_RESPONSE_CANCEL)
			filename = NULL;
		else
			filename =
					mystrdup (gtk_file_selection_get_filename
										(GTK_FILE_SELECTION (filesel)));
		gtk_widget_destroy (filesel);
	}
	if (filename) {
		if (filename != xe->entry->fullfilename
				&& strcmp (filename, xe->entry->fullfilename) != 0) {
			if (CheckFile (filename) == 0) {
				if (asgtk_yes_no_question1
						(GTK_WIDGET (xe),
						 "File with the name \"%s\" already exists. Overwrite it ?",
						 filename)) {
					unlink (filename);
				} else {
					free (filename);
					return;
				}
			}
			new_file = True;
		}
		if (save_text_buffer_to_file (xe, filename)) {
			xe->dirty = False;
			gtk_widget_set_sensitive (xe->save_btn, FALSE);
			signal_file_change = True;
		}
		if (filename != xe->entry->fullfilename)
			free (filename);
	}

	if (signal_file_change)
		if (xe->file_change_handler)
			xe->file_change_handler (xe, xe->file_change_user_data, new_file);

}

static void
on_text_changed (GtkTextBuffer * textbuffer, gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);

	xe->dirty = True;
	gtk_widget_set_sensitive (xe->refresh_btn, TRUE);
	gtk_widget_set_sensitive (xe->save_btn, TRUE);
}

static void check_save_changes (ASGtkXMLView * xe)
{
	if (xe->dirty && xe->entry) {
		xe->dirty = False;
		if (asgtk_yes_no_question1
				(GTK_WIDGET (xe),
				 "Contents of the xml script \"%s\" changed. Save changes ?",
				 xe->entry->name))
			save_text_buffer_to_file (xe, xe->entry->fullfilename);
	}
}

#define MAX_ASXML_SCRIPT_TAG	22

static char *ASXMLScriptTagsHelp[MAX_ASXML_SCRIPT_TAG] = {
	"renders text string into new image, using specific font, size and texture.",
	"superimposes arbitrary number of images on top of each other. Compositing \"op\" could be one of the follwoing : \n"
			"add, alphablend, allanon, colorize, darken, diff, dissipate, hue, lighten, overlay, saturate, screen, sub, tint, value.\n"
			"All tags surrounded by this tag may have some of the common attributes in addition to their normal ones : \n"
			"crefid=\"ref_id\" (If set, percentages in \"x\" and \"y\" will be "
			"derived from the width and height of the crefid image.) "
			"x=\"child_offset_x\" y=\"child_offset_y\" align=\"right|center|left\" "
			"valign=\"top|middle|bottom\" clip_x=\"child_origin_x\" clip_y=\"child_origin_y\" "
			"clip_width=\"child_tile_width\" clip_height=\"child_tile_height\" tile=\"0|1\" tint=\"color\".",
	"loads image from the file. If src=\"xroot\" root background image is used.",
	"recall previously generated and named image by its id.",
	"releases(destroy if possible) previously generated and named image by its id.",
	"defines symbolic name for a color and set of variables. \n"
			"In addition to defining symbolic name for the color this tag will define 7 other variables :\n"
			" domain.sym_name.red, domain.sym_name.green, domain.sym_name.blue, domain.sym_name.alpha,"
			" domain.sym_name.hue, domain.sym_name.saturation, domain.sym_name.value .",
	"prints variable value to standard output.\n"
			"format_string is a Standard C format string with exactly 1 placeholder; "
			"var is a name of the variable, which value will be printed and val is a math expression to be printed.",
	"renders multipoint gradient.\n"
			"offsets are represented as floating point values in the range of 0.0 to 1.0. If offsets are not given, a smooth"
			" stepping from 0.0 to 1.0 will be used.\n"
			" angle is given in degrees. Default is 0. Supported values are 0, 45, 90, 135, 180, 225, 270, 315.",
	"generates image of specified size and fill it with solid or semitransparent color. Opacity ranges from 0 to 100 with 100 being completely opaque.",
	"writes generated/loaded image into the file of one of the supported formats: jpg, bmp, xcf, png, xpm, tiff.\n"
			"compress value is relevant to jpg format (0 to 100) or png format (0 to 10).",
	"sets image's background color. Applies to first child only.",
	"performs a gaussian blurr on an image. It is possible to selectively blur only some of the channels : a-alpha, r-red, g-green, b-blue. Applies to first child only.",
	"draws solid or semitransparent bevel frame around the image. Applies to first child only.",
	"creates new image as mirror copy of an old one.",
	"rotates an image in 90 degree increments (flip).",
	"scales image to arbitrary size. \n"
			"If you want to keep image proportions while scaling - use \"proportional\" instead of specific size for particular dimention.",
	"slices image to arbitrary size leaving corners unchanged.\n"
			"Contents of the image between x_start and x_end will be tiled horizontally. "
			"Contents of the image between y_start and y_end will be tiled vertically. "
			"This is useful to get background images to fit the size of the text or a widget, while preserving its borders undistorted, "
			"which is the usuall result of simple scaling.\n"
			"If you want to keep image proportions while resizing-use \"proportional\" instead of specific size for particular dimention.",
	"crops image to arbitrary area within it, optionally tinting it to specific color.",
	"tiles an image to specified area, optionally tinting it to specific color.",
	"adjusts Hue, Saturation and/or Value of an image and optionally tile an image to arbitrary area.\n"
			"Hue is measured in degrees from 0 to 360, saturation and value are measured from 0 to 100. One of the offsets must be not 0 for this tag to actually work.",
	"pads an image with solid color rectangles.",
	NULL
};

static char *ASXMLScriptTags[MAX_ASXML_SCRIPT_TAG][2] = {
	{"text",
	 "\n<text id=\"new_id\" font=\"font\" point=\"size\" fgcolor=\"color\""
	 " bgcolor=\"color\" fgimage=\"image_id\" bgimage=\"image_id\" "
	 " spacing=\"points\">Insert Text here</text>\n"},
	{"composite",
	 "\n<composite id=\"new_id\" op=\"op_desc\" keep-transparency=\"0|1\" merge=\"0|1\">\n</composite>\n"},
	{"img", "<img id=\"new_img_id\" src=\"filename\"/>"},
	{"recall", "<recall id=\"new_id\" srcid=\"image_id\"/>"},
	{"release", "<release srcid=\"image_id\"/>"},
	{"color",
	 "\n<color name=\"sym_name\" domain=\"var_domain\" argb=\"colorvalue\"/>\n"},
	{"printf",
	 "\n<printf format=\"format_string\" var=\"variable_name\" val=\"value\"/>\n"},
	{"gradient",
	 "\n<gradient id=\"new_id\" angle=\"degrees\" refid=\"refid\""
	 " width=\"pixels\" height=\"pixels\" colors =\"color1 [...]\""
	 " offsets=\"fraction1 [...]\"/>\n"},
	{"solid",
	 "\n<solid id=\"new_id\" color=\"color\" opacity=\"opacity\""
	 " width=\"pixels\" height=\"pixels\" refid=\"refid\"/>\n"},
	{"save",
	 "\n<save id=\"new_id\" dst=\"filename\" format=\"format\" compress=\"value\""
	 " opacity=\"value\" replace=\"0|1\" delay=\"mlsecs\">\n</save>\n"},
	{"background",
	 "\n<background id=\"new_id\" color=\"color\">\n</background>\n"},
	{"blur",
	 "\n<blur id=\"new_id\" horz=\"radius\" vert=\"radius\" channels=\"argb\">\n</blur>\n"},
	{"bevel",
	 "\n<bevel id=\"new_id\" colors=\"color1 color2\" width=\"pixels\""
	 " height=\"pixels\" refid=\"refid\" border=\"left top right bottom\" solid=0|1 outline=0|1>\n</bevel>\n"},
	{"mirror",
	 "\n<mirror id=\"new_id\" dir=\"vertical|horizontal\" width=\"pixels\" height=\"pixels\" refid=\"refid\">\n</mirror>\n"},
	{"rotate",
	 "\n<rotate id=\"new_id\" angle=\"90|180|270\" width=\"pixels\" height=\"pixels\" refid=\"refid\">\n</rotate>\n"},
	{"scale",
	 "\n<scale id=\"new_id\" ref_id=\"other_imag\" width=\"pixels\" height=\"pixels\">\n</scale>\n"},
	{"slice",
	 "\n<slice id=\"new_id\" ref_id=\"other_imag\" width=\"pixels\" height=\"pixels\""
	 " x_start=\"slice_x_start\" x_end=\"slice_x_end\" y_start=\"slice_y_start\""
	 " y_end=\"slice_y_end\">\n</slice>\n"},
	{"crop",
	 "\n<crop id=\"new_id\" refid=\"other_image\" srcx=\"pixels\" srcy=\"pixels\""
	 " width=\"pixels\" height=\"pixels\" tint=\"color\">\n</crop>\n"},
	{"tile",
	 "\n<tile id=\"new_id\" refid=\"other_image\" width=\"pixels\" height=\"pixels\""
	 " x_origin=\"pixels\" y_origin=\"pixels\" tint=\"color\" complement=0|1>\n</tile>\n"},
	{"hsv",
	 "\n<hsv id=\"new_id\" refid=\"other_image\" x_origin=\"pixels\" y_origin=\"pixels\""
	 " width=\"pixels\" height=\"pixels\" affected_hue=\"degrees|color\""
	 " affected_radius=\"degrees\" hue_offset=\"degrees\""
	 " saturation_offset=\"value\" value_offset=\"value\">\n</hsv>\n"},
	{"pad",
	 "\n<pad id=\"new_id\" left=\"pixels\" top=\"pixels\""
	 " right=\"pixels\" bottom=\"pixels\" color=\"color\""
	 " refid=\"refid\" width=\"pixels\" height=\"pixels\">\n</pad>\n"},
	{NULL, NULL}
};

static GtkWidget *make_xml_tags_list (ASGtkXMLView * xe)
{
	GtkWidget *scrolled_window;
	GtkTreeSelection *selection;
	GtkTreeModel *tree_model;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;
	GtkTreeIter iter;
	GtkListStore *store;
	int i;


	xe->tags_list = GTK_TREE_VIEW (gtk_tree_view_new ());
	tree_model =
			GTK_TREE_MODEL (gtk_list_store_new
											(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING));
	gtk_tree_view_set_model (xe->tags_list, tree_model);

	cell = gtk_cell_renderer_text_new ();

	column =
			gtk_tree_view_column_new_with_attributes ("supported tags:", cell,
																								"text", 0, NULL);
	gtk_tree_view_append_column (xe->tags_list,
															 GTK_TREE_VIEW_COLUMN (column));

	scrolled_window =
			ASGTK_SCROLLED_WINDOW (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
														 GTK_SHADOW_IN);
	gtk_widget_set_size_request (scrolled_window, 100, 200);
	ASGTK_CONTAINER_ADD (scrolled_window, xe->tags_list);
	gtk_widget_show (scrolled_window);

	selection = gtk_tree_view_get_selection (xe->tags_list);
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
	//g_signal_connect (selection, "changed",  G_CALLBACK (asgtk_image_dir_sel_handler), id);
	g_signal_connect (xe->tags_list, "row_activated",
										G_CALLBACK (asgtk_xml_view_tag_activate), xe);

	g_signal_connect (gtk_tree_view_get_selection
										(GTK_TREE_VIEW (xe->tags_list)), "changed",
										G_CALLBACK (asgtk_xml_view_tag_selected), xe);

	colorize_gtk_tree_view_window (scrolled_window);

	store = GTK_LIST_STORE (gtk_tree_view_get_model (xe->tags_list));
	for (i = 0; ASXMLScriptTags[i][0]; ++i) {
		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter,
												0, ASXMLScriptTags[i][0],
												1, ASXMLScriptTags[i][1], 2,
												ASXMLScriptTagsHelp[i] ? ASXMLScriptTagsHelp[i] :
												"", -1);
	}

	return scrolled_window;
}

static void
insert_tag_template_at_cursor (ASGtkXMLView * xe, const char *text)
{
	GtkTextBuffer *buffer =
			gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->text_view));
	GtkTextMark *mark;
	GtkTextIter start, end, line_start;
	int text_len = strlen (text);
	int start_mark;
	int start_line_no;
	char *start_text, *filler = NULL;
	int i, curr_line_start, filler_len = 0;
	Bool skip_leading_eol = False;

	mark = gtk_text_buffer_get_insert (buffer);
	gtk_text_buffer_get_iter_at_mark (buffer, &start, mark);
	start_line_no = gtk_text_iter_get_line (&start);
	gtk_text_buffer_get_iter_at_line (buffer, &line_start, start_line_no);

	start_text = gtk_text_iter_get_text (&line_start, &start);
	if (start_text) {
		for (i = 0; start_text[i] != '\0'; ++i)
			if (!isspace (start_text[i]))
				break;
		if (start_text[i] == '\0')
			skip_leading_eol = True;
		if (i > 0) {
			filler_len = i;
			filler = mystrndup (start_text, i);
		}
		free (start_text);
	}

	start_mark = gtk_text_iter_get_offset (&start);
	curr_line_start = 0;
	for (i = 0; text[i] != '\0'; ++i) {
		if (text[i] == '\n') {
			if (!skip_leading_eol || i > 0) {
				gtk_text_buffer_insert_at_cursor (buffer, &text[curr_line_start],
																					i + 1 - curr_line_start);
				if (filler) {
					gtk_text_buffer_insert_at_cursor (buffer, filler, filler_len);
					text_len += filler_len;
				}
			}
			curr_line_start = i + 1;
		}
	}
	if (i > curr_line_start)
		gtk_text_buffer_insert_at_cursor (buffer, &text[curr_line_start],
																			i - curr_line_start);

	gtk_text_buffer_get_iter_at_offset (buffer, &start, start_mark);
	gtk_text_buffer_get_iter_at_offset (buffer, &end, start_mark + text_len);
	gtk_text_buffer_select_range (buffer, &start, &end);

}


static void
asgtk_xml_view_tag_activate (GtkTreeView * tree_view,
														 GtkTreePath * path,
														 GtkTreeViewColumn * column,
														 gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);
	GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
	GtkTreeIter iter;
	char *tag_template = NULL;

	gtk_tree_model_get_iter (model, &iter, path);
	gtk_tree_model_get (model, &iter, 1, &tag_template, -1);
	if (tag_template)
		insert_tag_template_at_cursor (xe, tag_template);
}

static void
asgtk_xml_view_tag_selected (GtkTreeSelection * selection,
														 gpointer user_data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (user_data);
	GtkTreeModel *model;
	GtkTreeIter iter;
	char *tag_template = NULL;
	char *tag_help = NULL;

	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
		GtkTextBuffer *buffer =
				gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->help_view));
		int tag_template_len = 0;

		gtk_tree_model_get (model, &iter, 1, &tag_template, 2, &tag_help, -1);
		if (tag_template) {
			if (tag_template[0] == '\n')
				++tag_template;
			tag_template_len = strlen (tag_template);
			gtk_text_buffer_set_text (buffer, tag_template, tag_template_len);
		}
		if (tag_help) {
			if (tag_template_len == 0)
				gtk_text_buffer_set_text (buffer, tag_help, strlen (tag_help));
			else {
				GtkTextIter iter;

				gtk_text_buffer_get_iter_at_offset (buffer, &iter,
																						tag_template_len);
				gtk_text_buffer_insert (buffer, &iter, tag_help,
																strlen (tag_help));
			}
		}
	}
}


static void on_insert_tag_clicked (GtkWidget * button, gpointer data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (data);
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	GtkTreeModel *model;

	selection = gtk_tree_view_get_selection (xe->tags_list);
	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
		gpointer p = NULL;

		gtk_tree_model_get (model, &iter, 1, &p, -1);
		if (p != NULL) {
			insert_tag_template_at_cursor (xe, p);
		}
	}
}

static void on_color_sel_destroy (GtkWidget * widget, gpointer data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (data);

	xe->color_sel = NULL;
	gtk_widget_set_sensitive (GTK_WIDGET (xe->color_browser_btn), TRUE);
}

static void on_insert_color_clicked (GtkWidget * button, gpointer data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (data);

	if (xe->color_sel != NULL) {
		char *col_str = asgtk_color_selection_get_color_str (xe->color_sel);

		if (col_str) {
			GtkTextBuffer *buffer =
					gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->text_view));
			GtkTextIter start, end;
			int insmark;
			char *tmp;
			Bool open_quote = True, close_quote = True;

			gtk_text_buffer_get_iter_at_mark (buffer, &start,
																				gtk_text_buffer_get_mark (buffer,
																																	"insert"));
			insmark = gtk_text_iter_get_offset (&start);
			if (insmark > 0) {
				end = start;
				gtk_text_buffer_get_iter_at_offset (buffer, &start, insmark - 1);
				tmp = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
				if (tmp) {
					if (tmp[0] == '\"') {
						open_quote = False;
						close_quote = (tmp[1] != '\"');
					}
					free (tmp);
				}
			}
			if (open_quote)
				gtk_text_buffer_insert_at_cursor (buffer, "\"", 1);
			gtk_text_buffer_insert_at_cursor (buffer, col_str, strlen (col_str));
			if (close_quote)
				gtk_text_buffer_insert_at_cursor (buffer, "\"", 1);
			free (col_str);
		}
	}
}

static void on_color_browser_clicked (GtkWidget * button, gpointer data)
{
	ASGtkXMLView *xe = ASGTK_XML_VIEW (data);

	if (xe->color_sel == NULL) {
		GtkWidget *select_btn;

		xe->color_sel = ASGTK_COLOR_SELECTION (asgtk_color_selection_new ());
		g_signal_connect (G_OBJECT (xe->color_sel), "destroy",
											G_CALLBACK (on_color_sel_destroy), xe);

		select_btn =
				asgtk_color_selection_add_main_button (xe->color_sel,
																							 GTK_STOCK_ADD,
																							 G_CALLBACK
																							 (on_insert_color_clicked),
																							 xe);
		gtk_button_set_label (GTK_BUTTON (select_btn), "Insert Color");
	}
	gtk_widget_show (GTK_WIDGET (xe->color_sel));
	gtk_widget_set_sensitive (GTK_WIDGET (xe->color_browser_btn), FALSE);
}

/*  public functions  */
GtkWidget *asgtk_xml_view_new ()
{
	ASGtkXMLView *xe;
	GtkWidget *scrolled_window;
	GtkWidget *panes;
	GtkWidget *scale_check_box;
	GtkWidget *edit_hbox, *tags_vbox, *edit_vpanes, *save_hbox;
	GtkWidget *insert_tag_btn;

	xe = g_object_new (ASGTK_TYPE_XML_VIEW, NULL);

	gtk_container_set_border_width (GTK_CONTAINER (xe), 3);
//      gtk_widget_show (main_vbox);
//  gtk_container_add (GTK_CONTAINER (xe), main_vbox);
	gtk_box_set_spacing (GTK_BOX (xe), 3);

	panes = gtk_vpaned_new ();
	gtk_widget_show (panes);
	gtk_box_pack_start (GTK_BOX (xe), panes, TRUE, TRUE, 0);

	xe->image_view = ASGTK_IMAGE_VIEW (asgtk_image_view_new_horizontal ());
	gtk_widget_set_size_request (GTK_WIDGET (xe->image_view), 320, 240);
	gtk_paned_pack1 (GTK_PANED (panes), GTK_WIDGET (xe->image_view), TRUE,
									 TRUE);
	gtk_widget_show (GTK_WIDGET (xe->image_view));
	asgtk_image_view_set_aspect (xe->image_view, get_screen_width (NULL),
															 get_screen_height (NULL));
	asgtk_image_view_set_resize (xe->image_view,
															 ASGTK_IMAGE_VIEW_SCALE_TO_VIEW |
															 ASGTK_IMAGE_VIEW_TILE_TO_ASPECT,
															 ASGTK_IMAGE_VIEW_RESIZE_ALL);

	/* text editor controls : */
	edit_hbox = gtk_hbox_new (FALSE, 0);
	gtk_widget_show (edit_hbox);
	gtk_box_set_spacing (GTK_BOX (edit_hbox), 6);
	tags_vbox = gtk_vbox_new (FALSE, 0);
	gtk_widget_show (tags_vbox);
	gtk_box_set_spacing (GTK_BOX (tags_vbox), 3);
	edit_vpanes = gtk_vpaned_new ();
	gtk_widget_show (edit_vpanes);

	gtk_box_pack_start (GTK_BOX (edit_hbox), tags_vbox, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (edit_hbox), edit_vpanes, TRUE, TRUE, 0);

	scrolled_window = make_xml_tags_list (ASGTK_XML_VIEW (xe));
	gtk_box_pack_start (GTK_BOX (tags_vbox), scrolled_window, TRUE, TRUE, 0);

	xe->color_browser_btn =
			asgtk_add_button_to_box (NULL, GTK_STOCK_ADD, "Color Selector",
															 G_CALLBACK (on_color_browser_clicked), xe);
	gtk_box_pack_end (GTK_BOX (tags_vbox), xe->color_browser_btn, FALSE,
										FALSE, 0);

	insert_tag_btn =
			asgtk_add_button_to_box (NULL, GTK_STOCK_ADD, "Insert Tag template",
															 G_CALLBACK (on_insert_tag_clicked), xe);
	gtk_box_pack_end (GTK_BOX (tags_vbox), insert_tag_btn, FALSE, FALSE, 0);

	xe->text_view = gtk_text_view_new ();
	scrolled_window =
			ASGTK_SCROLLED_WINDOW (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
														 GTK_SHADOW_IN);
	gtk_widget_set_size_request (scrolled_window, 400, 200);
	ASGTK_CONTAINER_ADD (scrolled_window, xe->text_view);

	gtk_widget_show (scrolled_window);
	gtk_paned_pack1 (GTK_PANED (edit_vpanes), scrolled_window, TRUE, TRUE);

	xe->help_view = gtk_text_view_new ();
	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (xe->help_view),
															 GTK_WRAP_WORD);
	gtk_text_view_set_editable (GTK_TEXT_VIEW (xe->help_view), FALSE);

	scrolled_window =
			ASGTK_SCROLLED_WINDOW (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC,
														 GTK_SHADOW_IN);
	gtk_widget_set_size_request (scrolled_window, 600, 50);
	ASGTK_CONTAINER_ADD (scrolled_window, xe->help_view);

	gtk_widget_show (scrolled_window);
	colorize_gtk_widget (scrolled_window, get_colorschemed_style_normal ());

	gtk_paned_pack2 (GTK_PANED (edit_vpanes), scrolled_window, FALSE, FALSE);

	gtk_paned_pack2 (GTK_PANED (panes), edit_hbox, TRUE, TRUE);

	colorize_gtk_window (GTK_WIDGET (xe));


	/* buttons : */

	save_hbox = gtk_hbutton_box_new ();
	gtk_widget_show (save_hbox);

	xe->render_selection_btn =
			asgtk_add_button_to_box (NULL, GTK_STOCK_SAVE_AS, "Render Selection",
															 G_CALLBACK (on_refresh_clicked), xe);
	xe->refresh_btn =
			asgtk_add_button_to_box (NULL, GTK_STOCK_REFRESH, NULL,
															 G_CALLBACK (on_refresh_clicked), xe);
	xe->validate_btn =
			asgtk_add_button_to_box (NULL, GTK_STOCK_REFRESH, "Validate XML",
															 G_CALLBACK (on_validate_clicked), xe);
	xe->save_btn =
			asgtk_add_button_to_box (GTK_BOX (save_hbox), GTK_STOCK_SAVE, NULL,
															 G_CALLBACK (on_save_clicked), xe);
	xe->save_as_btn =
			asgtk_add_button_to_box (GTK_BOX (save_hbox), GTK_STOCK_SAVE_AS,
															 NULL, G_CALLBACK (on_save_clicked), xe);

	gtk_widget_set_sensitive (xe->refresh_btn, FALSE);
	gtk_widget_set_sensitive (xe->save_btn, FALSE);

	asgtk_image_view_add_tool (xe->image_view, save_hbox, 0);
	asgtk_image_view_add_tool (xe->image_view, xe->validate_btn, 3);
	asgtk_image_view_add_tool (xe->image_view, xe->render_selection_btn, 3);
	asgtk_image_view_add_tool (xe->image_view, xe->refresh_btn, 3);
	//asgtk_image_view_add_tool( xe->image_view, xe->save_btn, 3 );
	//asgtk_image_view_add_tool( xe->image_view, xe->save_as_btn, 0 );

	g_signal_connect (gtk_text_view_get_buffer
										(GTK_TEXT_VIEW (xe->text_view)), "changed",
										G_CALLBACK (on_text_changed), xe);


	scale_check_box =
			gtk_check_button_new_with_label ("Use screen aspect ratio");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_check_box), TRUE);
	gtk_widget_show (scale_check_box);
	colorize_gtk_widget (scale_check_box, get_colorschemed_style_normal ());
	g_signal_connect (G_OBJECT (scale_check_box), "toggled",
										G_CALLBACK (asgtk_image_view_screen_aspect_toggle),
										(gpointer) xe->image_view);
	gtk_button_set_alignment (GTK_BUTTON (scale_check_box), 1.0, 0.5);
	asgtk_image_view_add_detail (xe->image_view, scale_check_box, 0);

	scale_check_box =
			gtk_check_button_new_with_label ("Scale to fit this view");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_check_box), TRUE);
	gtk_widget_show (scale_check_box);
	colorize_gtk_widget (scale_check_box, get_colorschemed_style_normal ());
	g_signal_connect (G_OBJECT (scale_check_box), "toggled",
										G_CALLBACK (asgtk_image_view_scale_to_view_toggle),
										(gpointer) xe->image_view);
	gtk_button_set_alignment (GTK_BUTTON (scale_check_box), 1.0, 0.5);
	asgtk_image_view_add_detail (xe->image_view, scale_check_box, 0);
	LOCAL_DEBUG_OUT ("created image ASGtkXMLView object %p", xe);
	return GTK_WIDGET (xe);
}

void
asgtk_xml_view_set_entry (ASGtkXMLView * xe,
													struct ASImageListEntry *entry)
{
	GtkTextBuffer *buffer;

	g_return_if_fail (ASGTK_IS_XML_VIEW (xe));

	LOCAL_DEBUG_OUT (" ASGtk XML Editor object's %p entry to %p", xe, entry);
	check_save_changes (xe);

	if (xe->entry) {
		unref_asimage_list_entry (xe->entry);
		xe->entry = NULL;
	}
	if (entry && entry->type != ASIT_XMLScript)
		entry = NULL;

	if (entry) {
		LOCAL_DEBUG_OUT (" entry ref_count = %d", entry->ref_count);
		xe->entry = ref_asimage_list_entry (entry);
		LOCAL_DEBUG_OUT (" entry ref_count = %d", entry->ref_count);
		asgtk_image_view_set_entry (xe->image_view, entry);
		LOCAL_DEBUG_OUT (" entry ref_count = %d", entry->ref_count);
	}
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (xe->text_view));
	if (xe->entry) {
#if 1
		char *text = load_file (xe->entry->fullfilename);

		gtk_text_buffer_set_text (buffer, text, -1);
#endif
	} else {
		gtk_text_buffer_set_text (buffer, NULL, 0);
	}

	xe->dirty = False;
	gtk_widget_set_sensitive (xe->refresh_btn, FALSE);
	gtk_widget_set_sensitive (xe->save_btn, FALSE);

}

void
asgtk_xml_view_file_change_handler (ASGtkXMLView * xe,
																		_ASGtkXMLView_handler change_handler,
																		gpointer user_data)
{
	g_return_if_fail (ASGTK_IS_XML_VIEW (xe));

	xe->file_change_handler = change_handler;
	xe->file_change_user_data = user_data;
}

GtkWidget *asgtk_xml_editor_new ()
{
	ASGtkXMLEditor *xe;

	xe = g_object_new (ASGTK_TYPE_XML_EDITOR, NULL);

	xe->xml_view = ASGTK_XML_VIEW (asgtk_xml_view_new ());
	gtk_container_add (GTK_CONTAINER (xe), GTK_WIDGET (xe->xml_view));
	gtk_widget_show (GTK_WIDGET (xe->xml_view));

	LOCAL_DEBUG_OUT ("created image ASGtkXMLEditor object %p", xe);
	return GTK_WIDGET (xe);
}

void
asgtk_xml_editor_set_entry (ASGtkXMLEditor * xe,
														struct ASImageListEntry *entry)
{
	g_return_if_fail (ASGTK_IS_XML_EDITOR (xe));
	if (xe->xml_view)
		asgtk_xml_view_set_entry (xe->xml_view, entry);
}

void
asgtk_xml_editor_file_change_handler (ASGtkXMLEditor * xe,
																			_ASGtkXMLView_handler change_handler,
																			gpointer user_data)
{
	g_return_if_fail (ASGTK_IS_XML_EDITOR (xe));

	if (xe->xml_view)
		asgtk_xml_view_file_change_handler (xe->xml_view, change_handler,
																				user_data);

}
