/* Bluefish HTML Editor
 * document.c - this file contains the document code
 *
 * Copyright (C) 2000-2001 Olivier Sessink
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "default_include.h"

#include <sys/types.h>                          /* stat() */
#include <sys/stat.h>                           /* stat() */
#include <stdio.h>				/* fopen */
#include <string.h>				/*strchr, memset */
#include <stdlib.h>				/* atoi */
#include <unistd.h>				/* unlink(), stat() */
#include <dirent.h>				/* DIR struct, open_dir() */
#include <time.h>				/* time() */
#include <ctype.h> 	                        /* isspace() */

#include <gdk/gdkkeysyms.h>

#include "bluefish.h"
#include "bf_lib.h"
#include "document.h"
#include "callbacks.h"	/* set_last_insert */
#include "gtk_easy.h"			/* error dialog */
#include "interface.h"			/* statusbar message & file_and_dir_history_add*/
#ifdef UNDO2
#include "undo2.h"
#else
#include "undo.h"				/*undo_list_add */
#endif /* UNDO2 */
#include "highlight.h"			/*line_hi_cb */
#include "rpopup.h"				/* rpopup_eventh */
#include "stringlist.h"			/* free_stringlist */
#include "snr2.h"				/* find_filenames in test_link_management() */
#include "menu.h" 		/* add_to_recent_list() */
#include "char_table.h"

#ifdef PARSEDTD
#include "parsedtd.h"
#endif
#ifdef AUTOCOMPLET
#include "complet.h"
#endif
#ifdef ATTRPAGE
#include "attrpage.h"
#endif

#ifdef DEBUG
	#define DEBUG_SHOW_POS
#endif

/*******************************************************************/

typedef struct {
	GtkWidget *print_window;	/* the print dialog window */
	GtkWidget *print_cmd;		/* this will hold the command to be executed*/
	GtkWidget *temp_dir;		/* this holds the temporary directory, e.g. /tmp */
} Tprintwin;

int pos_idx;

static void setpos(gint position)
{
	DEBUG_MSG("setting position in current document to %d\n", position);
	gtk_editable_set_position(GTK_EDITABLE
							  (main_v->current_document->textbox),
							  position);
	gtk_timeout_remove(pos_idx);
}

/*******************************************************************/

/*******************************************************************/
/* set several document settings */

void document_set_fonts(gchar * textfontstring, gchar * labelfontstring,
						Tdocument * document)
{
	if (labelfontstring) {
		apply_font_style(document->textbox, textfontstring);
	}
	if (labelfontstring) {
		apply_font_style(document->tab_label, labelfontstring);
	}
}

void document_set_wrap(gint word_wrap, gint line_wrap,
					   Tdocument * document)
{
	gtk_text_set_line_wrap(GTK_TEXT(document->textbox), line_wrap);
	gtk_text_set_word_wrap(GTK_TEXT(document->textbox), word_wrap);
}

void document_force_def_style(Tdocument *doc, gint do_force) {
	DEBUG_MSG("document_force_def_style, started");
	if (do_force) {
		GtkStyle *style;
		style = gtk_style_copy (gtk_widget_get_style (GTK_WIDGET(doc->textbox)));
		gtk_widget_set_style (GTK_WIDGET(doc->textbox), style);
		gtk_style_unref (style);
		gtk_widget_ensure_style(GTK_WIDGET(doc->textbox));
	}
}

/*************************************************************************/

static void doc_set_undo_redo_widget_state(Tdocument *doc) {
		gint redo, undo;
		redo = doc_has_redo_list(doc);
		undo = doc_has_undo_list(doc);
		if (main_v->props.v_main_tb) {
			gtk_widget_set_sensitive(main_v->toolb.redo, redo);
			gtk_widget_set_sensitive(main_v->toolb.undo, undo);
		}
		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtk_item_factory_from_widget(main_v->menubar), N_("/Edit/Undo")), undo);
		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtk_item_factory_from_widget(main_v->menubar), N_("/Edit/Undo all")), undo);
		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtk_item_factory_from_widget(main_v->menubar), N_("/Edit/Redo")), redo);
		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtk_item_factory_from_widget(main_v->menubar), N_("/Edit/Redo all")), redo);
}

void doc_set_modified(Tdocument *doc, gint value) {
	if (doc->modified != value) {
		gchar *temp_string;
		doc->modified = value;
		if (doc->modified) {
			if (doc->filename) {
				temp_string = g_strconcat(g_basename(doc->filename), " *", NULL);			
			} else {
				temp_string = g_strdup(_("Untitled *"));			
			}
		} else {
			if (doc->filename) {
				temp_string = g_strdup(g_basename(doc->filename));
			} else {
				temp_string = g_strdup(_("Untitled"));
			}
		}
		gtk_label_set(GTK_LABEL(doc->tab_label),temp_string);
		g_free(temp_string);
	}
	/* only when this is the current document we have to change these */
	if (doc == main_v->current_document) {
		doc_set_undo_redo_widget_state(doc);
	}
}

void doc_bind_signals(Tdocument *doc) {
	DEBUG_MSG("doc_bind_signals, started\n");
	/* this part is copied from gnotepad code 
   Copyright (C) 1998 Andy C. Kahn <kahn@zk3.dec.com> */
	doc->ins_txt_id = gtk_signal_connect(GTK_OBJECT(doc->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), doc);
	doc->del_txt_id = gtk_signal_connect(GTK_OBJECT(doc->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), doc);

}
void doc_unbind_signals(Tdocument *doc) {
	DEBUG_MSG("doc_unbind_signals, started\n");
	gtk_signal_disconnect(GTK_OBJECT(doc->textbox), doc->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(doc->textbox), doc->del_txt_id);
}

inline static void doc_update_mtime(Tdocument *doc) {
	if (doc->filename) {
		struct stat statbuf;
		if (stat(doc->filename, &statbuf) == 0) {
			doc->mtime = statbuf.st_mtime;
		}
	} else {
		doc->mtime = 0;
	}
}

/* returns 1 if the time didn't change, returns 0 
if the file is modified by another process, returns
1 if there was no previous mtime information available */
static gint doc_check_mtime(Tdocument *doc) {
	if (doc->filename && 0 != doc->mtime) {
		struct stat statbuf;
		if (stat(doc->filename, &statbuf) == 0) {
			if (doc->mtime < statbuf.st_mtime) {
				DEBUG_MSG("doc_check_mtime, doc->mtime=%d, statbuf.st_mtime=%d\n", (int)doc->mtime, (int)statbuf.st_mtime);
				return 0;
			}
		}
	} 
	return 1;
}

/* doc_set_stat_info() includes setting the mtime field, so there is no need
to call doc_update_mtime() as well */
static void doc_set_stat_info(Tdocument *doc) {
	if (doc->filename) {
		struct stat statbuf;
		if (lstat(doc->filename, &statbuf) == 0) {
			if (S_ISLNK(statbuf.st_mode)) {
				doc->is_symlink = 1;
				stat(doc->filename, &statbuf);
			} else {
				doc->is_symlink = 0;
			}
			doc->mtime = statbuf.st_mtime;
			doc->owner_uid = statbuf.st_uid;
			doc->owner_gid = statbuf.st_gid;
			doc->mode = statbuf.st_mode;
		}
	}
}

void notebook_switched_to_doc(Tdocument *doc) {
	if (doc_check_mtime(doc) == 0) {
		gchar *tmpstr;
		gint retval;
		gchar *options[] = {N_("Reload"), N_("Ignore"), NULL};

		tmpstr = g_strdup_printf(_("File %s\nis modified by another process"), doc->filename);
		retval = multi_button_dialog(_("Bluefish: Warning, file is modified"), 0, tmpstr, options);
		g_free(tmpstr);
		if (retval == 1) {
			doc_set_stat_info(doc);
		} else {
			doc_reload(doc);
		}
	}
	doc_set_undo_redo_widget_state(doc);
	DEBUG_MSG("notebook_switched_to_doc, grabbing document %p textbox=%p\n", doc, doc->textbox);

#ifdef AUTOCOMPLET
	hide_autocompletion_window();
#endif

	gtk_widget_grab_focus(GTK_WIDGET(doc->textbox));
}

inline static gboolean doc_highlight_line_once(Tdocument * whichdoc)
{
	DEBUG_MSG("highlight_line_once, need_highlighting=%d\n",
			  whichdoc->need_highlighting);
	whichdoc->need_highlighting = 0;
	line_hi_cb(NULL, whichdoc);
	return FALSE;
}

inline static gboolean doc_highlight_once(Tdocument * whichdoc)
{
	DEBUG_MSG("doc_highlight_once, need_highlighting=%d\n",
			  whichdoc->need_highlighting);
	whichdoc->need_highlighting = 0;
	refresh_hi_cb(NULL, whichdoc);
	return FALSE;
}

void doc_need_highlighting(Tdocument * whichdoc)
{
	DEBUG_MSG
		("doc_need_highlighting, need_highlighting=%d, cont_highlight_full=%d\n",
		 whichdoc->need_highlighting, main_v->props.cont_highlight_full);
	if (whichdoc->highlightstate && main_v->props.cont_highlight_update) {
		if (main_v->props.cont_highlight_full) {
			gtk_timeout_add(100, (GtkFunction) doc_highlight_once,
							whichdoc);
		} else {
			gtk_timeout_add(100, (GtkFunction) doc_highlight_line_once,
							whichdoc);
		}
	}
}


/*
 * Function: doc_save_selection
 * Arguments:
 * 	void
 * Return value:
 * 	void
 * Description:
 * 	save current selection status.
 */
void doc_save_selection(Tdocument *doc)
{
	GtkEditable *editable = GTK_EDITABLE(doc->textbox);
	doc->saved_sel.start_pos = editable->selection_start_pos;
	doc->saved_sel.end_pos =  editable->selection_end_pos;
	doc->saved_sel.has_selection =  editable->has_selection;
	doc->saved_sel.saved = TRUE;
#ifdef DEBUG_SHOW_POS
	g_print("doc_save_selection, startsel=%d, endsel=%d, hassel=%d\n", doc->saved_sel.start_pos, doc->saved_sel.end_pos, doc->saved_sel.has_selection);
#endif
}


/*
 * Function: doc_restore_selection
 * Arguments:
 * 	void
 * Return value:
 * 	void
 * Description:
 * 	restore saved selection status.
 */
void doc_restore_selection(Tdocument *doc) {
	if (doc->saved_sel.saved) {
		DEBUG_MSG("doc_restore_selection, restoring!!\n");
		if (doc->saved_sel.has_selection) {
			gtk_editable_select_region(GTK_EDITABLE(doc->textbox), doc->saved_sel.start_pos, doc->saved_sel.end_pos);
		}
		doc->saved_sel.saved = FALSE;
	}
#ifdef DEBUG_SHOW_POS
	g_print("doc_restore_selection, startsel=%d, endsel=%d, hassel=%d\n", doc->saved_sel.start_pos, doc->saved_sel.end_pos, doc->saved_sel.has_selection);
#endif
}

/************************************************************************/
/* gint find_filename_in_documentlist(gchar *filename)
 * returns -1 if the file is not open, else returns the index where
 * the file is in the documentlist
 */
gint find_filename_in_documentlist(gchar *filename) {
	GList *tmplist;
	gint count=0;

	if (!filename) {
		return -1;
	}
	
	tmplist = g_list_first(main_v->documentlist);
	while (tmplist) {
		DEBUG_MSG("test_doc_already_open, tmplist=%p\n", tmplist);
		if (((Tdocument *)tmplist->data)->filename &&(strcmp(filename, ((Tdocument *)tmplist->data)->filename) ==0)) {
			return count;
		}
		count++;
		tmplist = g_list_next(tmplist);
	}
	return -1;
}

