#include <signal.h>
#include <locale.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>

#include "../include/disk.h"
#include "../include/string.h"

#include "guiutils.h"
#include "cdialog.h"
#include "fprompt.h"
#include "clipboard.h"
#include "csd.h"
#include "fsd.h"
#include "fb.h"
#include "pdialog.h"

#include "editor.h"
#include "editorfio.h"
#include "viewer.h"
#include "viewerfio.h"
#include "pref.h"
#include "preffio.h"
#include "prefop.h"
#include "aboutdialog.h"
#include "manedit.h"
#include "maneditop.h"
#include "config.h"

#include "images/icon_manpage_heading_20x20.xpm"
#include "images/icon_manpage_section_20x20.xpm"
#include "images/icon_folder_closed_20x20.xpm"
#include "images/icon_folder_opened_20x20.xpm"
#include "images/icon_manual_closed_20x20.xpm"
#include "images/icon_manual_opened_20x20.xpm"


static void MEditSignalCB(int s);

static gboolean load_font_to_style(GtkStyle *style_ptr, const gchar *font_name);

gint MEditInit(medit_core_struct *core_ptr, int argc, char **argv);
gint MEditManage(gpointer data);
void MEditShutdown(medit_core_struct *core_ptr);


static int segfault_count = 0;
static guint manage_timeout_cb_id = (guint)(-1);

gboolean need_close_all_windows = FALSE;
medit_core_struct *context_core_ptr = NULL;
gboolean use_text_delete;


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Signal handler.
 */
void MEditSignalCB(int s)
{
	static gboolean doing_crash_save = FALSE;

	switch(s)
	{
	  case SIGINT: 
	  case SIGTERM:
	  case SIGKILL:
	    need_close_all_windows = TRUE;
	    break;

	  case SIGSEGV:
	    signal(SIGSEGV, MEditSignalCB);	/* Watch for SIGSEGV again */
	    segfault_count++;			/* Increment SIGSEGV count */
	    /* Print warning of segmentation fault */
	    g_printerr(
		"%s triggered a segmentation fault (%i times)\n",
		PROG_NAME,
		segfault_count
	    );
	    /* Already doing crash save? */
	    if(doing_crash_save)
	    {

	    }
	    else
	    {
		/* Mark and perform emergency save procedure */
		doing_crash_save = TRUE;
		MEditDoEmergencySave(context_core_ptr);
	    }
	    /* Got 3 or more segfaults? */
	    if(segfault_count >= 3)
	    {
		g_printerr(
		    "%s attempting immediate process exit().\n",
		    PROG_NAME
		);
		exit(1);
	    }
	    else
	    {
		need_close_all_windows = TRUE;
	    }
	    break;

	  case SIGPIPE:
	    signal(SIGPIPE, MEditSignalCB);
	    break;
	}
}


/*
 *	Macro used in MEditInit() to load fonts to styles.
 *
 *	Returns TRUE if the font was not able to be loaded, otherwise
 *	FALSE on success.
 */
static gboolean load_font_to_style(GtkStyle *style_ptr, const gchar *font_name)
{
	GdkFont *new_font = gdk_font_load(font_name);

	/* Could not load new font? */
	if(new_font == NULL)
	    return(TRUE);

	/* Unref current font on style structure */
	if(style_ptr->font != NULL)
	    gdk_font_unref(style_ptr->font);

	/* Set newly loaded font to style */
	style_ptr->font = new_font;

	return(FALSE);
}


/*
 *	Initializes all resources for the core structure.
 */