gint test_docs_modified(GList *doclist) {

	GList *tmplist;
	Tdocument *tmpdoc;

	if (doclist) {
		tmplist = g_list_first(doclist);
	} else {
		tmplist = g_list_first(main_v->documentlist);
	}
	
	while (tmplist) {
		tmpdoc = (Tdocument *) tmplist->data;
#ifdef DEBUG
		g_assert(tmpdoc);
#endif
		if (tmpdoc->modified) {
			DEBUG_MSG("test_docs_modified, found a modified document\n");
			return 1;
		}
		tmplist = g_list_next(tmplist);
	}
	DEBUG_MSG("test_docs_modified, no modified documents found\n");
	return 0;
}

gint test_only_empty_doc_left(Tdocument *doc) {
	Tdocument *tmpdoc;

	if (!doc) {
		tmpdoc = main_v->current_document;
	} else {
		tmpdoc = doc;
	}

	if (!tmpdoc->modified && !tmpdoc->filename
				&& (gtk_text_get_length(GTK_TEXT(tmpdoc->textbox)) == 0)
				&& (g_list_length(main_v->documentlist) == 1)) {
			DEBUG_MSG("test_only_empty_doc_left, returning 1\n");
			return 1;
	}
	DEBUG_MSG("test_only_empty_doc_left, returning 0\n");
	return 0;
}


/*
 * PUBLIC: doc_insert_text_cb, some parts copied from doc.c in the gnotepad code
 *
 * callback for text widget for text insertion from keyboard typing.  note that
 * this routine and the next one (doc_delete_text_cb()), gets invoked for EVERY
 * key typed by the user.
 * also cotains some highlighting related stuff and an autoindenter.
 */
void doc_insert_text_cb(GtkEditable * editable, const char *text, int len, int
						*pos, gpointer d)
{
	char *buf, space;
        const char update_chars[] = " <>'\"/";
	gint newlines = 0, position, count, touch = 0, max;
	gint chlen;

	if (main_v->props.cont_highlight_update) {
             if (strchr (update_chars, text[0])) {
                  doc_need_highlighting(main_v->current_document);
             }
	}
	/* does this introduce a memory leak? --> fixed, Olivier */
	buf = g_strndup(text, len);
#ifdef DEBUG
	g_print("doc_insert_text_cb, called for '%s'\n", buf);
#endif 

	/* we need the current position several times ;) */
	position =
		gtk_editable_get_position(GTK_EDITABLE
								  (main_v->current_document->textbox));
	count = position;
	max = position;

	if ((main_v->props.autoindent == 1) && (text[0] == '\n')) {
		/* don't count that newline you just entered  */
		position--;

		/* go back till you find newlines. count the chars in max, we'll need it later */
		while ((newlines < 1) && (position > 0)) {
			if (GTK_TEXT_INDEX
				(GTK_TEXT(main_v->current_document->textbox),
				 position) == '\n') {
				newlines++;
			}
			position--;
			max++;
		}
		/* one for the last go and one cause we need the char after the newline */
		position = position + 2;


		space =
			GTK_TEXT_INDEX(GTK_TEXT(main_v->current_document->textbox),
						   position);

		/* insert the stuff - don't do more than the max we counted before - this is because if space=='\n', this
		 * statement is true. don't ask me why. 
		 */
		while (((space == '\t') || (space == ' ')) && (count < max - 1)) {
			gtk_text_insert(GTK_TEXT(main_v->current_document->textbox),
							NULL, NULL, NULL, &space, 1);
			position++;
			space =
				GTK_TEXT_INDEX(GTK_TEXT(main_v->current_document->textbox),
							   position);
			count++;
			touch = 1;
		}

		if (touch == 1) {
			count++;

			/* this is real ugly. it seems to be a bug in gtk, you cannot set the position when
			 * you just entered a newline. so i build a workaround, with gtk_timeout_add, as you can see.
			 */
			pos_idx =	gtk_timeout_add(10, (GtkFunction) setpos,	GINT_TO_POINTER(count));
		}
	}

#ifdef UNDO2 
	DEBUG_MSG("doc_insert_text_cb, len=%d, buf[0]='%c', *pos=%d\n", len, buf[0], *pos);

	if ((len == 1 && (buf[0] == ' ' || buf[0] == '\n' || buf[0] == '\t')) || !doc_undo_op_compare(d,UndoInsert)) {
		DEBUG_MSG("doc_insert_text_cb, need a new undogroup\n");
		doc_unre_new_group(d);
	}
	
#endif /* UNDO2 */
	if (GTK_TEXT(((Tdocument*)d)->textbox)->use_wchar && len > 1) {
		chlen = wchar_len(buf, len);
	} else {
		chlen = len;
	}
	undo_list_add(d, buf, *pos, *pos + chlen, UndoInsert);

	g_free(buf);	
	doc_set_modified(main_v->current_document, 1);

#ifdef AUTOCOMPLET
	show_autocompletion_window(*(text+len-1));  /* Look for the last letter inserted */
#endif

#ifdef ATTRPAGE
	doc_attr_page_refresh(main_v->current_document, 1);
#endif

	DEBUG_MSG
		("doc_insert_text_cb, finished, modified = 1, need_highlighting=%d\n",
		 main_v->current_document->need_highlighting);
}


								/* doc_insert_text_cb */


/*
 * PUBLIC: doc_delete_text_cb, copied from doc.c in the gnotepad code
 *
 * callback for text widget for text deletion from keyboard typing
 */
void doc_delete_text_cb(GtkEditable * editable, int start, int end,
						gpointer d)
{
	char *buf;
	gint len = abs(end - start);
	DEBUG_MSG
		("doc_delete_text_cb, started, editable=%p, start=%d, end=%d, document=%p\n",
		 editable, start, end, d);
	buf = gtk_editable_get_chars(editable, start, end);
	DEBUG_MSG("doc_delete_text_cb, buf=%p, document->textbox=%p\n", buf,
			  ((Tdocument *) d)->textbox);
#ifdef UNDO2
	if ((len == 1 && (buf[0] == ' ' || buf[0] == '\n' || buf[0] == '\t')) || !doc_undo_op_compare(d,UndoDelete)) {
		DEBUG_MSG("doc_delete_text_cb, need a new undogroup\n");
		doc_unre_new_group(d);
	}
#endif /* UNDO2 */
	undo_list_add(d, buf, start, end, UndoDelete);
	doc_set_modified((Tdocument *) d, 1);
	/* fixed a memory leak here! */
	g_free(buf);

#ifdef AUTOCOMPLET
	hide_autocompletion_window();
#endif
#ifdef ATTRPAGE
	doc_attr_page_refresh(main_v->current_document, 1);
#endif

	DEBUG_MSG("doc_delete_text_cb, finished, modified=1\n");
}								/* doc_delete_text_cb */

Ttext_positions get_positions(GtkWidget * textbox) {
	Ttext_positions positions;

	DEBUG_MSG("get_positions, started on textbox=%p\n",textbox );
	positions.currentp = gtk_editable_get_position(GTK_EDITABLE(textbox));
	positions.adj = GTK_TEXT(textbox)->vadj->value;
	positions.selection = GTK_EDITABLE(textbox)->has_selection;
	if (positions.selection) {
		positions.startp = GTK_EDITABLE(textbox)->selection_start_pos;
		positions.endp = GTK_EDITABLE(textbox)->selection_end_pos;
		if (positions.endp < positions.startp) {
			gint tmp;
			tmp = positions.endp;
			positions.endp = positions.startp;
			positions.startp = tmp;
		}
	} else {
		positions.endp = positions.currentp;
		positions.startp = positions.currentp;
	}
#ifdef DEBUG_SHOW_POS
	g_print("get_positions, currentp=%d, startp=%d, endp=%d, hassel=%d\n", positions.currentp, positions.startp,positions.endp,positions.selection);
#endif
	return positions;
}

void restore_positions(Ttext_positions positions, GtkWidget * textbox)
{

	gtk_editable_set_position(GTK_EDITABLE(textbox), positions.currentp);
	gtk_adjustment_set_value(GTK_TEXT(textbox)->vadj, positions.adj);
	if (positions.selection) {
		DEBUG_MSG("restore_positions, startp=%d, endp=%d\n",
				  positions.startp, positions.endp);
		gtk_editable_select_region(GTK_EDITABLE(textbox), positions.startp,
								   positions.endp);
	}
#ifdef DEBUG_SHOW_POS
	g_print("restore_positions, currentp=%d, startp=%d, endp=%d, hassel=%d\n", positions.currentp, positions.startp,positions.endp,positions.selection);
#endif
}

/* #define USE_EDITABLE_FUNC */

void doc_replace_text_backend(const gchar * newstring, gint pos, gint end,
					  Tdocument * doc) {
	gchar *buf;
#ifdef USE_EDITABLE_FUNC
	gint beforepos = pos;
#endif
	doc_unbind_signals(doc);
	gtk_text_freeze(GTK_TEXT(doc->textbox));
	DEBUG_MSG("doc_replace_text, started, pos=%d, end=%d, newstring=%s, strlen(newstring)=%d\n",		 pos, end, newstring, strlen(newstring));
	buf = gtk_editable_get_chars(GTK_EDITABLE(GTK_TEXT(doc->textbox)), pos, end);
#ifdef USE_EDITABLE_FUNC
	gtk_editable_delete_text(GTK_EDITABLE(GTK_TEXT(doc->textbox)), pos, end);
#else
	gtk_text_set_point(GTK_TEXT(doc->textbox), pos);
	gtk_text_forward_delete(GTK_TEXT(doc->textbox), end - pos);
#endif
	undo_list_add(doc, buf, pos, end, UndoDelete);
	g_free(buf);
	DEBUG_MSG("doc_replace_text, text deleted from %d to %d\n", pos, end);
#ifdef USE_EDITABLE_FUNC
	gtk_editable_insert_text(GTK_EDITABLE(GTK_TEXT(doc->textbox)),
				newstring, strlen(newstring), &pos);
	DEBUG_MSG("doc_replace_text, text inserted, pos=%d, beforepos=%d, end=%d\n", pos, beforepos, end);
	undo_list_add(doc, newstring, beforepos, pos, UndoInsert);
#else
	gtk_text_set_point(GTK_TEXT(doc->textbox), pos);
	gtk_text_insert(GTK_TEXT(doc->textbox), NULL, NULL, NULL,newstring, strlen(newstring));
	undo_list_add(doc, newstring, pos, pos+strlen(newstring), UndoInsert);
#endif
	gtk_text_thaw(GTK_TEXT(doc->textbox));
	doc_bind_signals(doc);
	doc_set_modified(doc, 1);
}					  

void doc_replace_text(const gchar * newstring, gint pos, gint end,
					  Tdocument * doc) {

	doc_unre_new_group(doc);

	doc_replace_text_backend(newstring, pos, end, doc);
	
	doc_set_modified(doc, 1);
	doc_unre_new_group(doc);
	doc_need_highlighting(doc);
}

void replace_text(const gchar * newstring, gint pos, gint end)
{

	DEBUG_MSG
		("replace_text, calling doc_replace_text on current_document\n");
	doc_replace_text(newstring, pos, end, main_v->current_document);

}