gint MEditInit(medit_core_struct *core_ptr, gint argc, gchar **argv)
{
	gint i, status;
	const gchar *s, *arg;
	gboolean startup_viewer = FALSE;
	gint editor_num = -1;
	gint viewer_num = -1;
	editor_struct *editor = NULL;
	viewer_struct *viewer = NULL;
	const gchar *filename = NULL;
	gchar *rcfile;
	GtkStyle *style_standard;
	GdkColormap *colormap;
	GdkWindow *window = (GdkWindow *)GDK_ROOT_PARENT();
	medit_cursors_list_struct *cursors_list;
	medit_styles_list_struct *styles_list;
	medit_pixmaps_list_struct *pixmaps_list;
	medit_fetype_list_struct *fetype_list;


	if(core_ptr == NULL)
	    return(-1);

	/* Set path to default rcfile, coppied value */
	s = g_getenv("HOME");
	if(s == NULL)
	    s = "/";
	s = PrefixPaths(s, MEDIT_RCFILE_LOCAL);
	if(s == NULL)
	    s = MEDIT_RCFILE_LOCAL;
	rcfile = STRDUP(s);


	/* Reset values on core */
	core_ptr->untitled_count = 0;

	core_ptr->alt_key_state = FALSE;
	core_ptr->control_key_state = FALSE;
	core_ptr->shift_key_state = FALSE;


	/* Parse arguments */
	for(i = 1; i < argc; i++)
	{
	    arg = argv[i];
	    if(arg == NULL)
		continue;

	    /* Start up with viewer? */
	    if(!g_strcasecmp(arg, "--viewer") ||
	       !g_strcasecmp(arg, "-viewer") ||
	       !g_strcasecmp(arg, "--view") ||
	       !g_strcasecmp(arg, "-view") ||
	       !g_strcasecmp(arg, "--manview") ||
	       !g_strcasecmp(arg, "-manview")
	    )
	    {
		startup_viewer = TRUE;
	    }
	    /* Configuration file? */
	    else if(!g_strcasecmp(arg, "--config") ||
		    !g_strcasecmp(arg, "-config") ||
		    !g_strcasecmp(arg, "--rcfile") ||
		    !g_strcasecmp(arg, "-rcfile") ||
		    !g_strcasecmp(arg, "-f")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		if(arg != NULL)
		{
		    g_free(rcfile);
		    rcfile = STRDUP(arg);
		}
		else
		{
		    g_printerr(
			"%s: Requires argument.\n",
			argv[i - 1]
		    );
		}
	    }
	    /* Filename to load on startup? */
	    else if((*arg != '-') &&
		    (*arg != '+')
	    )
	    {
		filename = arg;
	    }
	}


	/* Get default gtk style */
	style_standard = gtk_widget_get_default_style();


	/* Load cursors */
	cursors_list = &core_ptr->cursors_list;
	if(TRUE)
	{
	    cursors_list->busy = gdk_cursor_new(GDK_WATCH);
	    cursors_list->text = gdk_cursor_new(GDK_XTERM);
	}


	/* Load styles */
	styles_list = &core_ptr->styles_list;
	colormap = gdk_colormap_get_system();
	if((window != NULL) && (style_standard != NULL) &&
	   (colormap != NULL)
	)
	{
	    GtkStyle *style_ptr;
	    GdkColor c;

	    /* Each style must have a loaded GdkFont and may have
	     * allocated GdkColors.
	     */

	    /* Heading 1 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING1);
	    }
	    styles_list->heading1_text = style_ptr;

	    /* Heading 2 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		if(load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING2))
		    load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING3);
	    }
	    styles_list->heading2_text = style_ptr;

	    /* Heading 3 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING3);
	    }
	    styles_list->heading3_text = style_ptr;

	    /* Heading 4 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		if(load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING4))
		    load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING5);
	    }
	    styles_list->heading4_text = style_ptr;

	    /* Heading 5 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING5);
	    }
	    styles_list->heading5_text = style_ptr;

	    /* Heading 6 */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING6);
	    }
	    styles_list->heading6_text = style_ptr;


	    /* Heading 1 reverse */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		c = style_ptr->fg[GTK_STATE_NORMAL];
		style_ptr->fg[GTK_STATE_NORMAL] =
		    style_ptr->bg[GTK_STATE_NORMAL];
		style_ptr->bg[GTK_STATE_NORMAL] = c;

		gdk_color_alloc(colormap, &style_ptr->fg[GTK_STATE_NORMAL]);
		gdk_color_alloc(colormap, &style_ptr->bg[GTK_STATE_NORMAL]);

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING1_REV);
	    }
	    styles_list->heading1_rev_text = style_ptr;

	    /* Heading 2 reverse */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		c = style_ptr->fg[GTK_STATE_NORMAL];
		style_ptr->fg[GTK_STATE_NORMAL] =   
		    style_ptr->bg[GTK_STATE_NORMAL];
		style_ptr->bg[GTK_STATE_NORMAL] = c;

		gdk_color_alloc(colormap, &style_ptr->fg[GTK_STATE_NORMAL]);
		gdk_color_alloc(colormap, &style_ptr->bg[GTK_STATE_NORMAL]);

		if(load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING2_REV))
		    load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING3_REV);
	    }
	    styles_list->heading2_rev_text = style_ptr;

	    /* Heading 3 reverse */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		c = style_ptr->fg[GTK_STATE_NORMAL];
		style_ptr->fg[GTK_STATE_NORMAL] =
		    style_ptr->bg[GTK_STATE_NORMAL];
		style_ptr->bg[GTK_STATE_NORMAL] = c;

		gdk_color_alloc(colormap, &style_ptr->fg[GTK_STATE_NORMAL]);
		gdk_color_alloc(colormap, &style_ptr->bg[GTK_STATE_NORMAL]);

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING3_REV);
	    }
	    styles_list->heading3_rev_text = style_ptr;

	    /* Heading 4 reverse */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		c = style_ptr->fg[GTK_STATE_NORMAL];
		style_ptr->fg[GTK_STATE_NORMAL] =
		    style_ptr->bg[GTK_STATE_NORMAL];
		style_ptr->bg[GTK_STATE_NORMAL] = c;

		gdk_color_alloc(colormap, &style_ptr->fg[GTK_STATE_NORMAL]);
		gdk_color_alloc(colormap, &style_ptr->bg[GTK_STATE_NORMAL]);

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_HEADING4_REV);
	    }
	    styles_list->heading4_rev_text = style_ptr;


	    /* Editing text standard */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_EDIT_TEXT_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_EDIT_STD);
	    }
	    styles_list->edit_text_standard = style_ptr;

	    /* Editing text background */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_EDIT_TEXT_BG, &c);
		style_ptr->base[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_EDIT_STD);
	    }
	    styles_list->edit_text_background = style_ptr;

	    /* Editing text tag deliminator */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_EDIT_TEXT_DELIM_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_EDIT_STD);
	    }
	    styles_list->edit_text_tag_deliminator = style_ptr;

	    /* Editing text XML tag text */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_EDIT_TEXT_TAG_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_EDIT_STD);
	    }
	    styles_list->edit_text_tag_text = style_ptr;

	    /* Editing text XML symbol representations text */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_EDIT_TEXT_SYMREP_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_EDIT_STD);
	    }
	    styles_list->edit_text_tag_symrep = style_ptr;


	    /* Manpage output text standard */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_MANPAGE_TEXT_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_MANPAGE_STD);
	    }
	    styles_list->manpage_text_standard = style_ptr;

	    /* Manpage output text background */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_MANPAGE_TEXT_BG, &c);
		style_ptr->base[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_MANPAGE_STD);
	    }
	    styles_list->manpage_text_background = style_ptr;

	    /* Manpage output text bold */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_MANPAGE_TEXT_BOLD_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_MANPAGE_BOLD);
	    }
	    styles_list->manpage_text_bold = style_ptr;

	    /* Manpage output text underline */
	    style_ptr = gtk_style_copy(style_standard);
	    if(style_ptr != NULL)
	    {
		gdk_color_parse(MEDIT_CSTR_MANPAGE_TEXT_UNDERLINE_FG, &c);
		style_ptr->fg[GTK_STATE_NORMAL] = c;

		load_font_to_style(style_ptr, MEDIT_FONT_NAME_MANPAGE_UNDERLINE);
	    }
	    styles_list->manpage_text_underline = style_ptr;


	}


	/* Load pixmaps list */
	pixmaps_list = &core_ptr->pixmaps_list;
	if((window != NULL) && (style_standard != NULL))
	{
	    GdkPixmap **pixmap;
	    GdkBitmap **mask;
	    u_int8_t **data;

#define DO_LOAD_PIXMAP	\
{ \
 (*pixmap) = gdk_pixmap_create_from_xpm_d( \
  window, mask, \
  &style_standard->bg[GTK_STATE_NORMAL], \
  (gchar **)data \
 ); \
}
	    pixmap = &pixmaps_list->folder_closed_20x20;
	    mask = &pixmaps_list->folder_closed_20x20_mask;
	    data = (u_int8_t **)icon_folder_closed_20x20_xpm;
	    DO_LOAD_PIXMAP

	    pixmap = &pixmaps_list->folder_opened_20x20;
	    mask = &pixmaps_list->folder_opened_20x20_mask;
	    data = (u_int8_t **)icon_folder_opened_20x20_xpm;
	    DO_LOAD_PIXMAP


	    pixmap = &pixmaps_list->manual_closed_20x20;
	    mask = &pixmaps_list->manual_closed_20x20_mask;
	    data = (u_int8_t **)icon_manual_closed_20x20_xpm;
	    DO_LOAD_PIXMAP

	    pixmap = &pixmaps_list->manual_opened_20x20;
	    mask = &pixmaps_list->manual_opened_20x20_mask;
	    data = (u_int8_t **)icon_manual_opened_20x20_xpm;
	    DO_LOAD_PIXMAP


	    pixmap = &pixmaps_list->manpage_heading_20x20;
	    mask = &pixmaps_list->manpage_heading_20x20_mask;
	    data = (u_int8_t **)icon_manpage_heading_20x20_xpm;
	    DO_LOAD_PIXMAP

	    pixmap = &pixmaps_list->manpage_section_20x20;
	    mask = &pixmaps_list->manpage_section_20x20_mask;
	    data = (u_int8_t **)icon_manpage_section_20x20_xpm;
	    DO_LOAD_PIXMAP

#undef DO_LOAD_PIXMAP
	}


	/* Load file extension types list */
	fetype_list = &core_ptr->fetype_list;
	if(TRUE)
	{
	    fb_type_struct ***list = &fetype_list->manual_page;
	    gint *total = &fetype_list->total_manual_pages;

	    *list = NULL;
	    *total = 0;

	    FileBrowserTypeListNew(
		list, total,
		".1 .2 .3 .4 .5 .6 .bz2 .gz",
		"Manual page"
	    );
	    FileBrowserTypeListNew(
		list, total,
		".mpt",
		"Manual page template"
	    );
	    FileBrowserTypeListNew(
		list, total,
		"*.*",
		"All files"
	    );
	}


	/* Create Preferences & Options window */
	core_ptr->pref = PrefNew(core_ptr);

	/* Load preferences from file */
	if(rcfile != NULL)
	{
	    status = PrefLoadFromFile(core_ptr->pref, rcfile);

	    /* Load preferences successfully? */
	    if(!status)
	    {
		/* Apply preferences to realize newly loaded values
		 * onto core structure and other resources.
		 */
		PrefDoApply(core_ptr->pref);
	    }
	}

	/* Check preferences for what kind of window to start up with,
	 * if we are not starting up with anything then set startup_viewer
	 * to TRUE because we must start up with *something*.
	 */
	if(!PrefParmGetValueB(core_ptr->pref, MEDIT_PREF_PARM_STARTUP_EDITOR) &&
	   !PrefParmGetValueB(core_ptr->pref, MEDIT_PREF_PARM_STARTUP_VIEWER)
	)
	{
	    g_printerr(
"%s: Warning: No window defined to start up with, default to viewer.\n",
		PROG_NAME
	    );
	    startup_viewer = TRUE;
	}

	/* Create a new editor on startup? */
	if(!startup_viewer &&
	   PrefParmGetValueB(core_ptr->pref, MEDIT_PREF_PARM_STARTUP_EDITOR)
	)
	    editor = EditorNew(core_ptr);
	else
	    editor = NULL;
	if(editor != NULL)
	{
	    if(core_ptr->total_editors < 0)
		core_ptr->total_editors = 0;

	    editor_num = core_ptr->total_editors;
	    core_ptr->total_editors++;
	    core_ptr->editor = (editor_struct **)g_realloc(
		core_ptr->editor,
		core_ptr->total_editors * sizeof(editor_struct *)
	    );
	    if(core_ptr->editor == NULL)
	    {
		core_ptr->total_editors = 0;

		EditorDelete(editor);
		editor = NULL;
	    }
	    else
	    {
		core_ptr->editor[editor_num] = editor;
		EditorMap(editor);

		if(filename != NULL)
		{
		    EditorFileLoad(editor, filename, NULL, FALSE);
		}
	    }
	}

	/* Create a new viewer on startup? */
	if((startup_viewer) ||
	   PrefParmGetValueB(core_ptr->pref, MEDIT_PREF_PARM_STARTUP_VIEWER)
	)
	    viewer = ViewerNew(
		core_ptr,
		(core_ptr->total_editors > 0) ? 0 : -1
	    );
	else
	    viewer = NULL;
	if(viewer != NULL)
	{
	    if(core_ptr->total_viewers < 0)
		core_ptr->total_viewers = 0;
	      
	    viewer_num = core_ptr->total_viewers;
	    core_ptr->total_viewers++;
	    core_ptr->viewer = (viewer_struct **)g_realloc(
		core_ptr->viewer,
		core_ptr->total_viewers * sizeof(viewer_struct *)
	    );
	    if(core_ptr->viewer == NULL)
	    {
		core_ptr->total_viewers = 0;

		ViewerDelete(viewer);
		viewer = NULL;
	    }
	    else
	    {
		core_ptr->viewer[viewer_num] = viewer;
		ViewerMap(viewer);

		if(filename != NULL)
		{
		    ViewerSetBusy(viewer);
		    ViewerTextDelete(viewer, 0, -1);
		    ViewerTextInsertPosition(viewer, 0);
		    ViewerLoadFile(viewer, filename, filename);
		    ViewerSetReady(viewer);
		}
	    }
	}


	/* Call crash check recovery function to prompt and recover any
	 * files saved by an emergency save in a previous run of this
	 * program.
	 */
	MEditDoCrashRecovery(core_ptr);


	/* Delete coppied rcfile path */
	g_free(rcfile);
	rcfile = NULL;

	return(0);
}