void doc_insert_dbl_text(Tdocument * document, const gchar * before,
					 const gchar * after)
{
	guint pos;
	Ttext_positions positions;

	DEBUG_MSG("doc_insert_dbl_text, document=%p, current_document=%p, before=%p, after=%p\n", document, main_v->current_document, before, after);
	if (!before && !after)
		return;

	if (!document && main_v->current_document) {
		document = main_v->current_document;
	} else if (!document && !main_v->current_document) {
		return;
	}

	set_last_insert(before, after);

	doc_restore_selection(document);
	/* find the current cursor position and selection position */
	positions = get_positions(document->textbox);

/* freeze the textbox, this is better when inserting large blocks */
	gtk_text_freeze(GTK_TEXT(document->textbox));
#ifdef DEBUG_SHOW_POS
	g_print("insert_dbl_text, currentp = %d, startp=%d, endp=%d\n",
			  positions.currentp, positions.startp, positions.endp);
#endif
	doc_unre_new_group(document);
	if (after != NULL && strlen(after) > 0) {
		gtk_text_set_point(GTK_TEXT(document->textbox), positions.endp);
		gtk_text_insert(GTK_TEXT(document->textbox), NULL, NULL, NULL,
						after, -1);
		/* strlen() is unable to find the lenght of multibyte characters (japanese)
		   so we use the new position in the text box to find out */
		pos = gtk_text_get_point(GTK_TEXT(document->textbox));
		undo_list_add(document, g_strdup(after), positions.endp, pos,
					  UndoInsert);
	} else {
		pos = positions.endp;
	}

	if (before != NULL && strlen(before) > 0) {
		gtk_text_set_point(GTK_TEXT(document->textbox), positions.startp);
		gtk_text_insert(GTK_TEXT(document->textbox), NULL, NULL, NULL,
						before, -1);
		pos = gtk_text_get_point(GTK_TEXT(document->textbox));
		undo_list_add(document, g_strdup(before), positions.startp, pos,
					  UndoInsert);
	} else {
		pos = positions.startp;
	}
	
/* thaw textbox, we're finished */
	DEBUG_MSG("insert_dbl_text, ->thaw\n");
	gtk_text_thaw(GTK_TEXT(document->textbox));
	
	doc_set_modified(document, 1);
	doc_unre_new_group(document);
	
	positions.endp += pos - positions.startp;
	positions.currentp += pos - positions.startp;
	positions.startp = pos;
	restore_positions(positions, document->textbox);

	/* grab focus so we can continue working */
	DEBUG_MSG("insert_dbl_text, about to grab focus\n");
	gtk_widget_grab_focus(GTK_WIDGET(document->textbox));
	DEBUG_MSG("insert_dbl_text, set modified=1\n");
	
	doc_need_highlighting(document);
}

inline void insert_dbl_text(const gchar * before, const gchar * after)
{
	doc_insert_dbl_text(main_v->current_document, before, after);
}

void doc_insert_text(Tdocument *document, const gchar * newstring) {
	gint befopos, aftpos;
	Ttext_positions positions;
	
	if (!newstring || !document) {
		return;
	}

	doc_restore_selection(document);
	/* find the current cursor position and selection position */
	positions = get_positions(document->textbox);
#ifdef DEBUG_SHOW_POS
	g_print("doc_insert_text, currentp = %d, startp=%d, endp=%d\n",
			  positions.currentp, positions.startp, positions.endp);
#endif
	doc_unre_new_group(document);
	gtk_text_set_point(GTK_TEXT(document->textbox), positions.currentp);

	befopos = gtk_text_get_point(GTK_TEXT(document->textbox));
	gtk_text_insert(GTK_TEXT(document->textbox), NULL, NULL, NULL, newstring, -1);
	/* strlen() is unable to find the lenght of multibyte characters (japanese)
	   so we use the new position in the text box to find out */
	aftpos = gtk_text_get_point(GTK_TEXT(document->textbox));
	/* why call unbdo_list_add, isn't this called by the insert callback ???? */
	DEBUG_MSG("doc_insert_text, manually calling undo_list_add for '%s'\n", newstring);
	undo_list_add(document, newstring, befopos, aftpos, UndoInsert);
	doc_unre_new_group(document);

}

inline void insert_text(const gchar * newstring) {
	DEBUG_MSG("insert_text, started\n");
	doc_insert_text(main_v->current_document, newstring);
}


static void scroll_textbox(gint direction_up)
{
	gfloat newvalue;

	newvalue =
		GTK_ADJUSTMENT(GTK_TEXT(main_v->current_document->textbox)->vadj)->
		value;
	if (direction_up) {
		newvalue +=
			(GTK_ADJUSTMENT
			 (GTK_TEXT(main_v->current_document->textbox)->vadj)->
			 page_increment / 2);
	} else {
		newvalue -=
			(GTK_ADJUSTMENT
			 (GTK_TEXT(main_v->current_document->textbox)->vadj)->
			 page_increment / 2);
	}
	gtk_adjustment_set_value(GTK_ADJUSTMENT
							 (GTK_TEXT(main_v->current_document->textbox)->
							  vadj), newvalue);
#ifdef AUTOCOMPLET
	hide_autocompletion_window();
#endif

}

static gint textbox_event_lcb(GtkWidget * widget, GdkEvent * event)
{
	if (event->type == GDK_BUTTON_PRESS) {
		GdkEventButton *bevent = (GdkEventButton *) event;
#ifdef AUTOCOMPLET
	        hide_autocompletion_window();
#endif
		DEBUG_MSG("rpopup_eventh, bevent->button=%d\n", bevent->button);
		/* on a mouseclick we end the undo group and start a new one */
		doc_unre_new_group(main_v->current_document);
		if (bevent->button == 3) {
			rpopup_eventh(bevent);
			return TRUE;
		} else if (bevent->button == 4) {
			scroll_textbox(0);
			return TRUE;
		} else if (bevent->button == 5) {
			scroll_textbox(1);
			return TRUE;
		}
	}
	else if (event->type == GDK_KEY_PRESS) {
		GdkEventKey *kevent = (GdkEventKey *) event;

#ifdef AUTOCOMPLET
	        hide_autocompletion_window();
#endif

		/*DEBUG_MSG("key pressed : keyval(0x%04x)  state(0x%04x)\n", kevent->keyval, kevent->state);*/
		switch(kevent->keyval) {
		case GDK_Return:
			if (main_v->props.conv_shift_enter
			    && kevent->state & GDK_SHIFT_MASK
			    && main_v->props.shift_enter_text) {
				insert_text(main_v->props.shift_enter_text);
				return TRUE;
			}
			if (main_v->props.conv_ctrl_enter
			    && kevent->state & GDK_CONTROL_MASK
			    && main_v->props.ctrl_enter_text) {
				insert_text(main_v->props.ctrl_enter_text);
				return TRUE;
			}
			break;
		case GDK_less:
			if (main_v->props.conv_special_char
			    && kevent->state & GDK_CONTROL_MASK) {
				insert_text("&lt;");
				return TRUE;
			}
			break;
		case GDK_greater:
			if (main_v->props.conv_special_char
			    && kevent->state & GDK_CONTROL_MASK) {
				insert_text("&gt;");
				return TRUE;
			}
			break;
		case GDK_ampersand:
			if (main_v->props.conv_special_char
			    && kevent->state & GDK_CONTROL_MASK) {
				insert_text("&amp;");
				return TRUE;
			}
			break;
		case GDK_quotedbl:
			if (main_v->props.conv_special_char
			    && kevent->state & GDK_CONTROL_MASK) {
				insert_text("&quot;");
				return TRUE;
			}
			break;
		case GDK_Home:
		case GDK_Left:
		case GDK_Right:
		case GDK_Up:
		case GDK_Down:
		case GDK_Page_Up:
		case GDK_Page_Down:
		case GDK_End:
		case GDK_Begin:
			doc_unre_new_group(main_v->current_document);
			break;
		}
	}
#ifdef ATTRPAGE
	else if (event->type == GDK_BUTTON_RELEASE) {
		doc_attr_page_refresh(main_v->current_document, 0);
	} else if (event->type == GDK_KEY_RELEASE) {
	  GdkEventKey *kevent = (GdkEventKey *) event;
	  DEBUG_MSG("Key_release event");
	  switch(kevent->keyval) {
	  case GDK_Home:
	  case GDK_Left:
	  case GDK_Right:
	  case GDK_Up:
	  case GDK_Down:
	  case GDK_Page_Up:
	  case GDK_Page_Down:
	  case GDK_End:
	  case GDK_Begin:
	    doc_attr_page_refresh(main_v->current_document, 0);
	    break;
	  }
	}
#endif

	/* Tell calling code that we have not handled this event; pass it on. */
	return FALSE;
}

gint doc_xy_to_cursor_pos(Tdocument *doc, gint x, gint y) {
	/* original function Copyright (C) David A Knight */
	/* changes (C) Olivier Sessink                */
	gchar *text;
	gchar *line = NULL;
	gint i;
	gint lineWidth = 0;
	gint editorWidth;
	gint height;
	gint start = 0;
	gint charWidth=0;
	/*	GdkFont *font = gdk_fontset_load(main_v->props.cfg_editor_font); */
	GdkFont *font = doc->textbox->style->font;
	GtkText *e = GTK_TEXT(doc->textbox);
	const int line_wrap_room = 8;	/* 8 == LINE_WRAP_ROOM in gtktext.c */
	gboolean wrapped = FALSE;
	gint previ;

	DEBUG_MSG("xy_to_cursor_pos, started, e=%p, doc=%p\n", e, doc);
	DEBUG_MSG("xy_to_cursor_pos, x=%d, y=%d\n", (gint) x, (gint) y);
	gdk_window_get_size(e->text_area, &editorWidth, NULL);
	DEBUG_MSG("xy_to_cursor_pos, editorWidth=%d, line_wrap_room=%d\n", editorWidth, line_wrap_room);
	editorWidth -= line_wrap_room;
	DEBUG_MSG("xy_to_cursor_pos, editorWidth=%d\n", editorWidth);
	text = gtk_editable_get_chars(GTK_EDITABLE(e), 0, -1);
	DEBUG_MSG("xy_to_cursor_pos, strlen(text)=%d, font=%p\n", strlen(text), font);
	height = font->ascent + font->descent;
	DEBUG_MSG("xy_to_cursor_pos, height=%d, ascent=%d, descent=%d\n", height, font->ascent, font->descent);
	y += e->first_cut_pixels;
	DEBUG_MSG("xy_to_cursor_pos, e->first_line_start_index=%d, strlen(text)=%d\n", e->first_line_start_index, strlen(text));
	for (i = e->first_line_start_index; i < strlen(text); i++) {
		if (text[i] != '\n') {
			charWidth = gdk_char_width(font, text[i]);
			if (text[i] == '\t') {
				charWidth = gdk_char_width(font, ' ');
				charWidth *= main_v->props.cfg_editor_tabwidth;
			}
			lineWidth += charWidth;
		}
		DEBUG_MSG("xy_to_cursor_pos, text[%d]=%c, lineWidth=%d, charWidth=%d \n", i, text[i], lineWidth, charWidth);
		if ((text[i] == '\n') || (lineWidth > editorWidth)) {
			lineWidth = 0;
			if ((y -= height) <= 0) {
				DEBUG_MSG("y <= 0 --> break\n");
				break;
			}
			wrapped = (text[i] != '\n');
			previ = i;
			while (!isspace((int)text[i]))
				i--;
			if (i == start && wrapped)
				i = previ;
			start = i;
		}
	}
	line = g_strndup(&text[start], i - start);
	DEBUG_MSG("xy_to_cursor_pos, line=%s, y=%d, x=%d\n", line, y, x);
	/* wasn't inside the text area */
/*	
I (Olivier) commented this if out since it seems the x position isn't calculated 
well if you skip this. Hopefully this will work better
if (y <= 0) { */
		for (start = 0; x > 0; start++) {
			charWidth = gdk_char_width(font, line[start]);
			if (line[start] == '\t') {
				charWidth = gdk_char_width(font, ' ');
				charWidth *= main_v->props.cfg_editor_tabwidth;
			}
			x -= charWidth;
			DEBUG_MSG("xy_to_cursor_pos, substracted char %c (width=%d), so x=%d\n", line[start], charWidth, x);
		}
		if ((start = (strlen(line) - start)) > 0)
			i -= start;
/*	} */

	g_free(line);
	g_free(text);
	DEBUG_MSG("xy_to_cursor_pos, return %d\n", i);
	return i;
}