/*
 *	Manage (timeout) function.
 */
gint MEditManage(gpointer data)
{
	static gboolean reenterant = FALSE;
	gboolean need_break, still_processing;
	gint i, status;
	editor_struct *editor;
	viewer_struct *viewer;
	GtkCTreeNode *branch;
	GtkCTreeRow *branch_row;
	editor_item_struct *item;
	medit_core_struct *core_ptr = (medit_core_struct *)data;
	if(core_ptr == NULL)
	    return(FALSE);

	if(reenterant)
	    return(TRUE);
	else
	    reenterant = TRUE;


	/* Need to close all windows? */
	if(need_close_all_windows) 
	{
	    /* Check if any dialogs are in query, break query as needed
	     * and return. Let next call to this function handle rest of
	     * closing all windows
	     */
	    if(CDialogIsQuery())
	    {
		CDialogBreakQuery();
		reenterant = FALSE;
		return(TRUE);
	    }
	    if(FileBrowserIsQuery())
	    {
		FileBrowserBreakQuery();
		reenterant = FALSE; 
		return(TRUE);
	    }
	    if(CSDIsQuery())
	    {
		CSDBreakQuery();
		reenterant = FALSE; 
		return(TRUE);
	    }
	    if(FSDIsQuery())
	    {
		FSDBreakQuery();
		reenterant = FALSE;
		return(TRUE);
	    }
	    if(PDialogIsQuery())
	    {
		PDialogBreakQuery();
		reenterant = FALSE;
		return(TRUE);
	    }
	    /* All dialogs not in query */


	    /* Reset need_close_all_windows back to FALSE and then check
	     * a few things.
	     */
	    need_close_all_windows = FALSE;

	    /* Check if any windows are processing */
	    still_processing = FALSE;

	    /* Check each editor to see if any are still processing */
	    for(i = 0; i < core_ptr->total_editors; i++)
	    {
		editor = core_ptr->editor[i];
		if(editor == NULL)
		    continue;
		
		if(editor->initialized && editor->processing)
		{
		    still_processing = TRUE;
		    break;
		}
	    }
	    /* Check each viewer to see if any are still processing */
	    for(i = 0; i < core_ptr->total_viewers; i++)
	    {
		viewer = core_ptr->viewer[i];
		if(viewer == NULL)
		    continue;

		if(viewer->initialized && viewer->processing)
		{
		    still_processing = TRUE;
		    break;
		}
	    }
	    /* One or more windows still processing? */
	    if(still_processing)
	    {
		/* Return now, next call to timeout won't request to
		 * close all windows.
		 */
		reenterant = FALSE;
		return(TRUE);
	    }


	    /* Go through each editor and check for changes */
	    for(i = 0; i < core_ptr->total_editors; i++)
	    {
		editor = core_ptr->editor[i];
		if(editor == NULL)
		    continue;

		if(editor->initialized)
		{
		    need_break = FALSE;
		    branch = EditorItemGetFirstToplevel(editor);
		    while(branch != NULL)
		    {
			item = EditorBranchGetData(
			    (GtkCTree *)editor->layout_ctree,
			    branch
			);
			if(item->has_changes)
			{
			    need_break = TRUE;
			    break;
			}

			branch_row = GTK_CTREE_ROW(branch);
			branch = ((branch_row == NULL) ?
			    NULL : branch_row->sibling
			);
		    }

		    if(need_break)
			break;
		}
	    }
	    if(i < core_ptr->total_editors)
	    {
		/* Found some changed data that was not saved */
		status = CDialogGetResponse(
"Changes detected!",
"There are changes to one or more of the editors\n\
that have not been saved. Are you sure you want to\n\
exit and discard those changes?",
"The program is about to exit and some changed data have\n\
not been saved. If you say no then the program will not\n\
exit and you will have a chance to save those changes, or\n\
you can say yes to exit and discard those changes.",
		    CDIALOG_ICON_WARNING,
		    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
		    CDIALOG_BTNFLAG_HELP,
		    CDIALOG_BTNFLAG_NO
		);
		switch(status)
		{
		  case CDIALOG_RESPONSE_YES:
		  case CDIALOG_RESPONSE_YES_TO_ALL: 
		  case CDIALOG_RESPONSE_OK:
		    need_close_all_windows = TRUE;
		    break;
		}
	    }
	    else
	    {
		/* No changed data found, can continue to close all
		 * windows.
		 */
		need_close_all_windows = TRUE;
	    }

/* Go through other types of windows to check for changes? */


	    /* Definatly sure to close all windows? */
	    if(need_close_all_windows)
	    {
		/* Go through each editor and close all editors
		 * that are initialized by resetting and unmapping them.
		 */
		for(i = 0; i < core_ptr->total_editors; i++)
		{
		    editor = core_ptr->editor[i];
		    if(editor == NULL)
			continue;

		    if(!editor->initialized)
			continue;

		    EditorRecordPositions(editor);
		    EditorReset(editor, editor->map_state);
		}
		/* Viewer windows */
		for(i = 0; i < core_ptr->total_viewers; i++)
		{
		    viewer = core_ptr->viewer[i];
		    if(viewer == NULL)
			continue;
	    
		    if(!viewer->initialized)
			continue;

		    ViewerRecordPositions(viewer);
		    ViewerReset(viewer, viewer->map_state);
		}
		/* Add support for resetting and unmapping other types of
		 * windows here.
		 */

	    }
	}


	/* Go through each editor, checking if one or more are
	 * initialized and/or mapped. This is to ensure this program
	 * keeps running while one or more Manual Page Editor editor is
	 * in use, otherwise this program will start shutting down.
	 */
	for(i = 0; i < core_ptr->total_editors; i++)
	{
	    editor = core_ptr->editor[i];
	    if(editor == NULL)
		continue;

	    /* Is this editor initialized and mapped? */
	    if(editor->initialized && editor->map_state)
		break;
	}
	/* Atleast one editor was initialized and mapped? */
	if(i < core_ptr->total_editors)
	{
	    /* Atleast one editor is still in use, keep running */
	}
	else
	{
	    /* All editors were uninitialized and/or unmapped */

	    /* Check if any viewers are initialized and mapped */
	    for(i = 0; i < core_ptr->total_viewers; i++)
	    {
		viewer = core_ptr->viewer[i];
		if(viewer == NULL)
		    continue;

		/* Is this viewer initialized and mapped? */
		if(viewer->initialized && viewer->map_state)
		    break;
	    }
	    /* Atleast one viewer was initialized and mapped? */
	    if(i < core_ptr->total_viewers)
	    {
		/* Atleast one viewer is still in use, keep running */
	    }
	    else
	    {
		/* All viewers were uninitialized and/or unmapped */

		/* Break out of main GUI loop */
		gtk_main_quit();

		/* Remove callback to this function */
		if(manage_timeout_cb_id != (guint)(-1))
		{
		    gtk_timeout_remove(manage_timeout_cb_id);
		    manage_timeout_cb_id = (guint)(-1);
		}

		/* Flush events */
		while(gtk_events_pending() > 0)
		    gtk_main_iteration();

		/* Return false so this timeout is not called again */
		reenterant = FALSE;
		return(FALSE);
	    }
	}

	reenterant = FALSE;
	return(TRUE);
}

/*
 *	Deletes all resources on the core structure but does not
 *	delete the structure itself.
 */
void MEditShutdown(medit_core_struct *core_ptr)
{
	gint i;
	gchar *rcfile;
	medit_cursors_list_struct *cursors_list;
	medit_pixmaps_list_struct *pixmaps_list;
	medit_styles_list_struct *styles_list;
	medit_fetype_list_struct *fetype_list;

	if(core_ptr == NULL)
	    return;

	/* Delete all editors */
	for(i = 0; i < core_ptr->total_editors; i++)
	{
	    EditorDelete(core_ptr->editor[i]);
	    core_ptr->editor[i] = NULL;
	}
	g_free(core_ptr->editor);
	core_ptr->editor = NULL;
	core_ptr->total_editors = 0;

	/* Delete all viewers */
	for(i = 0; i < core_ptr->total_viewers; i++)
	{
	    ViewerDelete(core_ptr->viewer[i]);
	    core_ptr->viewer[i] = NULL;
	}
	g_free(core_ptr->viewer);
	core_ptr->viewer = NULL;
	core_ptr->total_viewers = 0;

	/* Get path to rcfile and save preferences */
	rcfile = (char *)PrefParmGetValueP(
	    core_ptr->pref, MEDIT_PREF_PARM_LOCATIONS_RCFILE
	);
	if(rcfile != NULL)
	    rcfile = STRDUP(rcfile);
	if(rcfile != NULL)
	    PrefSaveToFile(core_ptr->pref, rcfile);
	g_free(rcfile);
	rcfile = NULL;

	/* Delete Preferences & Options window */
	PrefDelete(core_ptr->pref);
	core_ptr->pref = NULL;

	/* Delete about dialog */
	AboutDialogDelete(core_ptr->about);
	core_ptr->about = NULL;



	/* Unref all cursors */
	cursors_list = &core_ptr->cursors_list;
	if(TRUE)
	{
	    GdkCursor **cursor;

#define UNREF_CURSOR	{	\
 if((*cursor) != NULL) {	\
  gdk_cursor_destroy(*cursor);	\
  (*cursor) = NULL;		\
} }
	    cursor = &cursors_list->busy;
	    UNREF_CURSOR

	    cursor = &cursors_list->text;
	    UNREF_CURSOR

#undef UNREF_CURSOR
	}


	/* Unref all pixmaps */
	pixmaps_list = &core_ptr->pixmaps_list;
	if(TRUE)
	{
	    GdkPixmap **pixmap;
	    GdkBitmap **mask;

#define UNREF_PIXMAP	{	\
 if(*pixmap != NULL) {		\
  gdk_pixmap_unref(*pixmap);	\
  *pixmap = NULL;		\
 }				\
 if((*mask) != NULL) {		\
  gdk_bitmap_unref(*mask);	\
  *mask = NULL;			\
 }				\
}
	    pixmap = &pixmaps_list->medit_icon_48x48;
	    mask = &pixmaps_list->medit_icon_48x48_mask;
	    UNREF_PIXMAP


	    pixmap = &pixmaps_list->folder_closed_20x20;
	    mask = &pixmaps_list->folder_closed_20x20_mask;
	    UNREF_PIXMAP

	    pixmap = &pixmaps_list->folder_opened_20x20;
	    mask = &pixmaps_list->folder_opened_20x20_mask;
	    UNREF_PIXMAP


	    pixmap = &pixmaps_list->manual_closed_20x20;
	    mask = &pixmaps_list->manual_closed_20x20_mask;
	    UNREF_PIXMAP

	    pixmap = &pixmaps_list->manual_opened_20x20;
	    mask = &pixmaps_list->manual_opened_20x20_mask;
	    UNREF_PIXMAP


	    pixmap = &pixmaps_list->manpage_heading_20x20;
	    mask = &pixmaps_list->manpage_heading_20x20_mask;
	    UNREF_PIXMAP

	    pixmap = &pixmaps_list->manpage_section_20x20;
	    mask = &pixmaps_list->manpage_section_20x20_mask;
	    UNREF_PIXMAP