/* static void scrollbar_update(GtkText * txt, gint tl, gint ln)
{
	gfloat value;

	if (tl < 3 || ln > tl)
		return;

	value = (ln * GTK_ADJUSTMENT(txt->vadj)->upper) / tl - GTK_ADJUSTMENT(txt->vadj)->page_increment;

	gtk_adjustment_set_value(GTK_ADJUSTMENT(txt->vadj), value);
} */

gint position_to_linenum(gint position)
{
	gint i, lines;
	gchar *c;					/* = g_malloc0(3); */

	lines = 0;
	i = gtk_text_get_length(GTK_TEXT(main_v->current_document->textbox));
	if (position > i) {
		position = i;
	}
	c =
		gtk_editable_get_chars(GTK_EDITABLE
							   (main_v->current_document->textbox), 0,
							   position);
	if (!c)
		return 0;
	DEBUG_MSG("position_to_linenum, strlen(c)=%d, position=%d\n",
			  strlen(c), position);
	/* for multibyte-text support, don't use 'position' */
	for (i = 0; c[i]; i++) {
		if (c[i] == '\n')
			lines++;
	}
	g_free(c);
	return lines;
}

void go_to_line(gint linenum, gint select_line)
{
/* Thanks to Andy Kahn for the amazing non-leaking go to line function
   It dances, it does tricks, AND you have memory left after... :) */

	gchar *buf, *haystack, *needle;
	gint numlines;
	gint a, b, len;

	DEBUG_MSG("doc_go_to_line, started, linenum=%d, select_line=%d\n",
			  linenum, select_line);
	numlines = 1;

	len = gtk_text_get_length(GTK_TEXT(main_v->current_document->textbox));

/*  If index is past the end, or 0 (Usually because line number is -) */

	if ((linenum > (len - 1)) || (linenum == 0))
		return;

	a = 0;
	b = len;
	if (GTK_TEXT(main_v->current_document->textbox)->use_wchar) {
		int i;
		GtkText *text = GTK_TEXT(main_v->current_document->textbox);
		for (i = 0; i < len; i++) {
			if ((int) GTK_TEXT_INDEX(text, i) == '\n') {
				if (linenum == numlines) {
					b = i;
					break;
				}
				numlines++;
				if (linenum == numlines)
					a = i + 1;
			}
		}
	} else {
		buf =
			gtk_editable_get_chars(GTK_EDITABLE
								   (main_v->current_document->textbox), 0,
								   len);
		haystack = buf;
		do {
			needle = strchr(haystack, '\n');
			if (needle) {
				haystack = needle + 1;
				if (linenum == numlines) {
					b = needle - buf;
					break;
				}
				numlines++;
				if (linenum == numlines)
					a = needle - buf + 1;
			}
		}
		while (needle != NULL);

		g_free(buf);
	}
	DEBUG_MSG("doc_go_to_line, started, a=%d, b=%d\n", a, b);

	if (select_line) {
		gtk_editable_select_region(GTK_EDITABLE
								   (main_v->current_document->textbox), a,
								   b);
	}

/* this oneliner does the trick as well :) */

	gtk_editable_set_position(GTK_EDITABLE
							  (GTK_TEXT
							   (main_v->current_document->textbox)), b);


/*	DEBUG_MSG("doc_go_to_line, going to edit/delete a char to position the widget\n");	
	gtk_signal_disconnect(GTK_OBJECT(main_v->current_document->textbox), main_v->current_document->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(main_v->current_document->textbox), main_v->current_document->del_txt_id); 

	gtk_editable_insert_text(GTK_EDITABLE((GtkText *)
										  main_v->current_document->textbox), " ", 1, &b);
	gtk_editable_delete_text(GTK_EDITABLE((GtkText *)
										  main_v->current_document->textbox), b - 1, b);

	main_v->current_document->ins_txt_id = gtk_signal_connect(GTK_OBJECT(main_v->current_document->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), main_v->current_document);
	main_v->current_document->del_txt_id = gtk_signal_connect(GTK_OBJECT(main_v->current_document->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), main_v->current_document);
*/
}


/*****************************************************/

void copy_cb(GtkWidget * w, gpointer data)
{
	gtk_editable_copy_clipboard(GTK_EDITABLE
								(main_v->current_document->textbox));
}

/*****************************************************/

void paste_cb(GtkWidget * w, gpointer data)
{
	gtk_editable_paste_clipboard(GTK_EDITABLE
								 (main_v->current_document->textbox));
	doc_set_modified(main_v->current_document, 1);
	if (main_v->current_document->highlightstate) {
		doc_need_highlighting(main_v->current_document);
	}
}


/*****************************************************/

void cut_cb(GtkWidget * w, gpointer data)
{
	gtk_editable_cut_clipboard(GTK_EDITABLE
							   (main_v->current_document->textbox));
	doc_set_modified(main_v->current_document, 1);
	if (main_v->current_document->highlightstate) {
		doc_need_highlighting(main_v->current_document);
	}
}

/*****************************************************/

void sel_all_cb(GtkWidget * w, gpointer data)
{
	gtk_editable_select_region(GTK_EDITABLE
							   (main_v->current_document->textbox), 0,
							   gtk_text_get_length(GTK_TEXT
												   (main_v->
													current_document->
													textbox)));
}

/*****************************************************/

static void doc_selection_case(Tdocument *doc, gint to_uppercase) {
	gchar *changecase;
	gint startpos, endpos;

	if (!GTK_EDITABLE(doc->textbox)->has_selection) {
		return;
	}
	startpos = (GTK_EDITABLE(doc->textbox)->selection_start_pos 
			< GTK_EDITABLE(doc->textbox)->selection_end_pos) 
			? GTK_EDITABLE(doc->textbox)->selection_start_pos 
			: GTK_EDITABLE(doc->textbox)->selection_end_pos;
	endpos = (GTK_EDITABLE(doc->textbox)->selection_start_pos 
			> GTK_EDITABLE(doc->textbox)->selection_end_pos) 
			? GTK_EDITABLE(doc->textbox)->selection_start_pos 
			: GTK_EDITABLE(doc->textbox)->selection_end_pos;
	changecase = gtk_editable_get_chars(GTK_EDITABLE(doc->textbox), startpos, endpos);
	if (!changecase) {
		return;
	}

	if (to_uppercase) {
		g_strup(changecase);
	} else {
		g_strdown(changecase);
	}	
	doc_replace_text(changecase, startpos, endpos, doc);
}

void selection_to_uppercase_cb(GtkWidget * w, gpointer data) {
	doc_selection_case(main_v->current_document, 1);
}

void selection_to_lowercase_cb(GtkWidget * w, gpointer data) {
	doc_selection_case(main_v->current_document, 0);
}


/*****************************************************/
/* Region- Indenting */

void indent_base_cb (GtkWidget *thetextbox, gint start, gint end, gint type) {
  /* type 0=indent, 1 = unindent*/
  gint poscounter;
  const char indent_string[]="\t";

  gtk_text_freeze (GTK_TEXT (thetextbox));

  /* get the first pos */
  DEBUG_MSG ("indent_region_cb: start=%d, end=%d\n", start, end);

  /* seek the \n */
  poscounter = start;

  while ((GTK_TEXT_INDEX (GTK_TEXT (thetextbox), poscounter) != '\n')
	 && (poscounter > 0)) {
    DEBUG_MSG ("Seeking \\n\n");
    poscounter--;
  }
  DEBUG_MSG ("Got \\n at %d\n", poscounter);
  if (poscounter < 1) return;  

  DEBUG_MSG ("Inserting %s at %d\n", indent_string, poscounter);


  /* loop with insert.
     This should be done via replace_doc_multiple, but it doesn't
     work yet.
   */
  while (poscounter < end) {
    if (GTK_TEXT_INDEX (GTK_TEXT (thetextbox), poscounter) == '\n') { 
      poscounter++;
      if (type == 0) {
	gtk_text_set_point (GTK_TEXT (thetextbox), poscounter);
	gtk_text_insert (GTK_TEXT (thetextbox),
			 NULL, NULL, NULL, indent_string, -1);
      } else if (type == 1) {
	if (GTK_TEXT_INDEX (GTK_TEXT (thetextbox), poscounter) == '\t') { 
	  gtk_text_set_point (GTK_TEXT (thetextbox), poscounter);
	  gtk_text_forward_delete (GTK_TEXT (thetextbox), 1);
	}
      }
    }
    poscounter++;
  }
  gtk_text_thaw (GTK_TEXT (thetextbox));

}


void indent_region_cb (GtkWidget *widget, gpointer data) {
  indent_base_cb (main_v->current_document->textbox,
		  GTK_EDITABLE (main_v->current_document->textbox)->selection_start_pos,
		  GTK_EDITABLE (main_v->current_document->textbox)->selection_end_pos,
		  0);
}



void unindent_region_cb (GtkWidget *widget, gpointer data) {
  indent_base_cb (main_v->current_document->textbox,
		  GTK_EDITABLE (main_v->current_document->textbox)->selection_start_pos,
		  GTK_EDITABLE (main_v->current_document->textbox)->selection_end_pos,
		  1);
}



/*****************************************************/

void all_documents_update_links(Tdocument * curdoc, gchar * oldfilename,
								gchar * newfilename)
{
	gchar *eff_newfilename, *eff_oldfilename;
	GList *tmplist;
	gint count =
		bf_statusbar_message(_("Updating Links in all Documents"));

	eff_newfilename = most_efficient_filename(g_strdup(newfilename));
	eff_oldfilename = most_efficient_filename(g_strdup(oldfilename));
	tmplist = g_list_first(main_v->documentlist);
	while (tmplist) {
		DEBUG_MSG("all_documents_update_links, doc=%p\n", tmplist->data);
		if ((Tdocument *) tmplist->data != curdoc) {
			DEBUG_MSG("all_documents_update_links, updating!\n");
			update_filenames_in_file((Tdocument *) tmplist->data,
									 eff_oldfilename, eff_newfilename, 0);
		}
		tmplist = g_list_next(tmplist);
	}
	g_free(eff_newfilename);
	g_free(eff_oldfilename);
	statusbar_remove(GINT_TO_POINTER(count));
}

/**********************************************/

typedef struct {
	GtkWidget *win;
	GtkWidget *entry;
	GtkWidget *check;
} Tgotoline;

static void tgl_destroy_lcb(GtkWidget * widget, GdkEvent *event,  Tgotoline *tgl) {
	window_destroy(tgl->win);
	g_free(tgl);
}

static void tgl_ok_clicked_lcb(GtkWidget * widget, Tgotoline *tgl)
{
	gchar *linestr;
	gint linenum;

	linestr = gtk_editable_get_chars(GTK_EDITABLE(tgl->entry), 0, -1);
	linenum = get_int_from_string(linestr);
	DEBUG_MSG("tgl_ok_clicked_lcb, going to line %d (linestr=%s)\n", linenum, linestr);
	g_free(linestr);
	
	if (linenum > 0) {
		go_to_line(linenum, 1);
	}

	if (GTK_TOGGLE_BUTTON(tgl->check)->active) {
		if (linenum > 0) {
			gchar *new_text;
			gint position=0;
			gtk_editable_delete_text (GTK_EDITABLE(tgl->entry), 0, -1);
			new_text = g_strdup_printf("%d", linenum);
			gtk_editable_insert_text(GTK_EDITABLE(tgl->entry),new_text,strlen(new_text),&position);
			g_free(new_text);
		}
	} else {
		tgl_destroy_lcb(NULL, NULL, tgl);
	}

}

static void tgl_fromsel_clicked_lcb(GtkWidget * widget, Tgotoline *tgl) {
/*	guint32 timev;*/
/*	GdkAtom clipboard_atom = gdk_atom_intern ("COMPOUND_TEXT", FALSE); */
	GdkAtom clipboard_atom = GDK_TARGET_STRING;

	DEBUG_MSG("tgl_fromsel_clicked_lcb, started\n");
	gtk_editable_delete_text (GTK_EDITABLE(tgl->entry), 0, -1);

/*	timev = time(NULL);*/ /* should be the button-click time */
	gtk_selection_convert (tgl->entry, GDK_SELECTION_PRIMARY,
				 clipboard_atom, GDK_CURRENT_TIME);
	flush_queue();
	tgl_ok_clicked_lcb(widget, tgl);
}

static void tgl_cancel_clicked_lcb(GtkWidget *widget, gpointer data) {
	tgl_destroy_lcb(NULL, NULL, data);
}

void tgl_enter_lcb (GtkWidget *widget, gpointer ud) {
     Tgotoline *tgl;
     tgl = ud;
     tgl_ok_clicked_lcb (widget, tgl);
}

void go_to_line_win_cb(GtkWidget * widget, gpointer data)
{
	Tgotoline *tgl;
	GtkWidget *but1, *vbox, *hbox;
	
	tgl = g_malloc(sizeof(Tgotoline));
	tgl->win = window_full(_("Goto line"), GTK_WIN_POS_MOUSE,
						  GTK_WINDOW_DIALOG, 5, tgl_destroy_lcb, tgl);
	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(tgl->win), vbox);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

	gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("Line number: ")), FALSE, FALSE, 0);
	tgl->entry = boxed_entry_with_text(NULL, 20, hbox);

	but1 = bf_stock_button(_("From selection"), tgl_fromsel_clicked_lcb, tgl);
	gtk_box_pack_start(GTK_BOX(hbox), but1, FALSE, FALSE, 0);

	hbox = gtk_hbutton_box_new();
	gtk_hbutton_box_set_layout_default(GTK_BUTTONBOX_END);
	gtk_button_box_set_spacing(GTK_BUTTON_BOX(hbox), 1);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

	tgl->check = boxed_checkbut_with_value(_("Keep dialog"), 0, hbox);
	
	but1 = bf_stock_ok_button(tgl_ok_clicked_lcb, tgl);
	gtk_box_pack_start(GTK_BOX(hbox), but1, FALSE, FALSE, 0);
	gtk_window_set_default(GTK_WINDOW(tgl->win), but1);

	but1 = bf_stock_cancel_button(tgl_cancel_clicked_lcb, tgl);
	gtk_box_pack_start(GTK_BOX(hbox), but1, FALSE, FALSE, 0);
	gtk_widget_grab_focus (tgl->entry);
        gtk_signal_connect (GTK_OBJECT (tgl->entry), "activate", GTK_SIGNAL_FUNC (tgl_enter_lcb),
                            (gpointer *) tgl);
	gtk_widget_show_all(tgl->win);
}

static Tchar_entity escape_double_quotes[] = {
	{34, "\\\""}, {0, NULL}
};

/* charset 0 = ascii, 1 = iso8859-1, 2 = escape quotes  */

static void doc_special_char_replace(Tdocument * document, gint startpos,
									 gint endpos, gint charset)
{
	gint i, j, contj;
	unsigned char curchar;
	gchar undobuf[2];
	Tchar_entity *set;

	undobuf[1] = '\0';

	if (endpos == -1) {
		endpos = gtk_text_get_length(GTK_TEXT(document->textbox));
	}

	if (endpos < startpos) {
		i = startpos;
		startpos = endpos;
		endpos = i;
	}
	switch (charset) {
		case 0:
			set = &ascii_charset[0];
		break;
		case 1:
			set = &iso8859_1_charset[0];
		break;
		case 2:
			set = &escape_double_quotes[0];
		break;
		default:
			return;
		break;
	}

	DEBUG_MSG
		("doc_special_char_replace, startpos=%d, endpos=%d, doc length=%d\n",
		 startpos, endpos,
		 gtk_text_get_length(GTK_TEXT(document->textbox)));
	gtk_text_freeze(GTK_TEXT(document->textbox));
	doc_unbind_signals(document);
#ifdef UNDO2
	doc_unre_new_group(document);
#endif
	for (i = startpos; i < endpos; i++) {
		curchar = (GTK_TEXT_INDEX(GTK_TEXT(document->textbox), i));
		DEBUG_MSG("doc_special_char_replace, i=%d, curchar=%d (%c)\n", i,curchar, curchar);
		if (!curchar) {
			DEBUG_MSG("doc_special_char_replace, char %d doesn exist\n",i);
			gtk_text_thaw(GTK_TEXT(document->textbox));
			return;
		}
		j = 0;
		contj = (set[j].entity != NULL);
		while (contj) {
			undobuf[0] = set[j].id;
			if (set[j].id == curchar) {
				gint entitylen = strlen(set[j].entity);
				gtk_text_set_point(GTK_TEXT(document->textbox), i);
				undo_list_add(document, undobuf, i, i+1, UndoDelete);
				gtk_text_forward_delete(GTK_TEXT(document->textbox), 1);
				gtk_text_insert(GTK_TEXT(document->textbox), NULL, NULL,
								NULL, set[j].entity, entitylen);
				undo_list_add(document, set[j].entity, i, i+entitylen, UndoInsert);
				endpos += (entitylen - 1);
				i += (entitylen -1);
				contj = 0;
			} else {
				j++;
				contj = (set[j].entity != NULL);
			}
		}
	}
#ifdef UNDO2
	doc_unre_new_group(document);
#endif
	doc_bind_signals(document);
	gtk_text_thaw(GTK_TEXT(document->textbox));
	doc_set_modified(document, 1);
	DEBUG_MSG("doc_special_char_replace, finished\n");
}


static void special_char_replace(gint charset)
{
	gint start, end, count;
	GtkWidget *textbox = main_v->current_document->textbox;

	count = bf_statusbar_message(_("Charset conversion running..."));

	if (GTK_EDITABLE(textbox)->has_selection) {
		start = GTK_EDITABLE(textbox)->selection_start_pos;
		end = GTK_EDITABLE(textbox)->selection_end_pos;
	} else {
		start = 0;
		end = -1;
	}
	DEBUG_MSG("special_char_replace, start=%d, end=%d, length=%d\n", start, end, end-start);
	doc_special_char_replace(main_v->current_document, start, end,
							 charset);
	if (main_v->current_document->highlightstate) {
		doc_highlight_once(main_v->current_document);
	}
	statusbar_remove(GINT_TO_POINTER(count));
}

void iso8859_1_replace_cb(GtkWidget * widget, gpointer data)
{
	special_char_replace(1);
}

void ascii_replace_cb(GtkWidget * widget, gpointer data)
{
	special_char_replace(0);
}

void escape_quotes_replace_cb(GtkWidget * widget, gpointer data)
{
	special_char_replace(2);
}


/*****************************************************/
/**** doc new, close, open, save etc. functions ******/
/*****************************************************/
#define STARTING_BUFFER_SIZE 512
void doc_file_to_textbox(Tdocument * doc, gchar * filename)
{
	FILE *fd;
	gchar *errmessage, line[STARTING_BUFFER_SIZE], *linebuff = NULL, *message;
	gint buffsize = 0, newsize;

	message = g_strconcat(_("Opening file "), filename, NULL);
	statusbar_message(message, 1000);
	g_free(message);
	/* This opens the contents of a file to a textbox */
	change_dir(filename);
	fd = fopen(filename, "r");
	if (fd == NULL) {
		DEBUG_MSG("file_to_textbox, cannot open file %s\n", filename);
		errmessage =
			g_strconcat(_("Could not open file:\n"), filename, NULL);
		error_dialog(_("Error"), errmessage);	/* 7 */
		g_free(errmessage);
		return;
	} else {
		gtk_text_freeze(GTK_TEXT(doc->textbox));

		newsize = sizeof(line);
		linebuff = g_malloc(newsize);
		if (linebuff) {
			linebuff[0] = '\0';
			buffsize = newsize;
		}

		while (fgets(line, STARTING_BUFFER_SIZE, fd) != NULL) {
			if (main_v->props.auto_convert_CR) {
				buf_replace_char(line, STARTING_BUFFER_SIZE, '\r', ' ');
			}
			if (!linebuff) {
				gtk_text_insert(GTK_TEXT(doc->textbox),
					NULL, NULL, NULL, line, -1);
			} else {
				newsize = strlen(linebuff) + strlen(line) + 1;
				if (buffsize < newsize) {
					gchar *ptr = g_realloc( linebuff, newsize );
					if (!ptr) {
						DEBUG_MSG("realloc linebuff failed\n");
						gtk_text_insert(GTK_TEXT(doc->textbox),
							NULL, NULL, NULL, linebuff, -1);
						linebuff[0] = '\0';
						gtk_text_insert(GTK_TEXT(doc->textbox),
							NULL, NULL, NULL, line, -1);
						continue;
					}
					linebuff = ptr;	
					buffsize = newsize;
					DEBUG_MSG("realloc linebuff : %d bytes\n", newsize);
				}
				strcat( linebuff, line );
				if( strlen(linebuff)>0 &&
				    linebuff[strlen(linebuff)-1]=='\n' ) {
					gtk_text_insert(GTK_TEXT(doc->textbox),
						NULL, NULL, NULL, linebuff, -1);
					linebuff[0] = '\0';
				}
			}
		}
		if( linebuff ) {
			gtk_text_insert(GTK_TEXT(doc->textbox),
		 		NULL, NULL, NULL, linebuff, -1);
			g_free( linebuff );
			linebuff = NULL;
 		}
		gtk_text_thaw(GTK_TEXT(doc->textbox));
		fclose(fd);
		if (doc->highlightstate) {
			doc_highlight_once(doc);
			DEBUG_MSG("file_to_textbox, %p needs highlighting\n", doc);
		}
#ifdef PARSEDTD
		/* ehmm, this function is also used with file_insert (is that true?)
		 and stuff, should this be here ?!?!?!? */
		doc->dtd_loaded = -1;
		doc_set_doctype(doc);
#endif
	}
}


/* static gint file_check_backup(Tdocument *doc)
 * returns 1 on success, 0 on failure
 * if no backup is required, or no filename known, 1 is returned
 */
static gint file_check_backup(Tdocument *doc) {
	gchar *backupfilename;
	gint res = 1;

	if (main_v->props.backup_file && doc->filename
		&& file_exists_and_readable(doc->filename)) {
		backupfilename = g_strconcat(doc->filename, main_v->props.backup_filestring, NULL);
		/* if the document is a symlink we default to backup by copy
		to preserve the symlink */
		if (main_v->props.backup_by_copy || doc->is_symlink) {
			res = file_copy(doc->filename, backupfilename);
		} else {
			res = rename(doc->filename, backupfilename);
			if (res == 0) {
				res =  1;
			} else {
				res =  0;
			}
		}
		g_free(backupfilename);
	}
	return res;
}

/*
 * gint doc_textbox_to_file(Tdocument * doc, gchar * filename)
 * returns 1 on success
 * returns 2 on success but the backup failed
 * returns -1 if the backup failed and save was aborted
 * returns -2 if the file pointer could not be opened
 * returns -3 if the backup failed and save was aborted by the user
 */