#undef UNREF_PIXMAP
	}


	/* Unref all styles */
	styles_list = &core_ptr->styles_list;
	if(TRUE)
	{
#define UNREF_STYLE(p)	{	\
if(*(p) != NULL) {		\
 gtk_style_unref(*(p));		\
 *(p) = NULL;			\
} }
	    styles_list->standard = NULL;	/* Came from GTK internal, do
						 * not unref it */

	    UNREF_STYLE(&styles_list->heading1_text);
	    UNREF_STYLE(&styles_list->heading2_text);
	    UNREF_STYLE(&styles_list->heading3_text);
	    UNREF_STYLE(&styles_list->heading4_text);
	    UNREF_STYLE(&styles_list->heading5_text);
	    UNREF_STYLE(&styles_list->heading6_text);

	    UNREF_STYLE(&styles_list->heading1_rev_text);
	    UNREF_STYLE(&styles_list->heading2_rev_text);
	    UNREF_STYLE(&styles_list->heading3_rev_text);
	    UNREF_STYLE(&styles_list->heading4_rev_text);

	    UNREF_STYLE(&styles_list->edit_text_standard);
	    UNREF_STYLE(&styles_list->edit_text_background);
	    UNREF_STYLE(&styles_list->edit_text_tag_deliminator);
	    UNREF_STYLE(&styles_list->edit_text_tag_text);
	    UNREF_STYLE(&styles_list->edit_text_tag_symrep);

	    UNREF_STYLE(&styles_list->manpage_text_standard);
	    UNREF_STYLE(&styles_list->manpage_text_background);
	    UNREF_STYLE(&styles_list->manpage_text_bold);
	    UNREF_STYLE(&styles_list->manpage_text_underline);
#undef UNREF_STYLE
	}


	/* Delete all file extension types list */
	fetype_list = &core_ptr->fetype_list;
	if(fetype_list != NULL)
	{
	    fb_type_struct ***fb_type;
	    int *total_fb_types;

#define DELETE_FETYPE_LIST	{			\
 FileBrowserDeleteTypeList(*fb_type, *total_fb_types);	\
 *fb_type = NULL;					\
 *total_fb_types = 0;					\
}

	    fb_type = &fetype_list->manual_page;
	    total_fb_types = &fetype_list->total_manual_pages;

#undef DELETE_FETYPE_LIST
	}

	memset(core_ptr, 0x00, sizeof(medit_core_struct));
}


int main(int argc, char **argv)
{
	gint i, status;
	const gchar *arg;
	medit_core_struct *core_ptr = MEDIT_CORE(g_malloc0(
	    sizeof(medit_core_struct)
	));
	if(core_ptr == NULL)
	    return(1);


	/* Reset globals */
	segfault_count = 0;
	manage_timeout_cb_id = (guint)(-1);
	need_close_all_windows = FALSE;

	/* Set global core pointer */
	context_core_ptr = core_ptr;

use_text_delete = TRUE;


	/* Set up signals */
	signal(SIGINT, MEditSignalCB);
	signal(SIGTERM, MEditSignalCB);
	signal(SIGSEGV, MEditSignalCB);
	signal(SIGKILL, MEditSignalCB);
	signal(SIGPIPE, MEditSignalCB);


	/* Parse basic arguments */
	for(i = 1; i < argc; i++)
	{
	    arg = argv[i];
	    if(arg == NULL)
		continue;

	    /* Help */
	    if(g_strcasepfx(arg, "-h") ||
	       g_strcasepfx(arg, "--h") ||
	       g_strcasepfx(arg, "-?") ||
	       g_strcasepfx(arg, "/?")
	    )
	    {
		g_print(PROG_USAGE_MESG);
		return(0);
	    }
	    /* Version */
	    else if(g_strcasepfx(arg, "-ver") ||
		    g_strcasepfx(arg, "--ver")
	    )
	    {
		g_print(
		    "%s Version %s\n%s\n",
		    PROG_NAME_FULL, PROG_VERSION,
		    PROG_COPYRIGHT
		);
		return(0);
	    }
	    else
	    {
		/* Leave other arguments alone, they will be handled
		 * later
		 */
	    }
	}


	/* Set up localization code */
	setlocale(LC_ALL, "");

	gtk_set_locale();

	/* Initialize GTK */
	gtk_init(&argc, &argv);

	/* Initialize dialogs resources */
	CDialogInit();
	FPromptInit();
	ClipboardBrowserInit();
	FileBrowserInit();
	CSDInit();
	FSDInit();
	PDialogInit();

	/* Initialize Manual Page Editor's resources */
	status = MEditInit(core_ptr, argc, argv);
	if(status)
	{
	    MEditShutdown(core_ptr);
	    return(1);
	}

	/* Set timeout to management function */
	manage_timeout_cb_id = gtk_timeout_add(
	    250l,
	    MEditManage, core_ptr
	);

	/* Enter main GTK loop */
	gtk_main();


	/* Shutdown Manual Page Editor's resources */
	MEditShutdown(core_ptr);
	context_core_ptr = NULL;
	g_free(core_ptr);
	core_ptr = NULL;

	/* Shutdown dialogs */
	CDialogShutdown();
	FPromptShutdown();
	ClipboardBrowserShutdown();
	FileBrowserShutdown();
	CSDShutdown();
	FSDShutdown();
	PDialogShutdown();

	return(0);
}