gint doc_textbox_to_file(Tdocument * doc, gchar * filename)
{
	FILE *fd;
	gchar *tmpchar;
	gint backup_retval;

	DEBUG_MSG("textbox_to_file, started file %s\n", filename);
	statusbar_message(_("Saving file"), 1000);
	/* This writes the contents of a textbox to a file */
	backup_retval = file_check_backup(doc);
	if (!backup_retval) {
		if (strcmp(main_v->props.backup_abort_style, "abort")==0) {
			DEBUG_MSG("doc_textbox_to_file, backup failure, abort!\n");
			return -1;
		} else if (strcmp(main_v->props.backup_abort_style, "ask")==0) {
			gchar *options[] = {N_("Abort save"), N_("Continue save"), NULL};
			gint retval;
			gchar *tmpstr =  g_strdup_printf(_("Backup %s failed"), filename);
			retval = multi_button_dialog(_("Bluefish warning, file backup failure"), 1, tmpstr, options);
			g_free(tmpstr);
			if (retval == 0) {
				DEBUG_MSG("doc_textbox_to_file, backup failure, user aborted!\n");
				return -3;
			} 
		}
	}
	change_dir(filename);
	fd = fopen(filename, "w");
	if (fd == NULL) {
		DEBUG_MSG("textbox_to_file, cannot open file %s\n", filename);
		return -2;
	} else {
		DEBUG_MSG("textbox_to_file, lenght=%d\n",
				  gtk_text_get_length(GTK_TEXT(doc->textbox)));
		DEBUG_MSG("textbox_to_file, fd=%p\n", fd);
		DEBUG_MSG("textbox_to_file, filename=%s\n", filename);

		tmpchar =
			gtk_editable_get_chars(GTK_EDITABLE(doc->textbox), 0, -1);
		fputs(tmpchar, fd);
		g_free(tmpchar);
		fclose(fd);
		if (doc->owner_uid != -1 && !main_v->props.backup_by_copy && !doc->is_symlink) {
			chmod(filename, doc->mode);
			chown(filename, doc->owner_uid, doc->owner_gid);
		}
		doc_set_modified(doc, 0);
		if (main_v->props.clear_undo_on_save) {
			doc_unre_clear_all(doc);
		}
		if (!backup_retval) {
			return 2;
		} else {
			return 1;
		}
	}
}

/*************
file_save_cb is called in file_close_all_cb, but it shouldn't work on
the current document, so we need doc_save
also for file_save_as in file_save_all_cb
************/

Tdocument *doc_new(void)
{
	GtkWidget *tmptable, *tmpscrollbar;
	Tdocument *document;

	/* Here we create a new document, and add it to the GList */
	DEBUG_MSG("doc_new, started\n");
	if (main_v->current_document != NULL) {
		if(test_only_empty_doc_left(main_v->current_document)) {
			DEBUG_MSG("doc_new, no new needed, the current one is empty\n");
			return main_v->current_document;
		}
	}

	document = g_malloc0(sizeof(Tdocument));
	DEBUG_MSG("doc_new, malloced at %p\n", document);
#ifdef UNDO2
	doc_unre_init(document);
#endif /* UNDO2 */
	tmptable = gtk_table_new(2, 1, FALSE);
	gtk_table_set_row_spacings(GTK_TABLE(tmptable), 0);
	gtk_table_set_col_spacings(GTK_TABLE(tmptable), 0);
	document->textbox = gtk_text_new(NULL, NULL);
	gtk_text_set_editable(GTK_TEXT(document->textbox), TRUE);
	
	/* before realizing the text widget the style is still the
	default gtk-theme style, which solves the speed problem
	with heavy pixmap themes. */
	document_force_def_style(document, main_v->props.force_def_style);

	DEBUG_MSG("doc_new, document->textbox=%p\n", document->textbox);
	gtk_table_attach_defaults(GTK_TABLE(tmptable), document->textbox, 0, 1,
							  0, 1);

	tmpscrollbar = gtk_vscrollbar_new(GTK_TEXT(document->textbox)->vadj);
	GTK_WIDGET_UNSET_FLAGS(tmpscrollbar, GTK_CAN_FOCUS);
	gtk_table_attach(GTK_TABLE(tmptable), tmpscrollbar, 1, 2, 0, 1,
					 GTK_FILL, GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0, 0);
	gtk_widget_show(tmpscrollbar);
	/* setting the string is done by doc_set_modified() now */
	document->tab_label = gtk_label_new(NULL);
	GTK_WIDGET_UNSET_FLAGS(document->tab_label, GTK_CAN_FOCUS);

	apply_font_style(GTK_WIDGET(document->tab_label),
					 main_v->props.cfg_tab_font);

	DEBUG_MSG("doc_new, before notebook_append_page\n");
	gtk_notebook_append_page(GTK_NOTEBOOK(main_v->notebook), tmptable,
							 document->tab_label);
	gtk_widget_show(tmptable);
	DEBUG_MSG("doc_new, before realize on textbox\n");
	gtk_widget_realize(document->textbox);
	DEBUG_MSG("doc_new, after realize on textbox\n");
	
	/* if you move this function _before_ the realize it will ignore
	the widgets style (set by document_force_def_style() ) and use
	the theme style instead. We don't want it for the textbox so
	we use it after realizing the textbox */
	apply_font_style(GTK_WIDGET(document->textbox),
					 main_v->props.cfg_editor_font);
	
	document_set_wrap(main_v->props.word_wrap, main_v->props.line_wrap,
					  document);
	gtk_signal_connect_object(GTK_OBJECT(document->textbox), "event",
							  GTK_SIGNAL_FUNC(textbox_event_lcb), NULL);

	GTK_TEXT(document->textbox)->default_tab_width =
		main_v->props.cfg_editor_tabwidth;
	GTK_TEXT(document->textbox)->tab_stops =
		g_list_remove(GTK_TEXT(document->textbox)->tab_stops,
					  GTK_TEXT(document->textbox)->tab_stops->data);
	GTK_TEXT(document->textbox)->tab_stops =
		g_list_remove(GTK_TEXT(document->textbox)->tab_stops,
					  GTK_TEXT(document->textbox)->tab_stops->data);
	GTK_TEXT(document->textbox)->tab_stops = NULL;
	GTK_TEXT(document->textbox)->tab_stops =
		g_list_prepend(GTK_TEXT(document->textbox)->tab_stops,
					   GINT_TO_POINTER(main_v->props.cfg_editor_tabwidth));
	GTK_TEXT(document->textbox)->tab_stops =
		g_list_prepend(GTK_TEXT(document->textbox)->tab_stops,
					   GINT_TO_POINTER(main_v->props.cfg_editor_tabwidth));

	gtk_widget_show(document->textbox);
	/* this will force function doc_set_modified to update the tab label*/
	document->modified = 1;
	doc_set_modified(document, 0);
	document->filename = NULL;
	document->need_highlighting = 0;
	document->mtime = 0;
	document->owner_uid = -1;
	document->owner_gid = -1;
	document->is_symlink = 0;
	document->highlightstate = main_v->props.defaulthighlight;

	main_v->documentlist = g_list_append(main_v->documentlist, document);
	gtk_widget_grab_focus(document->textbox);

	doc_bind_signals(document);

	/* set this new document as active notebook page */
	flush_queue();
	DEBUG_MSG("doc_new, before notebook_set_page, after flush_queue\n");
	gtk_notebook_set_page(GTK_NOTEBOOK(main_v->notebook),
						  g_list_length(main_v->documentlist) - 1);
	notebook_changed();

#ifdef PARSEDTD
	document->dtd_loaded = document->dtd_doctype = -1;
#endif

	DEBUG_MSG("doc_new, ended\n");

	return document;
}

void doc_destroy(Tdocument * doc)
{
	DEBUG_MSG("doc_destroy, started, notebook=%p, document=%p\n",
			  main_v->notebook, doc);
	DEBUG_MSG("doc_destroy, textbox=%p\n", doc->textbox);

	if (doc->filename) {
		add_to_recent_list(doc->filename, 1);
		DEBUG_MSG("doc_destroy, filename=%s, not modified, destroy it\n",doc->filename);
	}
#ifdef DEBUG
	 else {
		g_print("doc_destroy, filename=NULL, not modified, destroy it\n");
	}



	DEBUG_MSG("doc_destroy, page num should be %d\n",
			  gtk_notebook_page_num(GTK_NOTEBOOK(main_v->notebook),
									doc->textbox->parent));

#endif
	gtk_notebook_remove_page(GTK_NOTEBOOK(main_v->notebook),
							 gtk_notebook_page_num(GTK_NOTEBOOK
												   (main_v->notebook),
												   doc->textbox->parent));
	DEBUG_MSG("doc_destroy, g_list_length(documentlist)=%d\n",
			  g_list_length(main_v->documentlist));

	if (g_list_length(main_v->documentlist) > 1) {
		main_v->documentlist = g_list_remove(main_v->documentlist, doc);
	} else {
		main_v->documentlist = g_list_remove(main_v->documentlist, doc);
		DEBUG_MSG
			("doc_destroy, last document removed from documentlist\n");
		g_list_free(main_v->documentlist);
		DEBUG_MSG("doc_destroy, freed documentlist\n");
		main_v->documentlist = NULL;
		DEBUG_MSG("doc_destroy, documentlist = NULL\n");
	}
	DEBUG_MSG("doc_destroy, g_list_length(documentlist)=%d\n",
			  g_list_length(main_v->documentlist));
	if (doc->filename) {
		DEBUG_MSG("doc_destroy, doc->filename=%s\n", doc->filename);
		g_free(doc->filename);
	}
#ifdef UNDO2
	doc_unre_destroy(doc);
#endif /* UNDO2 */

#ifdef PARSEDTD
	unload_DTD(doc->dtd_loaded);
#endif

	g_free(doc);
/*		gtk_notebook_set_page(GTK_NOTEBOOK(main_v->notebook), ((gint) g_list_length(main_v->documentlist) - 1)); */
	notebook_changed();
}
/* gint doc_save(Tdocument * doc, gint do_save_as, gint do_move)
 * returns 1 on success
 * returns 2 on success but the backup failed
 * returns 3 on user abort
 * returns -1 if the backup failed and save was aborted
 * returns -2 if the file pointer could not be opened 
 * returns -3 if the backup failed and save was aborted by the user
 * returns -4 if there is no filename, after asking one from the user
 * returns -5 if another process modified the file, and the user chose cancel
 */

gint doc_save(Tdocument * doc, gint do_save_as, gint do_move)
{
	gchar *oldfilename = NULL;
	gint retval;
#ifdef DEBUG
	g_assert(doc);
#endif

	DEBUG_MSG("doc_save, doc=%p, save_as=%d, do_move=%d\n", doc, do_save_as, do_move);
	if (doc->filename == NULL) {
		do_save_as = 1;
	}
	if (do_move) {
		do_save_as = 1;
	}

	if (do_save_as) {
		gchar *newfilename = NULL;
		gint index;
		statusbar_message(_("Save as..."), 1);
		oldfilename = doc->filename;
		doc->filename = NULL;
		newfilename = return_file_w_title(NULL, _("Save document as"));
		index = find_filename_in_documentlist(newfilename);
		DEBUG_MSG("doc_save, index=%d, filename=%p\n", index, newfilename);
#ifdef DEBUG
		if (newfilename) {
			g_print("doc_save, filename=%s\n", newfilename);
		}
#endif
		if (index != -1) {
			gchar *tmpstr;
			gint retval;
			gchar *options[] = {N_("Overwrite"), N_("Cancel"), NULL};
			tmpstr = g_strdup_printf(_("File %s is open, overwrite?"), newfilename);
			retval = multi_button_dialog(_("Bluefish: Warning, file is open"), 1, tmpstr, options);
			g_free(tmpstr);
			if (retval == 1) {
				g_free(newfilename);
				doc->filename = oldfilename;
				return 3;
			} else {
				Tdocument *tmpdoc;
				tmpdoc = (Tdocument *)g_list_nth_data(main_v->documentlist, index);
				DEBUG_MSG("doc_save, tmpdoc=%p\n", tmpdoc);
#ifdef DEBUG
				g_assert(tmpdoc);
				g_assert(tmpdoc->filename);
#endif
				g_free(tmpdoc->filename);
				tmpdoc->filename = NULL;
				doc_set_modified(tmpdoc, 1);
				tmpstr = g_strconcat(_("Previously: "), strip_filename(newfilename), NULL);
				gtk_label_set(GTK_LABEL(tmpdoc->tab_label),tmpstr);
				g_free(tmpstr);
			}
		}
		doc->filename = newfilename;
		doc->modified = 1;
	}

	if (doc->filename == NULL) {
		doc->filename = oldfilename;
		return -4;
	}
	
	if (doc_check_mtime(doc) == 0) {
		gchar *tmpstr;
		gint retval;
		gchar *options[] = {N_("Overwrite"), N_("Cancel"), NULL};

		tmpstr = g_strdup_printf(_("File %s\nis modified by another process, overwrite?"), doc->filename);
		retval = multi_button_dialog(_("Bluefish: Warning, file is modified"), 0, tmpstr, options);
		g_free(tmpstr);
		if (retval == 1) {
			if (oldfilename) {
				g_free(oldfilename);
			}
			return -5;
		}
	}
	
	DEBUG_MSG("doc_save, returned file %s\n", doc->filename);
	if (do_save_as && oldfilename && main_v->props.link_management) {
		update_filenames_in_file(doc, oldfilename, doc->filename, 1);
	}
	statusbar_message(_("Save in progress"), 1);
	retval = doc_textbox_to_file(doc, doc->filename);
	doc_update_mtime(doc);
/*	should be set by doc_set_modified()
	gtk_label_set(GTK_LABEL(doc->tab_label),
				  strip_filename(doc->filename)); */
	switch (retval) {
		gchar *errmessage;
		case -1:
			/* backup failed and aborted */
			errmessage = g_strconcat(_("File save aborted, could not backup file:\n"), doc->filename, NULL);
			error_dialog(_("Error"), errmessage);
			g_free(errmessage);
		break;
		case -2:
			/* could not open the file pointer */
			errmessage = g_strconcat(_("File save error, could not write file:\n"), doc->filename, NULL);
			error_dialog(_("Error"), errmessage);
			g_free(errmessage);
		break;
#ifdef DEBUG
		default:
			DEBUG_MSG("doc_save, received return value %d from doc_textbox_to_file\n", retval);
		break;
#endif
	}
	if (oldfilename) {
		populate_dir_file_list();
		if (do_move && (retval > 0)) {
			if (main_v->props.link_management) {
				all_documents_update_links(doc, oldfilename,
							   doc->filename);
			}
			unlink(oldfilename);
		}

		g_free(oldfilename);
	}
	return retval;
}

void doc_new_with_new_file(gchar * filename) {

	Tdocument *doc;
	if (filename == NULL) {
		statusbar_message(_("No filename"), 2);
		return;
	}
	if (!main_v->props.allow_multi_instances) {
		gboolean res;
		res = switch_to_document_by_filename(filename);
		if (res){
			return;
		}
	} 
	DEBUG_MSG("doc_new_with_new_file, filename=%s\n", filename);
	file_and_dir_history_add(filename);
	doc = doc_new();
	doc->filename = g_strdup(filename);
	gtk_label_set(GTK_LABEL(doc->tab_label),
					  strip_filename(doc->filename));
	doc_save(doc, 0, 0);
	notebook_changed();
}

void doc_new_with_file(gchar * filename)
{

	Tdocument *doc;
	
	if ((filename == NULL) || (!file_exists_and_readable(filename))) {
        error_dialog("Error",filename);
		statusbar_message(_("Unable to open file"), 2000);
		return;
	}
	if (!main_v->props.allow_multi_instances) {
		gboolean res;
		res = switch_to_document_by_filename(filename);
		if (res){
			return;
		}
	} 
	DEBUG_MSG("doc_new_with_file, filename=%s exists\n", filename);
	file_and_dir_history_add(filename);
	doc = doc_new();
	doc->filename = g_strdup(filename);
	doc_file_to_textbox(doc, doc->filename);
	doc->modified = 1; /* force doc_set_modified() to update the tab-label */
	doc_set_modified(doc, 0);
	doc_set_stat_info(doc); /* also sets mtime field */
	notebook_changed();
}

void doc_reload(Tdocument *doc) {
	if ((doc->filename == NULL) || (!file_exists_and_readable(doc->filename))) {
		statusbar_message(_("Unable to open file"), 2000);
		return;
	}
	gtk_editable_delete_text(GTK_EDITABLE(doc->textbox), 0, -1);
	doc_file_to_textbox(doc, doc->filename);
	doc_set_modified(doc, 0);
	doc_set_stat_info(doc); /* also sets mtime field */
}

void docs_new_from_files(GList * file_list) {

	GList *tmplist;

	tmplist = g_list_first(file_list);
	while (tmplist) {
		doc_new_with_file((gchar *) tmplist->data);
		tmplist = g_list_next(tmplist);
	}
}

/*
 * Function: close_save_cancel_dialog
 * Arguments:
 * 	title - title string
 * 	label - label string
 * Return value:
 * 	1 - close, 2 - save, 3 - cancel
 * Description:
 * 	create dialog which returns a close, save or cancel
 */
static gint close_save_cancel_dialog(gchar * title, gchar * label) {

	gint retval;
	gchar *buttons[] = {N_("Save"), N_("Close"), N_("Cancel"), NULL};

	retval = multi_button_dialog(title, 2, label, buttons);
	
	switch (retval) {
		case 0:
			return 2;
		break;
		case 1:
			return 1;
		break;
		case 2:
			return 3;
		break;
	}
	return 3;
}


/*
returning 0 --> cancel or abort
returning 1 --> ok, closed or saved & closed
*/
gint doc_close(Tdocument * doc, gint warn_only)
{
	gchar *text;
	gint retval;

#ifdef AUTOCOMPLET
	hide_autocompletion_window();
#endif

	if (!doc) {
		return 0;
	}

	if (!doc->modified && !doc->filename
		&& (gtk_text_get_length(GTK_TEXT(doc->textbox)) == 0)
		&& (g_list_length(main_v->documentlist) == 1)) {
		return 0;
	}

	if (doc->modified) {
		if (doc->filename) {
			text =
				g_strdup_printf(_("Are you sure you want to close\n%s ?"),
								doc->filename);
		} else {
			text =
				g_strdup(_
						 ("Are you sure you want to close\nthis untitled file ?"));
		}
		retval =
			close_save_cancel_dialog(_
									 ("Bluefish warning: file is modified!"),
text);
		g_free(text);
		switch (retval) {
		case 3:
			DEBUG_MSG("doc_close, retval=3, returning\n");
			return 0;
			break;
		case 2:
			doc_save(doc, 0, 0);
			if (doc->modified == 1) {
				/* something went wrong it's still not saved */
				return 0;
			}
			if (!warn_only) {
				doc_destroy(doc);
			}
			break;
		case 1:
			if (!warn_only) {
				doc_destroy(doc);
			}
			break;
		default:
			return 0;			/* something went wrong */
			break;
		}
	} else {
		DEBUG_MSG("doc_close, closing doc=%p\n", doc);
		if (!warn_only) {
			doc_destroy(doc);
		}
	}

	notebook_changed();
	return 1;
}

void file_save_cb(GtkWidget * widget, gpointer data)
{
	DEBUG_MSG("file_save_cb, file save started\n");
	doc_save(main_v->current_document, 0, 0);
}


void file_save_as_cb(GtkWidget * widget, gpointer data)
{
	DEBUG_MSG("file_save_as_cb, file save started\n");
	doc_save(main_v->current_document, 1, 0);
}

void file_move_to_cb(GtkWidget * widget, gpointer data)
{
	DEBUG_MSG("file_move_to_cb, file save started\n");
	doc_save(main_v->current_document, 1, 1);
}

void file_open_cb(GtkWidget * widget, gpointer data)
{

	GList *tmplist;
	DEBUG_MSG("file_open_cb, started\n");
	if (GPOINTER_TO_INT(data) == 1) {
		tmplist = return_files_advanced();
	} else {
		tmplist = return_files(NULL);
	}
	if (!tmplist) {
		return;
	}
	docs_new_from_files(tmplist);
	free_stringlist(tmplist);
}

void revert_to_saved_cb(GtkWidget * widget, gpointer data) {
	doc_reload(main_v->current_document);
}

void file_insert_cb(GtkWidget * widget, gpointer data)
{
	gchar *tmpfilename;
	gint currentp;

	tmpfilename = return_file(NULL);
	if (tmpfilename == NULL) {
		error_dialog(_("Bluefish error"), _("No filename known"));
		return;
	} else {
		currentp =
			gtk_editable_get_position(GTK_EDITABLE
									  (main_v->current_document->textbox));
		gtk_text_set_point(GTK_TEXT(main_v->current_document->textbox),
						   currentp);
		doc_file_to_textbox(main_v->current_document, tmpfilename);
		g_free(tmpfilename);
		doc_set_modified(main_v->current_document, 1);
	}
}

void file_new_cb(GtkWidget * widget, gpointer data)
{
	Tdocument *doc;
	/* This is the callback function for a file new button */
	DEBUG_MSG("file_new_cb, started\n");
	doc = doc_new();
        
 	if ((main_v->current_project.template) && (file_exists_and_readable(main_v->current_project.template) == 1)) {
             doc_file_to_textbox(doc, main_v->current_project.template);
 	}
	DEBUG_MSG
		("file_new_cb, end, current_document=%p, current_document->filename=%p\n",
		 main_v->current_document, main_v->current_document->filename);
}

void file_close_cb(GtkWidget * widget, gpointer data)
{
	DEBUG_MSG("file_close_cb, starting doc_close with %p\n",
			  main_v->current_document);
	doc_close(main_v->current_document, 0);
	DEBUG_MSG("file_close_cb, finished\n");
}

void file_close_all_cb(GtkWidget * widget, gpointer data)
{
	GList *tmplist;
	Tdocument *tmpdoc;
	gint retval = -1;

	DEBUG_MSG("file_close_all_cb, started\n");

	/* first a warning loop */
	if (test_docs_modified(NULL)) {
		gchar *options[] = {N_("save all"), N_("close all"), N_("choose per file"), N_("cancel"), NULL};
		retval =	multi_button_dialog(_("Bluefish: Warning, some file(s) are modified!"), 3, _("Some file(s) are modified\nplease choose your action"), options);
		if (retval == 3) {
			DEBUG_MSG("file_close_all_cb, cancel clicked, returning 0\n");
			return;
		}
	} else {
		retval = 1;
	}
	DEBUG_MSG("file_close_all_cb, after the warnings, retval=%d, now close all the windows\n", retval);

	tmplist = g_list_first(main_v->documentlist);
	while (tmplist) {
		tmpdoc = (Tdocument *) tmplist->data;
		if (test_only_empty_doc_left(tmpdoc)) {
			return;
		}
		
		switch (retval) {
		case 0:
			doc_save(tmpdoc, 0, 0);
			if (!tmpdoc->modified) {
				doc_destroy(tmpdoc);
			} else {
				return;
			}
			tmplist = g_list_first(main_v->documentlist);
		break;
		case 1:
			doc_destroy(tmpdoc);
			tmplist = g_list_first(main_v->documentlist);
		break;
		case 2:
			if (doc_close(tmpdoc, 0)) {
				tmplist = g_list_first(main_v->documentlist);
			} else {
				notebook_changed();
				return;
			}
		break;
		default:
			notebook_changed();
			return;
		break;
		}
	}
	notebook_changed();
	DEBUG_MSG("file_close_all_cb, finished\n");
}


/*
 * Function: file_save_all_cb
 * Arguments:
 * 	widget	- callback widget
 * 	data	- data for callback function
 * Return value:
 * 	void
 * Description:
 * 	Save all editor notebooks
 */
void file_save_all_cb(GtkWidget * widget, gpointer data)
{

	GList *tmplist;
	Tdocument *tmpdoc;

	tmplist = g_list_first(main_v->documentlist);
	while (tmplist) {
		tmpdoc = (Tdocument *) tmplist->data;
#ifdef DEBUG
		g_print("file_save_all_cb, a NULL document in the documentlist");
		g_assert(tmpdoc);
#endif
		if (tmpdoc->modified) {
			doc_save(tmpdoc, 0, 0);
		}
		tmplist = g_list_next(tmplist);
	}
}


/******************************************************************************/
/* this function destroys the current_document */
void destroy_current_document(void)
{
	if (main_v->current_document) {
		doc_destroy(main_v->current_document);
	} else {
		DEBUG_MSG
			("destroy_current_document, cannot close a NULL current_document\n");
	}
}


/*******************************************************************/
/*               Printing sysem                                    */
/*******************************************************************/

static void print_dialog_destroy_lcb(GtkWidget * widget, GdkEvent *event, gpointer data) {
	window_destroy(((Tprintwin *) data)->print_window);
	g_free(((Tprintwin *) data));
}

static void print_dialog_cancel_lcb(GtkWidget * widget, gpointer data) {
	print_dialog_destroy_lcb(NULL, NULL, data);
}

static char *temporary_print_file(Tdocument * doc, Tprintwin * pw)
{
	char *filename;

	filename =
		g_strdup_printf("%s/.bluefish_print.%d%d",
						gtk_entry_get_text(GTK_ENTRY(pw->temp_dir)),
						(int) time(NULL), (int)getpid());
	doc_textbox_to_file(main_v->current_document, filename);
	return filename;
}


static void execute_print(GtkWidget * widget, gpointer data)
{
	char *orig_entry, *fname, *final_command, error_msg[256];
	DIR *dp;					/* to check if the temporary dir exists */
	Tprintwin *pw = (Tprintwin *) data;

	/* If no entry was given, there is no reason to continue */
	orig_entry =
		gtk_editable_get_chars(GTK_EDITABLE(pw->print_cmd), 0, -1);
	if (strlen(orig_entry) <= 0) {
		error_dialog(_("Error"), _("No commandline set!"));
		g_free(orig_entry);
		print_dialog_destroy_lcb(NULL, NULL, data);
		return;
	}

	/* Check if temporary directory exists */
	if ((dp = opendir(gtk_entry_get_text(GTK_ENTRY(pw->temp_dir)))) ==
		NULL) {
		strcpy(error_msg, gtk_entry_get_text(GTK_ENTRY(pw->temp_dir)));
		strcat(error_msg, _(" doesn't exist or isn't a directory."));
		error_dialog(_("Error"), error_msg);
		g_free(orig_entry);
		print_dialog_destroy_lcb(NULL, NULL, data);
		return;
	}
	closedir(dp);

	/* Now, let's explain a bit how this function works.
	 * We must create a temporary file in which the contents of
	 * the text area will be saved, in order to be printed. During
	 * this procedure, the actual document isn't saved. Then,
	 * we must replace %s in the command line with the temporary
	 * file name. We execute the command line and then erase
	 * the temporary file */

	/* Create temporary file and save its name at fname */
	if (!(fname = temporary_print_file(main_v->current_document, pw))) {
		/* if it couldn't be created, cancel print */
		error_dialog(_("Error"),
					 _("Error in creating temporary print file."));
		g_free(orig_entry);
		print_dialog_destroy_lcb(NULL, NULL, data);
		return;
	}

	/* Build the command and execute it. */
	final_command = g_strdup_printf(orig_entry, fname);

	if (system(final_command) == -1) {
		error_dialog(_("Error"),
					 _
					 ("System error in printing file. File not printed."));
	}

	/* Erase the temporary file */
	if (unlink(fname)) {
		error_dialog(_("Error"), _("Unable to erase temporary file."));
	}

	g_free(fname);
	g_free(final_command);
	g_free(orig_entry);
	print_dialog_destroy_lcb(NULL, NULL, data);
}

static void show_print_dialog()
{
	GtkWidget *button;
	GtkWidget *vbox, *hbox;
	Tprintwin *pw;

	pw = g_malloc(sizeof(Tprintwin));
	/* Create window */
	pw->print_window =
		window_full(_("Print Dialog"), GTK_WIN_POS_MOUSE,
					GTK_WINDOW_DIALOG, 3,print_dialog_destroy_lcb , pw);

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(pw->print_window), vbox);

	gtk_box_pack_start(GTK_BOX(vbox),
					   gtk_label_new(_
									 ("Enter print command-line below.\n Remember that you must place %s where\n the filename is supposed to be placed. \n\nPrint command:")),
					   TRUE, TRUE, 0);
	pw->print_cmd = entry_with_text("lpr %s", 255);
	gtk_box_pack_start(GTK_BOX(vbox), pw->print_cmd, TRUE, TRUE, 0);

	gtk_box_pack_start(GTK_BOX(vbox),
					   gtk_label_new(_("Temporary directory:")), TRUE,
					   TRUE, 0);
	pw->temp_dir = entry_with_text("/tmp", 255);
	gtk_box_pack_start(GTK_BOX(vbox), pw->temp_dir, TRUE, TRUE, 0);

	/* Ok and Cancel buttons */
	hbox = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);

	button = bf_stock_button(_(" Print "), execute_print, pw);
	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);

	button = bf_stock_cancel_button(print_dialog_cancel_lcb, pw);
	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);

	gtk_widget_show_all(pw->print_window);
}

void file_print_cb(GtkWidget * widget, gpointer data)
{
	show_print_dialog();
}

void word_count_cb (GtkWidget *widget, gpointer data) {
     int wc, idx, alnum = 0, tag = 0;
     const gchar delimiters[]=" .,;:?\n\t";
     const gchar alnums[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
     gchar *wc_message;
     gchar cidx;

     wc = 0;
     
     for (idx = 0; idx < gtk_text_get_length (GTK_TEXT (main_v->current_document->textbox)); idx++) {
          cidx = GTK_TEXT_INDEX (GTK_TEXT (main_v->current_document->textbox), idx);
          if ((strchr (delimiters, cidx)) && (alnum == 1) && (tag == 0)) wc++;
          if (cidx == '<') { tag = 1; } else if (cidx == '>') { tag = 0; };
          if (strchr (alnums, cidx)) { alnum = 1;} else { alnum = 0; }
     }
     
	  wc_message = g_strdup_printf("Word Count %d", wc);
     statusbar_message (wc_message, 5000);
     g_free (wc_message);
}

void show_document_status_cb (GtkWidget *widget, gpointer data) {

	if (main_v->current_document) {
		gchar curchar;
		gchar *message;
		gint col=-1, line=0, pos, i;

		pos = gtk_editable_get_position(GTK_EDITABLE(main_v->current_document->textbox));
		for (i=pos-1;i>=0;i--) {
			curchar = GTK_TEXT_INDEX(GTK_TEXT(main_v->current_document->textbox),i);
			if (curchar == '\n') {
				if (col == -1) {
					col = pos-i-1;
				}
				line++;
			}
		}
		if (col == -1) col = 0;
		if (main_v->current_document->filename) {
			message = g_strdup_printf("%s line %d col %d",main_v->current_document->filename, line, col);
		} else {
			message = g_strdup_printf("Untitled line %d col %d", line, col);
		}
		statusbar_message(message, 2000);
		g_free(message);
	}
}








#ifdef PARSEDTD

/*******************************************************************
 *
 * <!DOCTYPE related functions
 *
 *******************************************************************/


/*******************************************************************/
/* Function: doc_set_doctype
 * Description:
 *     Looks for <!DOCTYPE in the text of a document, and if found, sets dtd_doctype
 * Arguments:
 *     doc - pointer to the document
 * Return value:
 *     -1 if the DOCTYPE is not supported or not exists
 *     the index in HTML_doctypes if found
*/
gint doc_set_doctype(Tdocument *doc)
{
  char *text;
  char doctype[256];
  int doctypeindex = -1;
  int len = gtk_text_get_length(GTK_TEXT(doc->textbox));
  if( len > 512 )
    len = 512;
  if( len > 0 ) {
    text = gtk_editable_get_chars(GTK_EDITABLE(doc->textbox), 0, len);
    if( text ) {
      if( get_doctype_from_text(text, doctype, 256) ) {
	if( (doctypeindex  = find_dtd_index(doctype)) == -1 ) {
	  DEBUG_MSG("Doctype not supported: %s\n", doctype);
	} else {
	  DEBUG_MSG("Doctype found(%d):%s\n", doctypeindex, doctype);
	}
      }
      g_free(text);
    }
  }
  doc->dtd_doctype = doctypeindex;
  return doctypeindex;
}

/*******************************************************************/
/* Function: doc_load_dtd
 * Description:
 *    Loads the dtd for the document type.
 *    If there is no doctype, loads any loaded DTD or 
 *         the default DTD (DOCTYPE_DEFAULT)
 *    If there is a doctype, and a default DTD was loaded, unloads the default
 *         and loads de current
 * Arguments:
 *     doc - pointer to the document
*/
gint doc_load_dtd(Tdocument *doc)
{
  int dtdindex = doc->dtd_doctype;
  DEBUG_MSG("doc_load_dtd: start: doctype=%d, dtdloaded=%d\n", dtdindex, doc->dtd_loaded);
  if( doc->dtd_loaded == -1 || (dtdindex != doc->dtd_loaded ) ) {
    /* Either a DTD is not loaded or there is a default dtd loaded */
    if( dtdindex == -1 ) { /* another oportunity to set the doctype */
      doc_set_doctype(doc);
      dtdindex = doc->dtd_doctype;
      DEBUG_MSG("doc_load_dtd: setting doctype: doctype=%d, dtdloaded=%d\n", dtdindex, doc->dtd_loaded);
    }
    if( dtdindex != -1 && dtdindex != doc->dtd_loaded ) {
      DEBUG_MSG("doc_load_dtd: 1: doctype=%d, dtdloaded=%d\n", dtdindex, doc->dtd_loaded);
      /* There is a doctype different from the loaded */
      if( load_DTD(dtdindex) == RET_DTD_LOADED ) {
	if( doc->dtd_loaded != -1 ) 
	  unload_DTD(doc->dtd_loaded);
	doc->dtd_loaded = dtdindex;
      }
    } else if( dtdindex == -1 && doc->dtd_loaded == -1) {
      DEBUG_MSG("doc_load_dtd: 2: doctype=%d, dtdloaded=%d\n", dtdindex, doc->dtd_loaded);
      /* There is no doctype nor dtd loaded */
      dtdindex = find_a_loaded_dtd();
      if( dtdindex == -1 ) 
	dtdindex = DOCTYPE_DEFAULT;
      if( load_DTD(dtdindex) == RET_DTD_LOADED ) {
	doc->dtd_loaded = dtdindex;
      }
    }
  }
  DEBUG_MSG("doc_load_dtd: end: doctype=%d, dtdloaded=%d\n", doc->dtd_doctype, doc->dtd_loaded);
  return dtdindex;
}


#endif

#ifdef ATTRPAGE

void doc_attr_page_refresh(Tdocument *doc, int forced) 
{
  if( doc->dtd_loaded == -1 )
    doc_load_dtd(doc);
  if( doc->dtd_loaded != -1 )
    att_page_refresh(HTML_doctypes[doc->dtd_loaded].elements, forced,
		     gtk_editable_get_position(GTK_EDITABLE(doc->textbox)));
}

#endif
/* this function generates a GList with all document filenames
relative to the current document, for use in <A> or <form> and 
so on. the list and the filenames have to be freed! */
GList *generate_relative_doc_list() {
	GList *retlist = NULL;

	if (main_v->current_document->filename) {
		GList *tmplist;
		tmplist = g_list_first(main_v->documentlist);
		while (tmplist) {
			if ((Tdocument *) tmplist->data != main_v->current_document) {
				gchar *newlink;
				newlink = create_relative_link_to(main_v->current_document->filename, ((Tdocument *)tmplist->data)->filename);
				if (newlink) {
					retlist = g_list_append(retlist, newlink);
				}
			}
			tmplist = g_list_next(tmplist);
		}
	}
	return retlist;
}


