Logo Search packages:      
Sourcecode: manedit version File versions  Download package

editorcb.c

#include <string.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <unistd.h>

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

#include "guiutils.h"
#include "cdialog.h"
#include "clipboard.h"

#include "editor.h"
#include "editorfip.h"
#include "editorundo.h"
#include "editorcb.h"
#include "editordnd.h"
#include "editorop.h"
#include "editorfio.h"
#include "pref.h"
#include "prefop.h"
#include "manedit.h"
#include "config.h"


void EditorItemDestroyCB(gpointer data);

gint EditorKeyEventCB(GtkWidget *widget, GdkEventKey *key, gpointer data);
void EditorDestroyCB(GtkObject *object, gpointer data);
gint EditorCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void EditorCloseMCB(GtkWidget *widget, gpointer data);
void EditorCloseAllMCB(GtkWidget *widget, gpointer data);

void EditorManualNewCB(GtkWidget *widget, gpointer data);
void EditorManualOpenCB(GtkWidget *widget, gpointer data);
void EditorManualNewFromTemplateCB(GtkWidget *widget, gpointer data);
void EditorManualSaveCB(GtkWidget *widget, gpointer data);
void EditorManualSaveAsCB(GtkWidget *widget, gpointer data);
void EditorManualRevertCB(GtkWidget *widget, gpointer data);
void EditorManualCloseCB(GtkWidget *widget, gpointer data);

void EditorManualAddHeaderCB(GtkWidget *widget, gpointer data);
void EditorManualAddSectionCB(GtkWidget *widget, gpointer data);
void EditorManualRemoveSectionCB(GtkWidget *widget, gpointer data);
void EditorManualPropertiesCB(GtkWidget *widget, gpointer data);
void EditorManualPreviewCB(GtkWidget *widget, gpointer data);
void EditorManualSyntaxHighlightToggleCB(GtkWidget *widget, gpointer data);

void EditorUndoCB(GtkWidget *widget, gpointer data);
void EditorRedoCB(GtkWidget *widget, gpointer data);
void EditorCutCB(GtkWidget *widget, gpointer data);
void EditorCopyCB(GtkWidget *widget, gpointer data);
void EditorPasteCB(GtkWidget *widget, gpointer data);
void EditorDeleteCB(GtkWidget *widget, gpointer data);
void EditorClipboardBrowserCB(GtkWidget *widget, gpointer data);
void EditorSelectAllCB(GtkWidget *widget, gpointer data);
void EditorUnselectAllCB(GtkWidget *widget, gpointer data);
void EditorPreferencesCB(GtkWidget *widget, gpointer data);

void EditorFmtParagraphLeftCB(GtkWidget *widget, gpointer data);
void EditorFmtParagraphRightCB(GtkWidget *widget, gpointer data);
void EditorFmtParagraphIndentedCB(GtkWidget *widget, gpointer data);
void EditorFmtListItemCB(GtkWidget *widget, gpointer data);
void EditorFmtBoldCB(GtkWidget *widget, gpointer data);
void EditorFmtUnderlineCB(GtkWidget *widget, gpointer data);
void EditorFmtLineBreakCB(GtkWidget *widget, gpointer data);
void EditorFmtAmpersandCB(GtkWidget *widget, gpointer data);
void EditorFmtLessThanCB(GtkWidget *widget, gpointer data);
void EditorFmtGreaterThanCB(GtkWidget *widget, gpointer data);
void EditorStripTagsCB(GtkWidget *widget, gpointer data);

void EditorMapFIPCB(GtkWidget *widget, gpointer data);

void EditorFindBarFindCB(GtkWidget *widget, gpointer data);
void EditorFindBarFindActivateCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceAllCB(GtkWidget *widget, gpointer data);
void EditorFindBarReplaceEntirePageCB(GtkWidget *widget, gpointer data);
static int EditorFindBarReplaceEntirePageIterationCB(
      editor_struct *editor, GtkCTreeNode *branch,
      char *needle, const char *replacement,
      gboolean case_sensitive
);
void EditorFindBarReplaceActivateCB(GtkWidget *widget, gpointer data);

static void EditorDoFindCB(
      editor_struct *editor, GtkCombo *find_combo, gboolean case_sensitive
);
static void EditorDoReplaceCB(
      editor_struct *editor,
      GtkCombo *find_combo, GtkCombo *replace_combo,
      gboolean case_sensitive
);

static void EditorMenuMapPositionCB(
      GtkMenu *menu, gint *x, gint *y, gpointer data
);
gint EditorMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data);
void EditorButtonMenuMapCB(GtkButton *button, gpointer data);
void EditorMenuHideCB(GtkWidget *widget, gpointer data);

void EditorLayoutCTreeDoExpandCB(GtkWidget *widget, gpointer data);
void EditorLayoutCTreeSelectCB(
      GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void EditorLayoutCTreeUnselectCB(
      GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
);
void EditorLayoutCTreeExpandCB(
      GtkCTree *ctree, GtkCTreeNode *node, gpointer data
);

gint EditorSyntaxHighlightTimeoutCB(gpointer data);

gint EditorTextKeyEventCB(
      GtkEditable *editable, GdkEventKey *key, gpointer data
);
void EditorTextChangeCB(GtkEditable *editable, gpointer data);
void EditorTextInsertCB(
      GtkEditable *editable, const gchar *text, gint length,
      gint *position, gpointer data
);
void EditorTextDeleteCB(
      GtkEditable *editable, gint start_pos, gint end_pos, gpointer data
);


#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)


/*
 *    Set to the pointer of the editor that is currently having
 *    an undo or redo operation done on it, this is to keep track 
 *    of and discard signals like "insert_text" and "delete_text"
 *    which would be sent if text was being reinserted or redeleted
 *    during and undo or redo process.
 */
static editor_struct *editor_processing_undo = NULL;


/*
 *    Destroys a branch's editor_item_struct.
 */
void EditorItemDestroyCB(gpointer data)
{
      gint i, j;
      editor_struct *editor;
      editor_item_struct *item = EDITOR_ITEM(data);
      if(item == NULL)
          return;

      /* Get pointer to corresponding editor for this item */
      editor = item->editor;
      if(editor != NULL)
      {
          GtkCTreeNode *branch = item->this_branch;
          if(branch != NULL)
          {
            /* Editor has this selected? */
            if(editor->selected_branch == branch)
                editor->selected_branch = NULL;

            /* Remove this branch from the editor's layout_trunk list
             * if this branch is found to be in that list
             */
            for(i = 0; i < editor->total_layout_trunks; i++)
            {
                if(editor->layout_trunk[i] == branch)
                {
                  editor->total_layout_trunks--;
                  for(j = i; j < editor->total_layout_trunks; j++)
                      editor->layout_trunk[j] =
                        editor->layout_trunk[j + 1];
                  i--;  /* Back 1, incremented on next loop */
                }
            }
            if(editor->total_layout_trunks < 0)
                editor->total_layout_trunks = 0;
          }
      }

      /* Delete the item and all its allocated resources */
      EditorItemDelete(item);
}

/*
 *    Editor toplevel window key press and release callback.
 */
gint EditorKeyEventCB(GtkWidget *widget, GdkEventKey *key, gpointer data)
{
      gint status = FALSE;
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if((key == NULL) || (editor == NULL))
          return(status);

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return(status);

      /* Handle by event type */
      switch((gint)key->type)
      {
        case GDK_KEY_PRESS:
          /* Handle by key code */
          switch(key->keyval)
          {
            case GDK_Alt_L: case GDK_Alt_R:
            core_ptr->alt_key_state = TRUE;
            break;
            case GDK_Control_L: case GDK_Control_R:
            core_ptr->control_key_state = TRUE;
            break;
            case GDK_Shift_L: case GDK_Shift_R:
            core_ptr->shift_key_state = TRUE;
            break;
          }
          break;

        case GDK_KEY_RELEASE:
          /* Handle by key code */
          switch(key->keyval)
          {
            case GDK_Alt_L: case GDK_Alt_R:
            core_ptr->alt_key_state = FALSE;
            break;
            case GDK_Control_L: case GDK_Control_R:
            core_ptr->control_key_state = FALSE;
            break;
            case GDK_Shift_L: case GDK_Shift_R:
            core_ptr->shift_key_state = FALSE;
            break;
          }
          break;
      }

      return(status);
}

/*
 *    Destroy callback.
 */
void EditorDestroyCB(GtkObject *object, gpointer data)
{

}

/*
 *    Close callback.
 */
gint EditorCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
      gint i;
      gboolean yes_to_all = FALSE;
      GtkCTreeNode *branch;
      GtkWidget *toplevel;
      editor_item_struct *item;
      medit_core_struct *core_ptr;
      medit_fetype_list_struct *fetype_list;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return(TRUE);

      if(editor->processing)
          return(TRUE);

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return(TRUE);

      fetype_list = &core_ptr->fetype_list;
      toplevel = editor->toplevel;


      /* Go through each layout trunk's item data and check if
       * it has changes. Only need to check the toplevel trunk
       * branch node's data since it's has_changes member represents
       * changes on child branches.
       */
      for(i = 0; i < editor->total_layout_trunks; i++)
      {
          branch = editor->layout_trunk[i];
          if(branch == NULL)
            continue;

          item = EditorBranchGetData(
            (GtkCTree *)editor->layout_ctree, branch
          );
          if(item == NULL)
            continue;

          /* Item data not of type file? This would imply something
           * is very wrong since toplevel trunk branches should always
           * have item data of type EditorItemTypeFile
           */
          if(item->type != EditorItemTypeFile)
            continue;

          if(item->has_changes)
          {
            gint status;
            gchar *buf = g_strdup_printf(
                "Save changes to manual page `%s'?",
                item->name
            );
            if(yes_to_all)
            {
                status = CDIALOG_RESPONSE_YES_TO_ALL;
            }
            else
            {
                CDialogSetTransientFor(toplevel);
                status = CDialogGetResponse(
"Save changes?",
                  buf,
"The manual page has changes which have not\n\
been saved. If you say yes then those changes\n\
will be saved. Otherwise the changes will be\n\
discarded.",
                  CDIALOG_ICON_WARNING,
                  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
                  CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL |
                  CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_YES
                );
                CDialogSetTransientFor(NULL);
            }
            g_free(buf);

            /* Check response */
            switch(status)
            {
              case CDIALOG_RESPONSE_YES_TO_ALL:
                yes_to_all = TRUE;
              case CDIALOG_RESPONSE_YES:
              case CDIALOG_RESPONSE_OK:
                /* Request to save, check if the full path is set.
                 * If the full path is not set (regardless of if the
                 * name is set or not, we need to consider that
                 * this file was never saved before and thus we need
                 * to request from the user a `save as'.
                 */
                if(item->full_path == NULL)
                {
                  /* Save as */
                  gboolean status;
                  gchar **path_rtn;
                  gint path_total_rtns;
                  fb_type_struct *type_rtn;

                  FileBrowserSetTransientFor(toplevel);
                  status = FileBrowserGetResponse(
                      "Save Manual Page As",
                      "Save", "Cancel",
                      editor->last_save_as_path,
                      fetype_list->manual_page,
                      fetype_list->total_manual_pages,
                      &path_rtn, &path_total_rtns, &type_rtn
                  );
                  FileBrowserSetTransientFor(NULL);
                  if(status)
                  {
                      if(path_total_rtns > 0)
                      {
                        EditorFileSaveAs(
                            editor, path_rtn[0], branch
                        );
                      }
                      else
                      {
                        /* Did not get any paths, abort entire
                         * close procedure.
                         */
                        return(TRUE);
                      }
                  }
                  else
                  {
                      /* User canceled save as, abort entire close
                       * procedure.
                       */
                      return(TRUE);
                  }
                }
                else
                {
                  /* Save */
                  EditorFileSave(editor, branch);
                }
                break;

              case CDIALOG_RESPONSE_NO:
                break;

              case CDIALOG_RESPONSE_CANCEL:
              case CDIALOG_RESPONSE_NOT_AVAILABLE:
                /* Response not available, abort entire close
                 * procedure.
                 */
                return(TRUE);
                break;
            }
          }
      }

/* Record values from editor back to core structure, like last
 * opened and save paths?
 */

      /* Record editor window positions */
      EditorRecordPositions(editor);

      /* Reset editor and unmap it */
      EditorReset(editor, editor->map_state);


      return(TRUE);
}

/*
 *    Close callback from menu item.
 */
void EditorCloseMCB(GtkWidget *widget, gpointer data)
{
      EditorCloseCB(widget, NULL, data);
}

/*
 *    Close all windows.
 */
void EditorCloseAllMCB(GtkWidget *widget, gpointer data)
{
      /* Set global need_close_all_windows to TRUE, this will be checked
       * when the main manage timeout function MEditManage() is called
       * and it will begin closing each window.
       */
      need_close_all_windows = TRUE;
}


/*
 *    New manual page callback.
 */
void EditorManualNewCB(GtkWidget *widget, gpointer data)
{
      gint i, n;
      GtkCTree *ctree;
      GtkCTreeNode *branch, *branch_insert, *new_branch;
      GtkCTreeRow *branch_row;
      GtkWidget *toplevel;
      gchar *text[1];
      medit_pixmaps_list_struct *pixmaps_list;
      medit_core_struct *core_ptr;
      editor_item_struct *item;
      gchar untitled_title[256];
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      pixmaps_list = &core_ptr->pixmaps_list;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch */
      branch = editor->selected_branch;
      branch = EditorItemGetToplevel(editor, branch);
      if(branch == NULL)
      {
          branch_insert = NULL;
      }
      else
      {
          branch_row = GTK_CTREE_ROW(branch);
          branch_insert = (branch_row != NULL) ?
            branch_row->sibling : NULL;
      }

      /* Now branch_insert should be the branch to insert before or
       * NULL
       */

      /* Allocate a new entry in the layout_trunk pointer array */
      if(editor->total_layout_trunks < 0)
          editor->total_layout_trunks = 0;
      if(branch_insert == NULL)
      {
          n = editor->total_layout_trunks;
      }
      else
      {
          for(n = 0; n < editor->total_layout_trunks; n++)
          {
            if(editor->layout_trunk[n] == branch_insert)
                break;
          }
      }
      if(n > editor->total_layout_trunks)
          n = editor->total_layout_trunks;

      /* Variable n is now set to the index in the layout_trunk pointer
       * array where we want to insert the new branch pointer
       */

      /* Allocate a new pointer */
      editor->total_layout_trunks++;
      editor->layout_trunk = (GtkCTreeNode **)g_realloc(
          editor->layout_trunk,
          editor->total_layout_trunks * sizeof(GtkCTreeNode *)
      );
      if(editor->layout_trunk == NULL)
      {
          editor->total_layout_trunks = 0;
          return;
      }

      /* Shift pointers */
      for(i = editor->total_layout_trunks - 1; i > n; i--)
          editor->layout_trunk[i] = editor->layout_trunk[i - 1]; 


      EditorSetStatusMessage(editor, "Creating new Manual Page...");

      /* Increment untitled count and set untitled_title */
      core_ptr->untitled_count++;
      g_snprintf(
          untitled_title, sizeof(untitled_title),
          "Untitled%i.1",
          core_ptr->untitled_count
      );

      /* Now branch_insert should be the branch to insert before or
       * NULL
       */
      text[0] = untitled_title;
      new_branch = gtk_ctree_insert_node(
          ctree,
          NULL,               /* No parent, it's toplevel */
          branch_insert,            /* Insert before this sibling */
          text,
          MEDIT_LIST_ICON_TEXT_SPACING,
          pixmaps_list->manual_closed_20x20,
          pixmaps_list->manual_closed_20x20_mask,
          pixmaps_list->manual_opened_20x20,
          pixmaps_list->manual_opened_20x20_mask,
          FALSE,              /* Is leaf */
          TRUE                /* Expanded */
      );

      /* Record new branch on layout_trunk pointer array index n */
      editor->layout_trunk[n] = new_branch;

      /* Create editor item structure */
      item = EditorItemNew(
          EditorItemTypeFile,
          editor,
          ctree,
          NULL,         /* Parent branch */
          new_branch,         /* This branch */
          FALSE,        /* Is leaf */
          NULL, 0       /* Lines array */
      );
      if(item != NULL)
      {
          g_free(item->name);
          item->name = STRDUP(text[0]);

          /* No file name full, implies not saved yet */
          g_free(item->full_path);
          item->full_path = NULL;

          item->has_changes = TRUE;
      }
      EditorBranchSetData(
          ctree, new_branch,
          item, EditorItemDestroyCB
      );


      EditorSetStatusMessage(editor, "Created new Manual Page");
      EditorSetStatusProgress(editor, 0.0);


      /* Select new branch */
      EditorBranchSelect(editor, new_branch);

      /* Update menus */
      EditorUpdateMenus(editor);
}

/*
 *    Open manual page callback.
 */
void EditorManualOpenCB(GtkWidget *widget, gpointer data)
{
      GtkCTree *ctree;
      GtkCTreeNode *branch;
      GtkWidget *toplevel;
      medit_fetype_list_struct *fetype_list;
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing) 
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      fetype_list = &core_ptr->fetype_list;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch (may be NULL) */
      branch = editor->selected_branch;


      if(TRUE)
      {
          gboolean status;
          gchar **path_rtn;
          gint path_total_rtns;
          fb_type_struct *type_rtn;
          gchar *buf;

          /* Map file browser for opening of manual page */
          FileBrowserSetTransientFor(toplevel);
          status = FileBrowserGetResponse(
            "Open Manual Page",
            "Open", "Cancel",
            editor->last_open_path,
            fetype_list->manual_page,
            fetype_list->total_manual_pages,
            &path_rtn, &path_total_rtns, &type_rtn
          );
          FileBrowserSetTransientFor(NULL);
          if(status)
          {
            gint i, status;
            const gchar *filename;

            for(i = 0; i < path_total_rtns; i++)
            {
                filename = path_rtn[i];
                if(filename == NULL)
                  continue;

                /* Open manual page file, the given branch will be
                 * recursed to its toplevel parent and the new
                 * manual page will be inserted as a toplevel trunk
                 * after the given branch toplevel trunk.
                 */
                status = EditorFileLoad(
                  editor, filename, branch, FALSE
                );
                switch(status)
                {
                  case 0:
                  /* Success, check if loaded file is writeable */
                  if(access(filename, W_OK))
                  {
                      /* Warn that file is read only */
                      CDialogSetTransientFor(toplevel);
                      CDialogGetResponse(
"Manual page is read-only!",
"The loaded manual page is read-only, you will\n\
not be able to save it.",
"The loaded manual page has permissions set such\n\
that it is read-only and not writeable. If you\n\
want to make changes to it then you will need to\n\
save it to a different file name by using\n\
file->save as.",
                        CDIALOG_ICON_WARNING,
                        CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                        CDIALOG_BTNFLAG_OK
                      );
                      CDialogSetTransientFor(NULL);
                  }
                  break;

                  /* No such file */
                  case -2:
                  buf = g_strdup_printf(
                      "No such file:\n\n    %s",
                      filename
                  );
                  CDialogSetTransientFor(toplevel);
                  CDialogGetResponse(
                      "Manual page not found!",
                      buf,
"The specified manual page file could not be found,\n\
please verify that the given path exists and has\n\
read permissions set.",
                      CDIALOG_ICON_ERROR,
                      CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                      CDIALOG_BTNFLAG_OK
                  );
                  CDialogSetTransientFor(NULL);
                  g_free(buf);
                  break;

                  /* Other error */
                  default:
                  CDialogSetTransientFor(toplevel);
                  CDialogGetResponse(
"Manual page opening error!",
"Error occured while opening manual page.",
                      NULL,
                      CDIALOG_ICON_ERROR,
                      CDIALOG_BTNFLAG_OK,
                      CDIALOG_BTNFLAG_OK
                  );
                  CDialogSetTransientFor(NULL);
                  break;
                }
            }
          }
          else
          {
            /* User canceled save as, abort entire open procedure */
            return;
          }
      }

      /* Update menus, note that a successful load may have updated
       * menus already but it is safe to do it again here.
       */
      EditorUpdateMenus(editor);
}

/*
 *    New manual page from template.
 */
void EditorManualNewFromTemplateCB(GtkWidget *widget, gpointer data)
{
      const gchar *s;
      GtkCTree *ctree;
      GtkCTreeNode *branch;
      GtkWidget *toplevel;
      medit_fetype_list_struct *fetype_list;
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      fetype_list = &core_ptr->fetype_list;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch (may be NULL) */
      branch = editor->selected_branch;

      if(TRUE)
      {
          gboolean status;
          gint path_total_rtns;
          gchar **path_rtn;
          fb_type_struct *type_rtn;
          gchar template_path[PATH_MAX + NAME_MAX];
          struct stat stat_buf;

          /* Get template path */
          s = PrefParmGetValueP(
            core_ptr->pref,
            MEDIT_PREF_PARM_LOCATIONS_GLOBAL_DIR
          );
          if(s == NULL)
            s = MEDIT_GLOBAL_DIR;
          if(s == NULL)
          {
            strncpy(template_path, "/", sizeof(template_path));
            template_path[sizeof(template_path) - 1] = '\0';
          }
          else
          {
            s = PrefixPaths(s, MEDIT_TEMPLATES_DIR);
            if(s == NULL)
                s = "/";
            strncpy(template_path, s, sizeof(template_path));
            template_path[sizeof(template_path) - 1] = '\0';
          }

          /* Warn if templates directory does not exist */
          if(stat(template_path, &stat_buf))
          {
            gchar *buf = g_strdup_printf(
"Cannot find Manual Page templates directory:\n\n    %s",
                template_path
            );
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"Cannot find directory!",
                buf,
"The specified Manual Page templates directory\n\
could not be found. This directory contains all\n\
Manual Page template files for this program.\n\
Verify that the specified paths for this program\n\
are defined properly in the preferences, go to\n\
edit->preferences->locations.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            g_free(buf);
            return;
          }

          /* Map file browser for opening of manual page */
          FileBrowserSetTransientFor(toplevel);
          status = FileBrowserGetResponse(
            "Open Manual Page Template",
            "Open", "Cancel",
            template_path,
            fetype_list->manual_page,
            fetype_list->total_manual_pages,
            &path_rtn, &path_total_rtns, &type_rtn
          );
          FileBrowserSetTransientFor(NULL);
          if(status)
          {
            gint i, status;
            gchar *buf;
            const gchar *filename;

            for(i = 0; i < path_total_rtns; i++)
            {
                filename = path_rtn[i];
                if(filename == NULL)
                  continue;

                /* Open manual page file as a template, the given
                 * branch will be recursed to its toplevel parent
                 * and the new manual page will be inserted as a
                 * toplevel trunk after the given branch toplevel
                 * trunk.
                 */
                status = EditorFileLoad(
                  editor,
                  filename,
                  branch,
                  TRUE        /* Open as template */
                );
                switch(status)
                {
                  case 0:
                  /* Success */
                  break;

                  /* No such file */
                  case -2:
                  buf = g_strdup_printf(
                      "No such file:\n\n    %s",
                      filename
                  );
                  CDialogSetTransientFor(toplevel);
                  CDialogGetResponse(
                      "Manual page not found!",
                      buf,
"The specified manual page file could not be found,\n\
please verify that the given path exists and has\n\
read permissions set.",  
                      CDIALOG_ICON_ERROR,
                      CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                      CDIALOG_BTNFLAG_OK
                  );
                  CDialogSetTransientFor(NULL);
                  g_free(buf);
                  break;

                  /* Other error */
                  default:
                  CDialogSetTransientFor(toplevel);
                  CDialogGetResponse(
"Manual page opening error!",
"Error occured while opening manual page.",
                      NULL,
                      CDIALOG_ICON_ERROR,
                      CDIALOG_BTNFLAG_OK,
                      CDIALOG_BTNFLAG_OK
                  );
                  CDialogSetTransientFor(NULL);
                  break;
                }
            }
          }    
          else
          {
            /* User canceled save as, abort entire open procedure */
            return;
          }
      }

      /* Update menus, note that a successful load may have updated
       * menus already but it is safe to do it again here
       */
      EditorUpdateMenus(editor);
}

/*
 *    Save manual page callback.
 */
void EditorManualSaveCB(GtkWidget *widget, gpointer data)
{
      gint status;
      const gchar *filename = NULL;
      GtkCTree *ctree;
      GtkCTreeNode *trunk, *branch;
      GtkCTreeRow *branch_row;
      GtkWidget *toplevel;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing)
          return;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch and then its toplevel trunk */
      branch = editor->selected_branch;
      trunk = EditorItemGetToplevel(editor, branch);

      /* Must have trunk branch to save */
      if(trunk == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the toplevel trunk branch for the\n\
selected branch.",
            NULL,
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }
      else
      {
          /* Get full file name, this can be NULL implying its
           * never been saved before.
           */
          item = EditorBranchGetData(ctree, trunk);
          if(item != NULL)
            filename = item->full_path;
      }

      /* If no filename was associated with the selected manual then
       * prompt user for save as.
       */
      if(filename == NULL)
      {
          EditorManualSaveAsCB(widget, data);
          return;
      }

      /* Call save procedure, it will fetch the filename from the
       * given toplevel trunk branch.
       */
      status = EditorFileSave(editor, trunk);
      if(status)
      {
          /* Error occured while saving */
          gchar *buf;
          switch(status)
          {
            /* User aborted on confirmation of overwrite */
            case -3:
            break;

            /* Cannot save, not writeable */
            case -2:
            buf = g_strdup_printf(
"Cannot save to manual page to file:\n\n    %s",
                filename
            );
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
                "Cannot save manual page!",
                buf,
"The specified manual page file could not be written\n\
to, please verify that the given path exists and has\n\
write permissions set.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            g_free(buf);
            break;

            default:
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"Manual page saving error!",
"Error occured while saving manual page.",
                NULL,
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            break;
          }
      }
      else
      {
          /* Mark child branches as no longer having changes */
          branch = trunk;
          branch_row = GTK_CTREE_ROW(branch);
          if(branch_row == NULL)
            branch = NULL;
          else
            branch = branch_row->children;

          /* Iterate through each branch */
          while(branch != NULL)
          {
            item = EditorBranchGetData(ctree, branch);
            if(item != NULL)
                item->has_changes = FALSE;

            /* Get sibling of branch */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row == NULL)
                branch = NULL;
            else
                branch = branch_row->sibling;
          }

          /* Mark trunk as no longer having changes */
          item = EditorBranchGetData(ctree, trunk);
          if(item != NULL)
            item->has_changes = FALSE;
      }


      /* Update menus, note that a successful save may have updated
       * menus already but it is safe to do it again here.
       */
      EditorUpdateMenus(editor);
}

/*
 *    Save manual page as callback.
 */
void EditorManualSaveAsCB(GtkWidget *widget, gpointer data)
{
      gint status;
      const gchar *filename = NULL;
      GtkCTree *ctree;
      GtkCTreeNode *trunk, *branch;
      GtkCTreeRow *branch_row;
      GtkWidget *toplevel;
      medit_core_struct *core_ptr;
      medit_fetype_list_struct *fetype_list;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing)
          return;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      fetype_list = &core_ptr->fetype_list;


      /* Get selected branch and then its toplevel trunk */
      branch = editor->selected_branch;
      trunk = EditorItemGetToplevel(editor, branch);

      /* Must have trunk branch to save */
      if(trunk == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the toplevel trunk branch for the\n\
selected branch.",
            NULL,
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK, 
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      if(TRUE)
      {
          gboolean status;
          gchar **path_rtn;
          gint path_total_rtns;
          fb_type_struct *type_rtn;

          /* Map file browser for saving of manual page */
          FileBrowserSetTransientFor(toplevel);
          status = FileBrowserGetResponse(
            "Save Manual Page",
            "Save", "Cancel",
            editor->last_save_as_path,
            fetype_list->manual_page,
            fetype_list->total_manual_pages,
            &path_rtn, &path_total_rtns, &type_rtn
          );
          FileBrowserSetTransientFor(NULL);
          if(status)
            filename = (path_total_rtns > 0) ? path_rtn[0] : NULL;
      }
      if(filename == NULL)
          return;

      /* Call save procedure, it will update the file name on
       * the trunk's item data.
       */
      status = EditorFileSaveAs(editor, filename, trunk);
      if(status)
      {
          /* Error occured while saving */
          gchar *buf;
          switch(status)
          {
            /* User aborted on confirmation of overwrite */
            case -3:
            break;

            case -2:
            buf = g_strdup_printf(
"Cannot save to manual page file:\n\n    %s",
                filename
            );
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
                "Cannot save manual page!",
                buf,
"The specified manual page file could not be written\n\
to, please verify that the given path exists and has\n\
write permissions set.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            g_free(buf);
            break;

            default:
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"Manual page saving error!",
"Error occured while saving manual page.",
                NULL,
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            break;
          }
      }
      else
      {
          /* Mark child branches as no longer having changes */
          branch = trunk;
          branch_row = GTK_CTREE_ROW(branch);  
          if(branch_row == NULL)
            branch = NULL;
          else
            branch = branch_row->children;

          /* Itterate through each branch */
          while(branch != NULL)
          {   
            item = EditorBranchGetData(ctree, branch);
            if(item != NULL)
                item->has_changes = FALSE;

            /* Get sibling of branch */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row == NULL)
                branch = NULL;
            else
                branch = branch_row->sibling;
          }

          /* Mark trunk as no longer having changes */
          item = EditorBranchGetData(ctree, trunk);
          if(item != NULL)
            item->has_changes = FALSE;
      }


      /* Update menus, note that a successful save may have updated
       * menus already but it is safe to do it again here.
       */     
      EditorUpdateMenus(editor);
}

/*
 *    Revert callback.
 */
void EditorManualRevertCB(GtkWidget *widget, gpointer data)
{
      gint status;
      gchar *buf;
      GtkCTree *ctree;
      GtkCTreeNode *branch, *branch_prev;
      GtkCTreeRow *branch_row;
      GtkWidget *toplevel;
      editor_item_struct *item;
      gchar *full_path;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing) 
          return;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      branch = editor->selected_branch;
      branch = EditorItemGetToplevel(editor, branch);
      if(branch == NULL)
          return;

      item = EditorBranchGetData(ctree, branch);
      if(item == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the item data for the selected\n\
Manual Page.",
            NULL,
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      /* Item's file name full is NULL? Implying it was never saved
       * before?
       */
      if(item->full_path == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No file data to revert to!",
"The selected Manual Page has no associated file,\n\
which implies that it has never been saved before.\n\
There is no file data to revert to.",
            NULL,
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }


      /* Confirm before revert */
      buf = g_strdup_printf(
"Are you sure you want to revert Manual Page\n`%s'?",
          item->name
      );
      CDialogSetTransientFor(toplevel);
      status = CDialogGetResponse(
"Confirm Revert Manual Page",
          buf,
"You are being asked if you want to discard any changes\n\
to the selected Manual Page and reload it from file. Any\n\
changes that you have made will not be saved and the\n\
data will be reloaded from the time the Manual Page was\n\
last saved. If you are unsure say `no'.",
          CDIALOG_ICON_WARNING,
          CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
          CDIALOG_BTNFLAG_HELP,
          CDIALOG_BTNFLAG_NO
      );
      CDialogSetTransientFor(NULL);
      g_free(buf);
      switch(status)
      {                    
        case CDIALOG_RESPONSE_NOT_AVAILABLE:
        case CDIALOG_RESPONSE_CANCEL:
        case CDIALOG_RESPONSE_NO:
          /* Do not revert Manual Page */
          return;
          break;
 
        case CDIALOG_RESPONSE_YES_TO_ALL:
        case CDIALOG_RESPONSE_YES:
          break;
      }


      EditorSetStatusMessage(editor, "Reverting...");

      /* Make a copy of the item data's full file name */
      full_path = STRDUP(item->full_path);

      /* Get previous sibling to the currently selected branch,
       * this will be used as the toplevel trunk branch to insert
       * after.
       */
      branch_prev = EditorItemGetFirstToplevel(editor);
      while(branch_prev != NULL)
      {
          branch_row = GTK_CTREE_ROW(branch_prev);
          if(branch_row == NULL)
          {
            branch_prev = NULL;
          }
          else
          {
            if(branch == branch_row->sibling)
                break;
            else
                branch_prev = branch_row->sibling;
          }
      }

      EditorSetStatusMessage(editor, "Closing manual page...");

      /* Remove the trunk and all subsequent branch nodes, the  
       * item data will be deallocated when the destroy notify  
       * function is called and layout trunk list on editor will
       * be updated.
       */
      gtk_ctree_remove_node(ctree, branch);
      branch = NULL;


      /* Load from file again */
      status = EditorFileLoad(
          editor, full_path, branch_prev, FALSE
      );
      switch(status)
      {
        case 0:
          break;

        case -2:
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Cannot find original file!",
"The orignal file containing the Manual Page data\n\
could not be found. It may have been moved or deleted,\n\
try opening it manually by going to file->open.",
            NULL,
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          break;

        default:
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Error Reloading Manual Page!",
"An error occured while reloading the selected Manual\n\
Page. Try reloading manual by going to file->open.",
            NULL,
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          break;
      }

      /* Deallocate coppied file name */
      g_free(full_path);
      full_path = NULL;

      EditorSetStatusMessage(editor, "Revert done");
}

/*
 *    Close manual page callback (does not close the Manual Page Editor
 *    window, for that see EditorCloseMCB()).
 */
void EditorManualCloseCB(GtkWidget *widget, gpointer data)
{
      GtkCTree *ctree;
      GtkCTreeNode *branch;
      GtkCTreeRow *branch_row;
      GtkWidget *toplevel;
      editor_item_struct *item;
      medit_fetype_list_struct *fetype_list;
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(editor->processing)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      fetype_list = &core_ptr->fetype_list;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch */
      branch = editor->selected_branch;
      if(branch == NULL)
          return;

      /* Apply data on currently selected branch to ensure that we
       * have the most recent data.
       */
      EditorDoApplyValues(editor, branch);

      /* Recurse to selected branch toplevel trunk parent */
      while(TRUE)
      {
          branch_row = GTK_CTREE_ROW(branch);
          if(branch_row == NULL)
          {
            /* Discontinuity by missing row data, abort */
            return;
          }
          if(branch_row->parent == NULL)
            break;
          else
            branch = branch_row->parent;
      }
      if(branch == NULL)
          return;
      /* Branch should now be the toplevel trunk */


      /* Get item data */
      item = EditorBranchGetData(ctree, branch);
      if(item == NULL)
      {
          /* Could not get item data, but remove anyways */
          gtk_ctree_remove_node(ctree, branch);
          return;
      }


      /* Check if it has changes by checking the toplevel trunk
       * branch node's item data, it reflects if any child has changes.
       */
      if(item->has_changes)
      {
          /* Has changes, prompt for save */
          gint status;
          gchar *buf = g_strdup_printf(
            "Save changes to manual page\n`%s'?",
            item->name  
          );
          CDialogSetTransientFor(toplevel);
          status = CDialogGetResponse(
            "Save changes?",
            buf,
"The manual page has changes which have not\n\
been saved. If you say yes then those changes\n\
will be saved. Otherwise the changes will be\n\
discarded.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |
            CDIALOG_BTNFLAG_CANCEL | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_YES
          );
          CDialogSetTransientFor(NULL);
          g_free(buf);
          switch(status)
          {
            case CDIALOG_RESPONSE_YES:
            case CDIALOG_RESPONSE_YES_TO_ALL:
            case CDIALOG_RESPONSE_OK:
            /* Request to save, check if the full path is set.
             * If the full path is not set (regardless of if the
             * name is set or not, we need to consider that
             * this file was never saved before and thus we need
             * to request from the user a `save as'.
             */
            if(item->full_path == NULL)
            {
                /* Save as */
                gboolean status;
                gchar **path_rtn;
                gint path_total_rtns;
                fb_type_struct *type_rtn;

                FileBrowserSetTransientFor(toplevel);
                status = FileBrowserGetResponse( 
                  "Save Manual Page As",
                  "Save", "Cancel",
                  editor->last_save_as_path,
                  fetype_list->manual_page,
                  fetype_list->total_manual_pages,
                  &path_rtn, &path_total_rtns, &type_rtn
                );
                FileBrowserSetTransientFor(NULL);
                if(status)
                {
                  if(path_total_rtns > 0)
                  {
                      EditorFileSaveAs(
                        editor, path_rtn[0], branch
                      );
                  }   
                  else   
                  {
                      /* Did not get any paths, abort entire
                       * close procedure.
                       */
                      return;
                  }
                } 
                else
                {
                  /* User canceled save as, abort entire close
                   * procedure.
                   */
                  return;
                }
            }
            else
            {           
                /* Save */
                EditorFileSave(editor, branch);
            }
            break;

            case CDIALOG_RESPONSE_NO:
            break;

            case CDIALOG_RESPONSE_CANCEL:
            case CDIALOG_RESPONSE_NOT_AVAILABLE:
            /* Response not available, abort entire close
             * procedure.
             */  
            return;
            break;
          }
      }


      /* Remove the trunk and all subsequent branch nodes, the
       * item data will be deallocated when the destroy notify
       * function is called and layout trunk list on editor will
       * be updated.
       */
      gtk_ctree_remove_node(ctree, branch);
      branch = NULL;

      EditorSetStatusMessage(editor, "Manual page closed");

      /* Check if a branch is still selected, if it is then set it
       * to NULL. This should not happen since the selected branch node
       * should have been sent an unselect signal just before it was
       * deleted.
       */
      if(editor->selected_branch != NULL)
      {
          editor->selected_branch = NULL;

          /* Clear undo/redo list just in case, since branch node
           * unselect callback was probably not called.
           */
          EditorResetUndos(editor);
      }

      /* If no more layout trunks loaded then mark editor structure
       * as not having any changes.
       */

      EditorUpdateMenus(editor);
}

/*
 *    Add header to currently selected manual page.
 *
 *    Checks first if selected manual page already has a header
 *    and warns user.
 */
void EditorManualAddHeaderCB(GtkWidget *widget, gpointer data)
{
      gchar *text[1];
      GdkPixmap *closed_pm, *opened_pm;
      GdkBitmap *closed_mask, *opened_mask;
      GtkCTree *ctree;
      GtkCTreeRow *branch_row;
      GtkCTreeNode *branch, *trunk, *new_branch = NULL;
      GtkWidget *toplevel;
      medit_core_struct *core_ptr;
      medit_pixmaps_list_struct *pixmaps_list;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      pixmaps_list = &core_ptr->pixmaps_list;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch */
      branch = editor->selected_branch;
      if(branch == NULL)
      {
          /* Warn about no branch being selected */
          if(editor->total_layout_trunks > 0)
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"No selected manual page to add to!",
"You need to select the insert position of\n\
where you want to add the new header. This\n\
can be a manual page reference or an existing\n\
header or section to add under.",
"You have requested to add a new header but\n\
you have not specified a manual page or\n\
header or section to add it under. You must\n\
selected a manual page or an existing header\n\
or section to add the new header under.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"No manual pages opened!",
"There are no manual pages opened to add\n\
a new header to.",
"You must either create a new manual page or\n\
open an existing manual page to add a new header\n\
to.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          return;
      }

      /* Get toplevel trunk for the selected branch */
      trunk = EditorItemGetToplevel(editor, branch);
      if(trunk == NULL)
      {
          /* Could not get toplevel trunk, something is very wrong */
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the parent trunk for the selected\n\
layout branch.",
"An internal error has occured in which the parent\n\
trunk for the currently selected branch could not\n\
be found. There may be some inconsistancy in the\n\
loaded data. You should save this manual page to\n\
a new file (use `save as') and close this application\n\
to prevent and further data loss.\n",
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }
      else
      {
          /* Check if this manual page already has a header and warn
           * user about it if it exists.
           */

/* Need to work on this */


      }



      EditorSetBusy(editor);
      EditorSetStatusProgress(editor, 0.0);


      /* Set up data pointers */
      text[0] = "Header";
      closed_pm = pixmaps_list->manpage_heading_20x20;
      closed_mask = pixmaps_list->manpage_heading_20x20_mask;
      opened_pm = closed_pm;
      opened_mask = closed_mask;


      /* Get item data pointer for selected branch if possible */
      item = EditorBranchGetData(ctree, branch);
      if(item == NULL)
      {
          /* Item data is NULL, so insert as first child on trunk.
           * Get row pointer of the trunk.
           */
          branch_row = GTK_CTREE_ROW(trunk);
          if(branch_row != NULL)
          {
            /* Trunk must not be a leaf */
            if(!branch_row->is_leaf)
            {
                /* Insert before first child in trunk */
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->children,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE,
                  FALSE
                );
            }
          }
      }
      else
      {
          /* Insert by branch item data type */
          switch(item->type)
          {
            case EditorItemTypeFile:
            /* Get row pointer from selected branch which is
             * a trunk
             */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row != NULL)
            {
                /* If selected branch is of type EditorItemTypeFile
                 * then it must not be a leaf
                 */
                if(branch_row->is_leaf)
                  break;

                /* Insert before first child in selected branch
                 * (which is suppose to be a trunk
                 */
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->children,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE,
                  FALSE
                );
            }
            break;

            case EditorItemTypeHeader:
            case EditorItemTypeSection:
            /* Get row pointer from selected branch */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row != NULL)
            {
                /* If selected branch must be a leaf */
                if(!branch_row->is_leaf)
                  break;

                /* Insert after selected branch by passing the
                 * selected branch sibling as the insert position
                 * (which may be NULL)
                 */
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->sibling,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE,
                  FALSE
                );
            }
            break;
          }
      }

      /* If inserting of new branch node was successful, then new_branch
       * should not be NULL.
       */
      if(new_branch != NULL)
      {
          /* Create new item data for the new branch node */
          item = EditorItemNew(
            EditorItemTypeHeader,
            editor,
            ctree,
            trunk,            /* Parent branch */
            new_branch, /* This branch node */
            TRUE,       /* Is leaf */
            NULL,       /* No text initially */
            0
          );
          EditorBranchSetData(
            ctree, new_branch,
            item, EditorItemDestroyCB
          );

          /* Mark has changes */
          EditorItemSetToplevelHasChanges(editor, new_branch, TRUE);
      }


      /* Update layout clist column width */
      gtk_clist_set_column_width(
          (GtkCList *)ctree,
          0,                  /* Column */
          gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
      );

      /* Select new branch node */
      EditorBranchSelect(editor, new_branch);

      EditorSetStatusProgress(editor, 0.0);
      EditorSetStatusMessage(editor, "Added new header");

      /* Update menus on editor */
      EditorUpdateMenus(editor);

      EditorSetReady(editor);
}

/*
 *    Adds a new section to the manual page.
 */
void EditorManualAddSectionCB(GtkWidget *widget, gpointer data)
{
      gchar *text[1];
      GdkPixmap *closed_pm, *opened_pm;
      GdkBitmap *closed_mask, *opened_mask;
      GtkCTree *ctree;
      GtkCTreeRow *branch_row;
      GtkCTreeNode *branch, *trunk, *new_branch = NULL;
      GtkWidget *toplevel;
      medit_core_struct *core_ptr;
      medit_pixmaps_list_struct *pixmaps_list;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      pixmaps_list = &core_ptr->pixmaps_list;

      toplevel = EDITOR_TOPLEVEL(editor->toplevel);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      /* Get selected branch */
      branch = editor->selected_branch;
      if(branch == NULL)
      {
          /* Warn about no branch being selected */
          if(editor->total_layout_trunks > 0)
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"No selected manual page to add to!",
"You need to select the insert position of\n\
where you want to add the new section. This\n\
can be a manual page reference or an existing\n\
header or section to add under.",
"You have requested to add a new section but\n\
you have not specified a manual page or\n\
header or section to add it under. You must\n\
selected a manual page or an existing header\n\
or section to add the new section under.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"No manual pages opened!",
"There are no manual pages opened to add\n\
a new section to.",
"You must either create a new manual page or\n\
open an existing manual page to add a new section\n\
to.",
                CDIALOG_ICON_ERROR,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          return;
      }

      /* Get toplevel trunk for the selected branch */
      trunk = EditorItemGetToplevel(editor, branch);
      if(trunk == NULL)
      {
          /* Could not get toplevel trunk, something is very wrong */
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Non-Critical Internal Error!",
"Could not find the parent trunk for the selected\n\
layout branch.",
"An internal error has occured in which the parent\n\
trunk for the currently selected branch could not\n\
be found. There may be some inconsistancy in the\n\
loaded data. You should save this manual page to\n\
a new file (use `save as') and close this application\n\
to prevent and further data loss.\n",
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }


      EditorSetBusy(editor);
      EditorSetStatusProgress(editor, 0.0);


      /* Set up data pointers */
      text[0] = "UNTITLED";
      closed_pm = pixmaps_list->manpage_section_20x20;
      closed_mask = pixmaps_list->manpage_section_20x20_mask;
      opened_pm = closed_pm;
      opened_mask = closed_mask;

      /* Get item data pointer for selected branch if possible */
      item = EditorBranchGetData(ctree, branch);
      if(item == NULL) 
      {
          /* Item data is NULL, so insert as first child on trunk.
           * Get row pointer of the trunk.
           */
          branch_row = GTK_CTREE_ROW(trunk);
          if(branch_row != NULL)
          {
            /* Trunk must not be a leaf */
            if(!branch_row->is_leaf)
            {
                /* Insert before first child in trunk */   
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->children,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE,
                  FALSE
                ); 
            }
          }
      }    
      else
      {
          /* Insert by branch item data type */
          switch(item->type)
          {
            case EditorItemTypeFile:
            /* Get row pointer from selected branch which is
             * a trunk
             */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row != NULL)
            {
                /* If selected branch is of type EditorItemTypeFile
                 * then it must not be a leaf
                 */
                if(branch_row->is_leaf)
                  break;

                /* Insert before first child in selected branch
                 * (which is suppose to be a trunk
                 */
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->children,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE, 
                  FALSE
                );
            }
            break;

            case EditorItemTypeHeader:
            case EditorItemTypeSection:
            /* Get row pointer from selected branch */
            branch_row = GTK_CTREE_ROW(branch);
            if(branch_row != NULL)
            {
                /* If selected branch must be a leaf */
                if(!branch_row->is_leaf)
                  break;
      
                /* Insert after selected branch by passing the
                 * selected branch sibling as the insert position
                 * (which may be NULL).
                 */
                new_branch = gtk_ctree_insert_node(
                  ctree, trunk, branch_row->sibling,
                  text, MEDIT_LIST_ICON_TEXT_SPACING,
                  closed_pm, closed_mask,
                  opened_pm, opened_mask,
                  TRUE,
                  FALSE
                );
            }
            break;
          } 
      }

      /* If inserting of new branch node was successful, then new_branch
       * should not be NULL.
       */
      if(new_branch != NULL)
      {
          /* Create new item data for the new branch node */
          item = EditorItemNew(
            EditorItemTypeSection,
            editor,
            ctree,
            trunk,          /* Parent branch */
            new_branch,     /* This branch node */
            TRUE,           /* Is leaf */
            NULL,           /* No text initially */
            0
          );
          if(item != NULL)
          {
            g_free(item->section_name);
            item->section_name = STRDUP(text[0]);
          }
          EditorBranchSetData(
            ctree, new_branch,
            item, EditorItemDestroyCB
          );

          /* Mark has changes */
          EditorItemSetToplevelHasChanges(editor, new_branch, TRUE);
      }


      /* Update layout clist column width */
      gtk_clist_set_column_width(
          (GtkCList *)ctree,
          0,                  /* Column */
          gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
      );

      /* Select new branch node */
      EditorBranchSelect(editor, new_branch);

      EditorSetStatusProgress(editor, 0.0);
      EditorSetStatusMessage(editor, "Added new section");

      /* Update menus on editor */
      EditorUpdateMenus(editor);

      EditorSetReady(editor);
}

/*
 *    Removes a header or section from the currently selected
 *    manual page.
 */
void EditorManualRemoveSectionCB(GtkWidget *widget, gpointer data)
{
      gboolean yes_to_all = FALSE;
      gboolean need_break, need_continue;
      GtkCTree *ctree;
      GtkCTreeNode *branch;
      GtkWidget *toplevel;
      medit_core_struct *core_ptr;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      toplevel = EDITOR_TOPLEVEL(editor);
      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;


      EditorSetBusy(editor);
      EditorSetStatusProgress(editor, 0.0);


      /* Get selected branch */
      branch = editor->selected_branch;
      while(branch != NULL)
      {
          item = EditorBranchGetData(ctree, branch);
          if(item == NULL)
          {
            branch = NULL;
            continue;
          }

          /* Handle by type */
          need_continue = FALSE;
          need_break = FALSE;
          switch(item->type)
          {
            case EditorItemTypeHeader:
            EditorSetStatusMessage(
                editor,
                "Removing header..."
            );
            if(!yes_to_all)
            {
                gint status;

                CDialogSetTransientFor(toplevel);
                status = CDialogGetResponse(
"Confirm remove header",
"Are you sure you want to remove the header\n\
from the manual page?",
"You are being asked for confirmation about removing\n\
the header of the manual page. If you say `yes' then\n\
the header will be removed, otherwise say `no' to\n\
keep the header. To abort this operation entirly click\n\
on `cancel'.",
                  CDIALOG_ICON_WARNING,
                  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
                  CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL |
                  CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_NO
                );
                CDialogSetTransientFor(NULL);
                switch(status)
                {
                  case CDIALOG_RESPONSE_YES_TO_ALL:
                  yes_to_all = TRUE;
                  case CDIALOG_RESPONSE_YES:
                  break;

                  case CDIALOG_RESPONSE_CANCEL:
                  case CDIALOG_RESPONSE_NOT_AVAILABLE:
                  need_break = TRUE;
                  break;

                  default:
                  need_continue = TRUE;
                  break;
                }
            }
            break;

            case EditorItemTypeSection:
            EditorSetStatusMessage(
                editor,
                "Removing section..."
            );
            if(!yes_to_all)
            {
                gint status;
                gchar *buf = g_strdup_printf(
"Are you sure you want to remove section\n`%s' from the manual page?",
                  item->section_name
                );
                CDialogSetTransientFor(toplevel);
                status = CDialogGetResponse(
"Confirm remove section",
buf,
"You are being asked for confirmation about removing\n\
this section of the manual page. If you say `yes' then\n\
this section will be removed, otherwise say `no' to\n\
keep this section. To abort this operation entirly click\n\
on `cancel'.",
                  CDIALOG_ICON_WARNING,
                  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
                  CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL |
                  CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_NO
                );
                CDialogSetTransientFor(NULL);
                g_free(buf);
                switch(status)
                {
                  case CDIALOG_RESPONSE_YES_TO_ALL:
                  yes_to_all = TRUE;
                  case CDIALOG_RESPONSE_YES:
                  break;

                  case CDIALOG_RESPONSE_CANCEL:
                  case CDIALOG_RESPONSE_NOT_AVAILABLE:
                  need_break = TRUE;
                  break;

                  default:
                  need_continue = TRUE;
                  break;
                }
            }
            break;

            default:
            /* Some other type, skip it */
            need_continue = TRUE;
            break;
          }
          if(need_break)
            break;
          if(need_continue)
          {
            branch = NULL;
            continue;
          }

          /* Remove this branch, the destroy notify callback will
           * deallocate the branch item data and mark this branch
           * as unselected.
           */
          gtk_ctree_remove_node(ctree, branch);

          EditorSetStatusProgress(editor, 1.0);

          branch = NULL;
      }


      /* Update layout clist column width */
      gtk_clist_set_column_width(
          (GtkCList *)ctree,
          0,                  /* Column */
          gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
      );

      EditorSetStatusProgress(editor, 0.0);
      EditorSetStatusMessage(editor, "Remove done");

      /* Update menus on editor */
      EditorUpdateMenus(editor);

      EditorSetReady(editor);
}

/*
 *    Manual page properties callback.
 */
void EditorManualPropertiesCB(GtkWidget *widget, gpointer data)
{
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;



}

/*
 *      Manual page preview callback.
 */
void EditorManualPreviewCB(GtkWidget *widget, gpointer data)
{
      gint i, viewer_num = -1, editor_num = -1;
      viewer_struct *viewer = NULL;
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)
          return;

      /* Get number of this editor on core structure */
      for(i = 0; i < core_ptr->total_editors; i++)
      {
          if(editor == core_ptr->editor[i])
          {
            editor_num = i;
            break;
          }
      }

      /* Create a new viewer as needed */
      i = editor->viewer_num;
      if((i >= 0) && (i < core_ptr->total_viewers))
      {
          viewer = core_ptr->viewer[i];
          if(viewer != NULL)
            viewer_num = i;
      }

      /* If viewer is NULL then we need to create a new one */
      if(viewer == NULL)
      {
          /* Create new viewer and add it to the core structure */
          if(core_ptr->total_viewers < 0)
            core_ptr->total_viewers = 0;
          i = core_ptr->total_viewers;
          core_ptr->total_viewers = i + 1;
          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;
            return;
          }

          viewer = ViewerNew(
            core_ptr, editor_num
          );
          core_ptr->viewer[i] = viewer;

          if(viewer == NULL)
            viewer_num = -1;
          else
            viewer_num = i;

          /* Update viewer number on editor */
          editor->viewer_num = viewer_num;
      }

      /* Viewer valid? */
      if(viewer != NULL)
      {
          /* Map as needed */
          if(!viewer->map_state)
            ViewerMap(viewer);

          EditorDoPreview(
            editor, editor->selected_branch,
            viewer_num
          );
      }
}

/*
 *    Syntax highlight toggle callback.
 */
void EditorManualSyntaxHighlightToggleCB(GtkWidget *widget, gpointer data)
{
      static gboolean reenterent = FALSE;
      gboolean state = FALSE;
      GtkWidget *toplevel;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      if(editor->processing)
          return;

      if(reenterent)
          return;
      else
          reenterent = TRUE;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Syntax highlighting check menu item? */
      if(widget == editor->syntax_highlight_cmi)
          state = GUIMenuItemGetCheck(widget);

      /* Toggle syntax highlighting on editor */
      editor->syntax_highlighting = state;

      /* If turning on syntax highlighting, then warn user about
       * problems with GtkText widget
       */
      if(state && editor->map_state)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"Subsystems Warning",
"Enabling syntax highlighting may cause crashes!\n\
\n\
Click on `help' for explaination!",
"This program makes use of the GTK+ GtkText widget\n\
it has known bugs which cause applications to crash\n\
under certain circumstances such as syntax highlighting.\n\
You are advised not to use syntax highlighting, however\n\
if you do and a crash does occure then this program will\n\
try its best (no gaurantees) to emergency save any opened\n\
documents into the program's tempory directory before\n\
exiting.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
      }

      /* Update menus */
      EditorUpdateMenus(editor);

      /* Get pointer to selected branch item data */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree,
          editor->selected_branch
      );
      if(item != NULL)
      {
          /* Check if selected item is of type section */
          if(item->type == EditorItemTypeSection)
          {
            /* Need to apply and fetch values to ensure update
             * on section_text widget's text. The function
             * EditorDoFetchValues() will call syntax
             * highlighting on the newly loaded text.
             */
            EditorDoApplyValues(editor, editor->selected_branch);
            EditorDoFetchValues(editor, editor->selected_branch);
          }
      }

      reenterent = FALSE;
}


/*
 *    Undo callback.
 */
void EditorUndoCB(GtkWidget *widget, gpointer data)
{
      gint status;
      GtkWidget *toplevel;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      /* Nothing to undo? */
      if(editor->total_undos <= 0)
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Mark that we're redoing */
      editor_processing_undo = editor;                                      
      EditorSetBusy(editor);

      /* Perform undo procedure, passing the input list as the undo
       * list and the redo list as the output list. The first item
       * on the in list will be applied and an inverse operation
       * will be placed into the output list.
       */
      status = EditorUndoDoApply(
          editor,
          &editor->undo, &editor->total_undos,
          &editor->redo, &editor->total_redos,
          editor->max_undos
      );

      /* Flush and signals generated during undo apply */
      while(gtk_events_pending() > 0)
          gtk_main_iteration();

      /* Mark as no longer undoing */
      editor_processing_undo = NULL;
      EditorSetReady(editor);

      /* Undo failed? */
      if(status)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
            "Error processing undo!",
"The undo process could not be performed.",
"Circumstances or an error prevented the undo\n\
operation from being performed.",
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
      }

      EditorUpdateMenus(editor);
}

/*
 *    Redo callback.
 */
void EditorRedoCB(GtkWidget *widget, gpointer data)
{
      gint status;
      GtkWidget *toplevel;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      /* Nothing to redo? */
      if(editor->total_redos <= 0)
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Mark that we're redoing */
      editor_processing_undo = editor;
      EditorSetBusy(editor);

      /* Perform redo procedure, passing the input list as the redo
       * list and the undo list as the output list. The first item
       * on the in list will be applied and an inverse operation
       * will be placed into the output list.
       */
      status = EditorUndoDoApply(
          editor,
          &editor->redo, &editor->total_redos,
          &editor->undo, &editor->total_undos,
          editor->max_undos
      );

      /* Flush and signals generated during undo apply */
      while(gtk_events_pending() > 0)
          gtk_main_iteration();

      /* Mark as no longer undoing */
      editor_processing_undo = NULL;
      EditorSetReady(editor);

      /* Undo failed? */
      if(status)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
            "Error processing undo!",
"The redo process could not be performed.",
"Circumstances or an error prevented the redo\n\
operation from being performed.",
            CDIALOG_ICON_ERROR,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
      }

      EditorUpdateMenus(editor);
}


/*
 *    Cut text callback.
 */
void EditorCutCB(GtkWidget *widget, gpointer data)
{
      GtkText *text = NULL;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      /* Get selected item */
      item = EditorBranchGetData(
          GTK_CTREE(editor->layout_ctree), editor->selected_branch
      );
      if(item == NULL)
          return;

      /* Get text widget depending on selected branch item type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          text = GTK_TEXT(editor->header_text);
          break;
        case EditorItemTypeSection:
          text = GTK_TEXT(editor->section_text);
          break;
        case EditorItemTypeFile:
          break;
      }           

      /* Got text widget in focus? */
      if(text != NULL)
      {
          GtkEditable *editable = GTK_EDITABLE(text);
          gint    start_pos = editable->selection_start_pos,
                  end_pos = editable->selection_end_pos;
          if(end_pos < start_pos)
          {
            const gint tmp_pos = end_pos;
            end_pos = start_pos;
            start_pos = tmp_pos;
          }

          /* Has selection and valid selection bounds? */
          if(editable->has_selection &&
             (start_pos > -1) && (end_pos > -1) &&
             ((end_pos - start_pos) > 0)
          )
          {
            guint8 *buf = (guint8 *)gtk_editable_get_chars(
                editable, start_pos, end_pos
            );
            const gint buf_len = end_pos - start_pos;
            if(buf != NULL)
            {
                ClipboardPutBinary(
                  (const guint8 *)buf, buf_len
                );
                g_free(buf);
            }

            gtk_text_freeze(text);
            gtk_editable_delete_text(
                editable, start_pos, end_pos
            );
            gtk_text_thaw(text);

            /* Mark has changes */
            EditorItemSetToplevelHasChanges(
                editor, editor->selected_branch, TRUE
            );
          }
      }

      /* Update menus */
      EditorUpdateMenus(editor);

      if(text != NULL)
          EditorSetStatusMessage(editor, "Selection cut");
}

/*
 *    Copy text callback.
 */
void EditorCopyCB(GtkWidget *widget, gpointer data)
{
      GtkText *text = NULL;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      /* Get selected item */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree,
          editor->selected_branch
      );
      if(item == NULL)
          return;

      /* Get text widget depending on selected branch item type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          text = GTK_TEXT(editor->header_text);
          break;
        case EditorItemTypeSection:
          text = GTK_TEXT(editor->section_text);
          break;
        case EditorItemTypeFile:
          break;
      }

      /* Got text widget in focus? */
      if(text != NULL)
      {
          GtkEditable *editable = GTK_EDITABLE(text);
          gint    start_pos = editable->selection_start_pos,
                  end_pos = editable->selection_end_pos;
          if(end_pos < start_pos)
          {
            const gint tmp_pos = end_pos;
            end_pos = start_pos;
            start_pos = tmp_pos;
          }

          /* Has selection and valid selection bounds? */
          if(editable->has_selection &&
             (start_pos > -1) && (end_pos > -1) &&
             ((end_pos - start_pos) > 0)
          )
          {
            guint8 *buf = (guint8 *)gtk_editable_get_chars(
                editable, start_pos, end_pos
            );
            const gint buf_len = end_pos - start_pos;
            if(buf != NULL)
            {
                ClipboardPutBinary(
                  (const guint8 *)buf, buf_len
                );
                g_free(buf);
            }
          }
      }

      /* Update menus */
      EditorUpdateMenus(editor);

      if(text != NULL)
          EditorSetStatusMessage(editor, "Selection coppied");
}

/*
 *    Paste text callback.
 */
void EditorPasteCB(GtkWidget *widget, gpointer data)
{
      guint8 *buf;
      gint buf_len;
      GtkWidget *w;
      GtkCTreeNode *branch;
      editor_item_struct *item;
      medit_core_struct *core_ptr;
      medit_styles_list_struct *styles_list;
      GtkStyle *style;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      /* Get core pointer and subsequently the styles list */
      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr == NULL)  
          styles_list = NULL;
      else
          styles_list = &core_ptr->styles_list;

      /* Get selected branch */
      branch = editor->selected_branch;
      if(branch == NULL)
          return;

      item = EditorBranchGetData(
          GTK_CTREE(editor->layout_ctree), branch
      );
      if(item == NULL)
          return;

      /* Get clipboard buffer */
      buf = ClipboardFetchBinary(&buf_len);
      if(buf == NULL)
          return;
 
      /* Handle by branch item data type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          /* Get style */
          if(styles_list != NULL)
            style = styles_list->edit_text_standard;
          else
            style = NULL;
          /* Get pointer to text widget */
          w = editor->header_text;
          if(w != NULL)
          {
            GtkEditable *editable = GTK_EDITABLE(w);

            /* Text widget has selection? */
            if(editable->has_selection)
            {
/* Note that this would actually be a wierd case, if the
 * text widget already has selection, then it would be hard to
 * set a position to paste into itself. But it might happen and this
 * is the best we can do.
 */
                gint sel_start, sel_end, sel_len;


                sel_start = MAX(editable->selection_start_pos, 0);
                sel_end = MAX(editable->selection_end_pos, 0);
                sel_len = sel_end - sel_start;
                if(sel_len < 0)
                  sel_len *= -1;

                /* Clear selection */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_set_point(GTK_TEXT(w), (guint)sel_start);
                gtk_text_forward_delete(GTK_TEXT(w), (guint)sel_len);
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert point since selected buffer start
                 * position may not be the same as where the insert
                 * position is.
                 */
                gtk_text_set_point(GTK_TEXT(w), (guint)sel_start);

                /* Insert text */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_insert(
                  GTK_TEXT(w),
                  (style != NULL) ? style->font : NULL,
                  NULL,       /* Foreground color */
                  NULL,       /* Background color */
                  buf,
                  buf_len
                );
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert and cursor position */
                gtk_text_set_point(
                  GTK_TEXT(w),
                  (guint)(sel_start + buf_len)
                );
                gtk_editable_set_position(
                  editable, sel_start + buf_len
                );
/* Call text insert callback? */
            }
            else
            {
                gint start_pos;

                start_pos = gtk_editable_get_position(editable);
                gtk_text_set_point(GTK_TEXT(w), start_pos);

                /* Insert text */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_insert(
                  GTK_TEXT(w),
                  (style != NULL) ? style->font : NULL,
                  NULL,           /* Foreground color */
                  NULL,           /* Background color */
                  buf,
                  buf_len
                );
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert and cursor position */
                gtk_text_set_point(
                  GTK_TEXT(w),
                  (guint)(start_pos + buf_len)
                );
                gtk_editable_set_position(
                  editable, start_pos + buf_len
                );

                /* Need to call text insert callback, insert of text
                 * call above dosen't seem to trigger it.
                 */
                EditorTextInsertCB(
                  editable,
                  (const gchar *)buf, buf_len,
                  &start_pos, editor
                );
            }

            /* Mark branch as having changes */
            EditorItemSetToplevelHasChanges(editor, branch, TRUE);
          }
          break;

        case EditorItemTypeSection:
          /* Get pointer to style */
          if(styles_list != NULL)
            style = styles_list->edit_text_standard;
          else
            style = NULL;
          /* Get pointer to text widget */
          w = editor->section_text;
          if(w != NULL)
          {
            GtkEditable *editable = GTK_EDITABLE(w);

            /* Text widget has selection? */
            if(editable->has_selection)
            {
/* Note that this would actually be a wierd case, if the
 * text widget already has selection, then it would be hard to
 * set a position to paste into itself. But it might happen and this
 * is the best we can do.
 */
                gint sel_start, sel_end, sel_len;


                sel_start = MAX(editable->selection_start_pos, 0);
                sel_end = MAX(editable->selection_end_pos, 0);
                sel_len = sel_end - sel_start;
                if(sel_len < 0)
                  sel_len *= -1;
      
                /* Clear selection */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_set_point(GTK_TEXT(w), (guint)sel_start);
                gtk_text_forward_delete(GTK_TEXT(w), (guint)sel_len);
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert point since selected buffer start
                 * position may not be the same as where the insert
                 * position is.
                 */
                gtk_text_set_point(GTK_TEXT(w), (guint)sel_start);

                /* Insert text */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_insert(
                  GTK_TEXT(w),
                  (style != NULL) ? style->font : NULL,
                  NULL,           /* Foreground color */
                  NULL,           /* Background color */
                  buf,
                  buf_len
                );
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert and cursor position */
                gtk_text_set_point(
                  GTK_TEXT(w),
                  (guint)(sel_start + buf_len)
                );
                gtk_editable_set_position(
                  editable, sel_start + buf_len
                );

                /* No need to call text insert callback? */
            }
            else
            {
                gint start_pos;

                start_pos = gtk_editable_get_position(editable);
                gtk_text_set_point(GTK_TEXT(w), start_pos);

                /* Insert text */
                gtk_text_freeze(GTK_TEXT(w));
                gtk_text_insert(
                  GTK_TEXT(w),
                  (style != NULL) ? style->font : NULL,
                  NULL,           /* Foreground color */
                  NULL,           /* Background color */
                  buf,
                  buf_len
                );
                gtk_text_thaw(GTK_TEXT(w));

                /* Set new insert and cursor position */
                gtk_text_set_point(
                  GTK_TEXT(w),
                  (guint)(start_pos + buf_len)
                );
                gtk_editable_set_position(
                  editable, start_pos + buf_len
                );

                /* Need to call text insert callback, insert of text
                 * call above dosen't seem to trigger it.   
                 */
                EditorTextInsertCB(
                  editable,
                  (const gchar *)buf, buf_len,
                  &start_pos, editor
                );
            }

            /* Mark branch as having changes */
            EditorItemSetToplevelHasChanges(editor, branch, TRUE);
          }
          break;

          case EditorItemTypeFile:
            break;
      }

      /* Delete clipboard buffer */
      g_free(buf);

      /* Update menus */
      EditorUpdateMenus(editor);
}

/*
 *    Delete selected text callback.
 */
void EditorDeleteCB(GtkWidget *widget, gpointer data)
{
      return;
}

/*
 *    Maps the clipboard browser.
 */
void EditorClipboardBrowserCB(GtkWidget *widget, gpointer data)
{
      ClipboardBrowserMap();
}

/*
 *    Select all text callback.
 */
void EditorSelectAllCB(GtkWidget *widget, gpointer data)
{
      GtkEditable *editable = NULL;
      GtkText *text;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, editor->selected_branch
      );
      if(item == NULL)
          return;

      switch(item->type)
      {
        case EditorItemTypeHeader:
          editable = (GtkEditable *)editor->header_text;
          break;
        case EditorItemTypeSection:
          editable = (GtkEditable *)editor->section_text;
          break;
          case EditorItemTypeFile:
            break;
      }
      text = (GtkText *)editable;
      if(editable == NULL)
          return;

      gtk_editable_select_region(editable, 0, -1);
}

/*
 *    Unselect all text callback.
 */
void EditorUnselectAllCB(GtkWidget *widget, gpointer data)
{
      GtkEditable *editable = NULL;
      GtkText *text;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;
      
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, editor->selected_branch
      );
      if(item == NULL)
          return;
      
      switch(item->type)
      {
        case EditorItemTypeHeader: 
          editable = (GtkEditable *)editor->header_text;
          break;
        case EditorItemTypeSection:
          editable = (GtkEditable *)editor->section_text;
          break;
          case EditorItemTypeFile:
            break;
      }
      text = (GtkText *)editable;
      if(editable == NULL)
          return;

      gtk_editable_select_region(editable, 0, 0);
}

/*
 *    Maps the preferences window.
 */
void EditorPreferencesCB(GtkWidget *widget, gpointer data)
{
      medit_core_struct *core_ptr;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      core_ptr = MEDIT_CORE(editor->core_ptr);
      if(core_ptr != NULL)
      {
          pref_struct *pref = core_ptr->pref;
          GtkCTreeNode *branch;
          GtkCTreeRow *branch_row;
          GtkCTree *ctree;
          char *strptr;

          ctree = (GtkCTree *)((pref == NULL) ?
            NULL : pref->catagory_ctree
          );
          if(ctree != NULL)
          {
            branch = gtk_ctree_node_nth(ctree, 0);

            while(branch != NULL)
            {
                strptr = PrefPanelGetBranchText(pref, branch);
                if(strptr != NULL)
                {
                  if(!g_strcasecmp(strptr, "Appearance"))
                  {
                      gtk_ctree_expand(ctree, branch);
                      gtk_ctree_select(ctree, branch);
                      break;
                  }
                }

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


            PrefDoFetch(pref);
            PrefMap(pref);
          }
      }

      return;
}


/* Macro to set the GtkText *text variable to the appropriate
 * value. Uses variables editor, branch, item, and text.
 */
#define EDITOR_GET_TEXT_WIDGET      {           \
 text = NULL;                             \
 switch(item->type) {                     \
  case EditorItemTypeHeader:              \
   break;                           \
  case EditorItemTypeSection:             \
   text = GTK_TEXT(editor->section_text); \
   break;                           \
  case EditorItemTypeFile:                \
   break;                           \
 }                                  \
}

/* Pre code macro for editor fmt callbacks, sets up variables;
 * font, style, text, editable, ctree, branch, item, insert_pos,
 * end_pos, core_ptr, styles_list, and editor.
 */
#define EDITOR_FMT_CB_PRE     \
      GdkFont *font; \
      GtkStyle *style; \
      GtkWidget *toplevel; \
      GtkText *text; \
      GtkEditable *editable; \
      GtkCTree *ctree; \
      GtkCTreeNode *branch; \
      editor_item_struct *item; \
      int insert_pos, end_pos; \
      medit_core_struct *core_ptr; \
      medit_styles_list_struct *styles_list; \
      editor_struct *editor = EDITOR(data); \
      if(editor == NULL) \
          return; \
 \
      if(editor->processing) \
          return; \
 \
      core_ptr = MEDIT_CORE(editor->core_ptr); \
      toplevel = EDITOR_TOPLEVEL(editor); \
      ctree = (GtkCTree *)editor->layout_ctree; \
      branch = editor->selected_branch; \
      if((core_ptr == NULL) || (ctree == NULL) || (branch == NULL)) \
          return; \
 \
      styles_list = &core_ptr->styles_list; \
      style = styles_list->edit_text_standard; \
      font = ((style == NULL) ? NULL : style->font); \
 \
      item = EditorBranchGetData(ctree, branch); \
      if(item == NULL) \
          return; \
 \
      EDITOR_GET_TEXT_WIDGET \
      if(text == NULL) \
          return; \
      else \
          editable = GTK_EDITABLE(text); \
 \
      if(editable->has_selection) \
      { \
          insert_pos = (gint)editable->selection_start_pos; \
          end_pos = (gint)editable->selection_end_pos; \
          if(insert_pos > end_pos) \
          { \
            int tmp_pos = end_pos; \
            end_pos = insert_pos; \
            insert_pos = tmp_pos; \
          } \
      } \
      else \
      { \
          insert_pos = (gint)editable->current_pos; \
          end_pos = -1; \
      } \
 \

/* Post code macro for editor fmt callbacks, uses variables;
 * insert_pos, end_pos, new_str, new_str_len, editable, and editor.
 */
#define EDITOR_FMT_CB_POST    \
{ \
 gint position; \
 \
 position = insert_pos; \
 /* Need to call insert callback, notifying of changes, this will \
  * record an undo and mark has changes on the selected branch. \
  */ \
 EditorTextInsertCB( \
  editable, new_str, new_str_len, \
  &position, editor \
 ); \
 gtk_editable_set_position(editable, insert_pos + new_str_len); \
 gtk_text_set_point(GTK_TEXT(editable), insert_pos + new_str_len); \
 gtk_widget_grab_focus(GTK_WIDGET(editable)); \
}

/*
 *    Paragraph left callback.
 */
void EditorFmtParagraphLeftCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "<LP>";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Paragraph right callback.
 */
void EditorFmtParagraphRightCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "<RP>";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Paragraph indented callback.
 */
void EditorFmtParagraphIndentedCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "<IP>";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *      List item paragraph callback.
 */
void EditorFmtListItemCB(GtkWidget *widget, gpointer data)
{
      const gchar *new_str = "<TP>";
      gint new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Bold callback.
 */
void EditorFmtBoldCB(GtkWidget *widget, gpointer data)
{
      const gchar *new_str = "<fB>";
      gint new_str_len;
      EDITOR_FMT_CB_PRE
 
      new_str_len = strlen(new_str);
  
      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST

      /* Ending tag */
      new_str = "<fR>";
      if(end_pos < 0)
      {
          insert_pos = insert_pos + new_str_len;
          new_str_len = strlen(new_str);

          gtk_editable_set_position(editable, insert_pos);
          gtk_text_freeze(text);
          gtk_text_insert(
            text, font, NULL, NULL,
            new_str, new_str_len
          );
          gtk_text_thaw(text);
      }
      else
      {
          int new_end_pos = end_pos + new_str_len;

          insert_pos = end_pos + new_str_len;
          end_pos = -1;
          new_str_len = strlen(new_str);
          new_end_pos += new_str_len;
 
          gtk_text_set_point(text, insert_pos);
          gtk_text_freeze(text);
          gtk_text_insert(
            text, font, NULL, NULL,
            new_str, new_str_len
          );
          gtk_text_thaw(text);
      }

      EDITOR_FMT_CB_POST
}

/*
 *      Underline callback.
 */
void EditorFmtUnderlineCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "<fI>";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);
 
      EDITOR_FMT_CB_POST

      /* Ending tag */
      new_str = "<fR>";
      if(end_pos < 0)
      {
          insert_pos = insert_pos + new_str_len;
          new_str_len = strlen(new_str);

          gtk_editable_set_position(editable, insert_pos);              
          gtk_text_freeze(text);
          gtk_text_insert(
            text, font, NULL, NULL,
            new_str, new_str_len
          ); 
          gtk_text_thaw(text);
      }
      else
      {
          int new_end_pos = end_pos + new_str_len;

          insert_pos = end_pos + new_str_len;
          end_pos = -1;
          new_str_len = strlen(new_str); 
          new_end_pos += new_str_len;

          gtk_text_set_point(text, insert_pos);
          gtk_text_freeze(text);
          gtk_text_insert(
            text, font, NULL, NULL,
            new_str, new_str_len  
          );
          gtk_text_thaw(text);
      }

      EDITOR_FMT_CB_POST
}

/*
 *    Line break callback.
 */
void EditorFmtLineBreakCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "<br>";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Ampersand callback.
 */
void EditorFmtAmpersandCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "&amp;";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *      Less than callback.
 */
void EditorFmtLessThanCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "&lt;";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,   
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Greater than callback.
 */
void EditorFmtGreaterThanCB(GtkWidget *widget, gpointer data)
{
      const char *new_str = "&gt;";
      int new_str_len;
      EDITOR_FMT_CB_PRE

      new_str_len = strlen(new_str);

      gtk_text_set_point(text, insert_pos);
      gtk_text_freeze(text);
      gtk_text_insert(
          text, font, NULL, NULL,
          new_str, new_str_len
      );
      gtk_text_thaw(text);

      EDITOR_FMT_CB_POST
}

/*
 *    Strip tags from selected text callback.
 */
void EditorStripTagsCB(GtkWidget *widget, gpointer data)
{
      gint i, j, c, len, made_strip, strip_loss = 0;
      EDITOR_FMT_CB_PRE

      /* No selection? */
      if(end_pos < 0)
          return;

      len = end_pos - insert_pos;
      if(len <= 0)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No text selected!",
"No text selected to strip tags from.",
"You have not selected any text in which to strip\n\
tags from. If you wish to strip tags from the\n\
entire section then you must first go to\n\
edit->select all and then strip tags otherwise\n\
use the pointer to select the section of text you\n\
want to strip tags from and then strip tags.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      do
      {
          made_strip = 0;
          end_pos = MIN(end_pos, gtk_text_get_length(text));

          /* Itterate from insert position to ending position */
          for(i = insert_pos, len = 0; i < end_pos; i++)
          {
            c = GTK_TEXT_INDEX(text, i);

            /* Is character a tag deliminator start? */
            if(c == '<')
            {
                len++;  /* Count deliminator start char */

                /* Seek to ending tag deliminator (if any) */
                for(j = i + 1; j < end_pos; j++)
                {
                  len++;      /* Count this char regardless */

                  /* Begin calculating length */
                  c = GTK_TEXT_INDEX(text, j);
                  if(c == '>')
                      break;
                }
                /* No ending tag found? */
                if(j >= end_pos)
                  len = 0;

                break;
            }
          }

          /* Got a tag? */
          if((i < end_pos) && (len > 0))
          {
            /* Call text delete callback before deleting */
            EditorTextDeleteCB(
                editable, i, i + len, editor
            );

            /* Delete characters specified by len */
            gtk_text_freeze(text);
            gtk_text_set_point(text, i);
            gtk_text_forward_delete(text, len);
            gtk_text_thaw(text);

            made_strip = 1;
            strip_loss += len;
          }

          /* Need to reduce end position */
          end_pos -= len;

      } while(made_strip);


      EditorUpdateMenus(editor);
      EditorSetStatusMessage(editor, "Strip tags done");
}

#undef EDITOR_FMT_CB_POST
#undef EDITOR_FMT_CB_PRE
#undef EDITOR_GET_TEXT_WIDGET


/*
 *    Maps the Find In Pages dialog.
 */
void EditorMapFIPCB(GtkWidget *widget, gpointer data)
{
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      EditorFIPMap(editor->fip_dialog);

      return;
}


/*
 *    Find bar find button callback.
 */
void EditorFindBarFindCB(GtkWidget *widget, gpointer data)
{
      gchar *value;
      GtkCombo *find_combo;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      find_combo = (GtkCombo *)editor->find_combo;
      if(find_combo == NULL)
          return;

      value = STRDUP(gtk_entry_get_text(
          GTK_ENTRY(find_combo->entry)
      ));

      /* Call combo widget to set new value and activate, this will
       * then call EditorFindBarFindActivateCB()
       */
      GUIComboActivateValue(GTK_WIDGET(find_combo), value);

      g_free(value);
}

/*
 *    Find bar find combo box activate callback.
 */
void EditorFindBarFindActivateCB(GtkWidget *widget, gpointer data)
{
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))                    
          return;

      EditorSetBusy(editor);

      EditorDoFindCB(
          editor,
          (GtkCombo *)editor->find_combo,
          FALSE
      );

      EditorUpdateMenus(editor);
      EditorSetReady(editor);
}

/*
 *    Find bar replace button callback.
 */
void EditorFindBarReplaceCB(GtkWidget *widget, gpointer data)
{
      gchar *value;
      GtkCombo *replace_combo;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      replace_combo = (GtkCombo *)editor->replace_combo;
      if(replace_combo == NULL)
          return;

      value = STRDUP(gtk_entry_get_text(
          GTK_ENTRY(replace_combo->entry)
      ));

      /* Call combo widget to set new value and activate, this will
       * then call EditorFindBarReplaceActivateCB()
       */
      GUIComboActivateValue(GTK_WIDGET(replace_combo), value);

      g_free(value);
}

/*
 *    Find bar replace all button callback.
 *
 *    Note this function is a bit different than the other find and
 *    replace callback functions, it does all the setting up of values
 *    and calling procedure functions by itself.
 */
void EditorFindBarReplaceAllCB(GtkWidget *widget, gpointer data)
{
      gboolean case_sensitive = FALSE;
      gint replace_count = 0;
      GtkWidget *toplevel;
      GtkText *text = NULL;
      GtkEditable *editable = NULL;
      GtkCombo *find_combo, *replace_combo;
      gchar *needle, *replacement;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Get selected branch's item data */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, editor->selected_branch
      );
      if(item == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No item selected!",
"No item selected to search and replace through.",
"You have not selected an item with text\n\
to search and replace through.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      /* Get pointers to combo widgets */
      find_combo = (GtkCombo *)editor->find_combo;
      replace_combo = (GtkCombo *)editor->replace_combo;

      if((find_combo == NULL) || (replace_combo == NULL))
          return;


      /* Get values from each combo box (coppied) */
      needle = STRDUP(gtk_entry_get_text(
          GTK_ENTRY(find_combo->entry)
      ));

      replacement = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
      if(replacement != NULL)
          replacement = strdup(replacement);

      /* Update values to combo lists */
      GUIComboAddItem(GTK_WIDGET(find_combo), needle);
      GUIComboAddItem(GTK_WIDGET(replace_combo), replacement);


      EditorSetBusy(editor);

      /* Handle by item type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          /* Get pointer to header text */
          text = (GtkText *)editor->header_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

          /* Do replace all */
          replace_count += EditorDoReplaceAll(
            editor, text,
            needle, replacement,
            case_sensitive
          );

          gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);

          /* Did we get and replacements? */
          if(replace_count > 0)
          {
            gchar *buf = g_strdup_printf(
                "Replace done, %i occurances replaced",
                replace_count
            );
            EditorSetStatusMessage(editor, buf);
            g_free(buf);
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched and replaced was\n\
not found in the text, implying that the search\n\
string does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            EditorSetStatusMessage(editor, "Replace done");
          }
          break;

        case EditorItemTypeSection:
          /* Get pointer to section text */
          text = (GtkText *)editor->section_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

          /* Do replace all */
          replace_count += EditorDoReplaceAll(
            editor, text,
            needle, replacement,
            case_sensitive
          );

          gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);

          /* Did we get and replacements? */
          if(replace_count > 0)
          {
            gchar *buf = g_strdup_printf(
                "Replace done, %i occurances replaced",
                replace_count
            );
            EditorSetStatusMessage(editor, buf);
            g_free(buf);
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched and replaced was\n\
not found in the text, implying that the search\n\
string does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
            EditorSetStatusMessage(editor, "Replace done");
          }
          break;

          case EditorItemTypeFile:
            break;
      }


      /* Mark selected branch as having changes */
      EditorItemSetToplevelHasChanges(
          editor, editor->selected_branch, TRUE
      );

      /* Update menus */
      EditorUpdateMenus(editor);

      EditorSetReady(editor);

      g_free(replacement);
      g_free(needle);
}

/*
 *    Same as EditorFindBarReplaceAllCB() except that this will
 *    replace all occurances in all header and sections for the
 *    entire selected manual page.
 *
 *    This function relies on another function
 *    EditorFindBarReplaceEntirePageIterationCB() to iterate and
 *    recurse to replace branch and child branches.
 */
void EditorFindBarReplaceEntirePageCB(GtkWidget *widget, gpointer data)
{
      gint status;
      gint replace_count = 0;
      gboolean case_sensitive = FALSE;
      GtkCombo *find_combo, *replace_combo;
      GtkCTreeNode *branch;
      GtkWidget *toplevel;
      gchar *needle, *replacement;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Confirm before replace */
      CDialogSetTransientFor(toplevel);
      status = CDialogGetResponse(
"Confirm Replace Entire Page",
"You will not be able to undo this operation,\n\
are you sure you want to find and replace the\n\
entire selected Manual Page?",
"You are about the search and replace through\n\
the entire selected Manual Page, you will not\n\
be able to undo this operation. If you are\n\
unsure say `cancel'.",
          CDIALOG_ICON_WARNING,
          CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_CANCEL |
          CDIALOG_BTNFLAG_HELP,
          CDIALOG_BTNFLAG_CANCEL
      );
      CDialogSetTransientFor(NULL);
      switch(status)
      {
        case CDIALOG_RESPONSE_NOT_AVAILABLE:
        case CDIALOG_RESPONSE_CANCEL:
        case CDIALOG_RESPONSE_NO:
          return;
          break;

        case CDIALOG_RESPONSE_YES_TO_ALL:
        case CDIALOG_RESPONSE_YES:
          break;
      }

      /* Get selected branch */
      branch = editor->selected_branch;
      /* If not NULL, get toplevel parent branch */
      if(branch != NULL)
         branch = EditorItemGetToplevel(editor, branch);
      if(branch == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No Manual Page selected!",
"No Manual Page selected to search and replace through.",
"You have not selected a manual page to search and\n\
replace through.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      /* Get pointers to combo widgets */
      find_combo = (GtkCombo *)editor->find_combo;
      replace_combo = (GtkCombo *)editor->replace_combo;

      if((find_combo == NULL) || (replace_combo == NULL))
          return;

      /* Get values from each combo box (coppied) */
      needle = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
      if(needle != NULL)
          needle = strdup(needle);

      replacement = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
      if(replacement != NULL)
          replacement = strdup(replacement);

      /* Update values to combo lists */
      GUIComboAddItem(GTK_WIDGET(find_combo), needle);
      GUIComboAddItem(GTK_WIDGET(replace_combo), replacement);                

      /* Begin search and replace all occurances through all
       * child branches.
       */
      EditorSetBusy(editor);

      replace_count += EditorFindBarReplaceEntirePageIterationCB(
          editor, branch, needle, replacement, case_sensitive
      );
      if(replace_count > 0)
      {
          gchar *buf = g_strdup_printf(
            "Replace done, %i occurances replaced",
            replace_count
          );
          EditorSetStatusMessage(editor, buf);
          g_free(buf);
      }
      else
      {
          EditorSetStatusMessage(
            editor,
            "Replace done, no occurances found"
          );
      }

      /* Mark selected branch as having changes */
      EditorItemSetToplevelHasChanges(
          editor, editor->selected_branch, TRUE
      );

      /* Update menus */
      EditorUpdateMenus(editor);

      EditorSetReady(editor);

      /* Delete find buffers */
      g_free(replacement);
      g_free(needle);
}

/*
 *    Replace entire page iteration callback, called by function
 *    EditorFindBarReplaceEntirePageCB() to replace all occurances
 *    on branch and recurse into child branch.
 *
 *    The editor's selected branch will be updated then force selected
 *    to the given branch.
 *
 *    Inputs assumed valid, returns the number of replacements.
 */
static int EditorFindBarReplaceEntirePageIterationCB(
      editor_struct *editor, GtkCTreeNode *branch,
      char *needle, const char *replacement,
      gboolean case_sensitive
)
{
      gint replace_count = 0;
      GtkText *text = NULL;
      GtkEditable *editable = NULL;
      GtkCTreeRow *branch_row;
      editor_item_struct *item;


      if(branch == NULL)
          return(replace_count);

      /* Note, make sure editor is not set as processing, otherwise
       * we won't be able to apply and fetch values.
       */

      /* Force select given branch */
      EditorBranchSelect(editor, branch);


      /* Begin calling replace all procedures */

      EditorSetBusy(editor);

      /* Get selected branch's item data */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, branch
      );
      switch((item != NULL) ? item->type : -1)
      {
        case -1:
          break;

        case EditorItemTypeHeader:
          /* Get pointer to header text */
          text = (GtkText *)editor->header_text;
          if(text == NULL)
            break;
          else
            editable = GTK_EDITABLE(text);

          gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

          /* Do replace all */   
          replace_count += EditorDoReplaceAll(
            editor, text,
            needle, replacement,  
            case_sensitive
          );

          gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);
          break;

        case EditorItemTypeSection:
          /* Get pointer to section text */
          text = (GtkText *)editor->section_text;
          if(text == NULL)
            break;
          else
            editable = GTK_EDITABLE(text);

          gtk_widget_set_sensitive(GTK_WIDGET(text), FALSE);

          /* Do replace all */
          replace_count += EditorDoReplaceAll(
            editor, text,
            needle, replacement,
            case_sensitive
          );

          gtk_widget_set_sensitive(GTK_WIDGET(text), TRUE);
          break;

          case EditorItemTypeFile:
            break;
      }

      EditorSetReady(editor);


      /* Itterate through child branches */
      branch_row = GTK_CTREE_ROW(branch);
      branch = ((branch_row == NULL) ? NULL : branch_row->children);
      while(branch != NULL)
      {
          replace_count += EditorFindBarReplaceEntirePageIterationCB(
            editor, branch, needle, replacement, case_sensitive
          );

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

      return(replace_count);
}


/*
 *    Find bar replace combo box activate callback.
 */
void EditorFindBarReplaceActivateCB(GtkWidget *widget, gpointer data)
{
      gboolean case_sensitive = FALSE;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      /* Need to update the last item on the find combo */
      if(editor->find_combo != NULL)
      {
          GtkCombo *combo = (GtkCombo *)editor->find_combo;
          gchar *strptr;

          /* Get value from combo's entry widget and duplicate
           * the value if it is not NULL.
           */

          strptr = gtk_entry_get_text(GTK_ENTRY(combo->entry));
          if(strptr != NULL)
            strptr = strdup(strptr);

          GUIComboAddItem(GTK_WIDGET(combo), strptr);

          /* Free value which we duplicated */
          g_free(strptr);
      }

      EditorSetBusy(editor);

      EditorDoReplaceCB(
          editor,
          (GtkCombo *)editor->find_combo,
          (GtkCombo *)editor->replace_combo,
          case_sensitive
      );

      EditorUpdateMenus(editor);
      EditorSetReady(editor);
}


/*
 *    Front end for find callbacks to perform find procedure.
 */
static void EditorDoFindCB(
      editor_struct *editor, GtkCombo *find_combo, gboolean case_sensitive
)
{
      gboolean got_match, search_wrapped;
      editor_item_struct *item;
      GtkWidget *toplevel;
      GtkText *text;
      GtkEditable *editable;
      gint start_pos;
      gchar *strptr, *haystack = NULL, *needle = NULL;


      if((editor == NULL) || (find_combo == NULL))
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Get selected branch's item data */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, editor->selected_branch
      );
      if(item == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No item selected!",
"No item selected to search through.",
"You have not selected an item with text\n\
to search through.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }


      /* Get needle buffer (coppied) */
      strptr = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
      if(strptr != NULL)
          needle = strdup(strptr);
      else
          needle = NULL;


      /* Handle by item type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          /* Get pointer to header text */
          text = (GtkText *)editor->header_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          /* Get haystack from header text */
          haystack = gtk_editable_get_chars(editable, 0, -1);

          /* Set starting position plus one */
          start_pos = gtk_editable_get_position(editable) + 1;

          /* Do find */
          got_match = EditorDoFind(
            editor, text,
            haystack, needle,
            (gint)gtk_text_get_length(text), start_pos,
            case_sensitive,
            TRUE,             /* Scroll to matched position */
            &search_wrapped
          );
          /* Did we get a match? */
          if(got_match)
          {
            /* Search wrapped? */
            if(search_wrapped)
            {
                CDialogSetTransientFor(toplevel);
                CDialogGetResponse(
"Search wrapped!",
"Search wrapped!",
"The matched string was found before the last\n\
cursor position, implying that no further text\n\
was matched after the cursor position.",
                  CDIALOG_ICON_INFO,
                  CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_OK
                );
                CDialogSetTransientFor(NULL);
            }
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched was not found\n\
in the text, implying that the search string\n\
does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          break;

        case EditorItemTypeSection:
          /* Get pointer to section text */
          text = (GtkText *)editor->section_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          /* Get haystack from section text */
          haystack = gtk_editable_get_chars(editable, 0, -1);

          /* Set starting position plus one */
          start_pos = gtk_editable_get_position(editable) + 1;

          /* Do find */
          got_match = EditorDoFind(
            editor, text,
            haystack, needle,
            (gint)gtk_text_get_length(text), start_pos,
            case_sensitive,
            TRUE,             /* Scroll to matched position */
            &search_wrapped
          );
          /* Did we get a match? */
          if(got_match)
          {
            /* Search wrapped? */
            if(search_wrapped)
            {
                CDialogSetTransientFor(toplevel);
                CDialogGetResponse(
"Search wrapped!",
"Search wrapped!",
"The matched string was found before the last\n\
cursor position, implying that no further text\n\
was matched after the cursor position.",
                  CDIALOG_ICON_INFO,
                  CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                  CDIALOG_BTNFLAG_OK
                );
                CDialogSetTransientFor(NULL);
            }
          }
          else
          {
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(  
"String not found!",
"String not found!",
"The given string to be searched was not found\n\
in the text, implying that the search string\n\
does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          break;

          case EditorItemTypeFile:
            break;
      }

      g_free(haystack);
      g_free(needle);
}

/*
 *    Front end for replace callbacks to perform find and replace
 *    procedure.
 */
static void EditorDoReplaceCB(
      editor_struct *editor,
      GtkCombo *find_combo, GtkCombo *replace_combo,
      gboolean case_sensitive
)
{
      gboolean made_replace;
      editor_item_struct *item;
      GtkWidget *toplevel;
      GtkText *text;
      GtkEditable *editable;
      gint start_pos;
      gchar *strptr, *haystack = NULL, *needle = NULL,
            *replacement = NULL;


      if((editor == NULL) || (find_combo == NULL) ||
         (replace_combo == NULL)
      )
          return;

      toplevel = EDITOR_TOPLEVEL(editor);

      /* Get selected branch's item data */
      item = EditorBranchGetData(
          (GtkCTree *)editor->layout_ctree, editor->selected_branch
      );
      if(item == NULL)
      {
          CDialogSetTransientFor(toplevel);
          CDialogGetResponse(
"No item selected!",
"No item selected to search and replacethrough.",
"You have not selected an item with text\n\
to search and replace through.",
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }

      /* Get needle buffer (coppied) */
      strptr = gtk_entry_get_text(GTK_ENTRY(find_combo->entry));
      if(strptr != NULL)
          needle = strdup(strptr); 
      else
          needle = NULL;

      /* Get replacement (coppied) */
      strptr = gtk_entry_get_text(GTK_ENTRY(replace_combo->entry));
      if(strptr != NULL)
          replacement = strdup(strptr);
      else
          replacement = NULL;


      /* Handle by item type */
      switch(item->type)
      {
        case EditorItemTypeHeader:
          /* Get pointer to header text */
          text = (GtkText *)editor->header_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          /* Get haystack from header text */
          haystack = gtk_editable_get_chars(editable, 0, -1);

          /* Set starting position plus one */
          start_pos = gtk_editable_get_position(editable) + 1;

          /* Do replace */
          made_replace = EditorDoReplace(
            editor, text,
            haystack, needle, replacement,
            (gint)gtk_text_get_length(text), start_pos,
            case_sensitive
          );
          /* Did we make a replacement? */
          if(made_replace)
          {

          }
          else
          {
            /* No replacement, implies search string not found */
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched and replaced\n\
was not found in the text, implying that the\n\
search string does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          break;

        case EditorItemTypeSection:
          /* Get pointer to section text */
          text = (GtkText *)editor->section_text;
          if(text != NULL)
            editable = GTK_EDITABLE(text);
          else
            break;

          /* Get haystack from section text */
          haystack = gtk_editable_get_chars(editable, 0, -1);

          /* Set starting position plus one */
          start_pos = gtk_editable_get_position(editable) + 1;

          /* Do replace */
          made_replace = EditorDoReplace(
            editor, text,
            haystack, needle, replacement,
            (gint)gtk_text_get_length(text), start_pos,
            case_sensitive
          );
          /* Did we make a replacement? */
          if(made_replace)
          {

          }
          else
          {
            /* No replacement, implies search string not found */
            CDialogSetTransientFor(toplevel);
            CDialogGetResponse(
"String not found!",
"String not found!",
"The given string to be searched and replaced\n\
was not found in the text, implying that the\n\
search string does not exist in the text.",
                CDIALOG_ICON_WARNING,
                CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
                CDIALOG_BTNFLAG_OK
            );
            CDialogSetTransientFor(NULL);
          }
          break;

          case EditorItemTypeFile:
            break;
      }

      /* Mark selected branch as having changes */
      EditorItemSetToplevelHasChanges(
          editor, editor->selected_branch, TRUE
      );

      g_free(replacement);
      g_free(haystack);
      g_free(needle);
}


/*
 *    Updates the position relative to be just under the given
 *    widget as data.
 */
static void EditorMenuMapPositionCB(
      GtkMenu *menu, gint *x, gint *y, gpointer data
)
{
      gint rx, ry;
      GtkWidget *rel_widget = (GtkWidget *)data;
      if((menu == NULL) || (rel_widget == NULL))
          return;

      if(!GTK_WIDGET_NO_WINDOW(rel_widget))
      {
          GUIGetWindowRootPosition(
            rel_widget->window,
            &rx, &ry
          );

          if(x != NULL)
            *x = rx;
          if(y != NULL)
            *y = ry + rel_widget->allocation.height;
      }
}

/*
 *    Right-click menu mapping.
 */
gint EditorMenuMapCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
      static gboolean reenterant = FALSE;
      GtkWidget *w;
      GdkEventButton *button;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) ||
         (event == NULL) ||
         (widget == NULL)
      )
          return(TRUE);

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

      /* Is event type button press? If so then get pointer to button
       * event structure.
       */
      if((event->type == GDK_BUTTON_PRESS) ||
         (event->type == GDK_BUTTON_RELEASE)
      )
          button = (GdkEventButton *)event;
      else
          button = NULL;

      /* Layout ctree? */
      if(widget == editor->layout_ctree)
      {
          w = editor->layout_menu;
          if((button != NULL) && (w != NULL))
          {
            if(button->button == 3)
            {
                gint row, column;
                GtkCTreeNode *branch;


                /* Try to select branch button event occured
                 * over first.
                 */
                if(gtk_clist_get_selection_info(
                  (GtkCList *)widget,
                  button->x, button->y,
                  &row, &column
                ))
                {
                  branch = gtk_ctree_node_nth(
                      (GtkCTree *)widget, row
                  );
                  /* Branch valid and not selected? */
                  if((branch != NULL) &&
                     (branch != editor->selected_branch)
                  )
                  {
                      EditorBranchSelect(editor, branch);
                  }
                }

                /* Map menu */
                gtk_menu_popup(
                  GTK_MENU(w),
                  NULL, NULL, NULL, editor,
                  button->button, button->time
                );
            }
          }
      }
      /* Edit panel text widgets */
      else if((widget == editor->header_text) ||
            (widget == editor->section_text)
      )
      {
          w = editor->edit_panel_menu;
          if((button != NULL) && (w != NULL))
          {
            /* Call key press event for text widget since position
             * of cursor may have changed.
             */
            EditorTextKeyEventCB(
                (GtkEditable *)widget, NULL, data
            );

            if(button->button == 3)
            {
                /* Map menu */
                gtk_menu_popup(
                  GTK_MENU(w),
                  NULL, NULL, NULL, editor,
                  button->button, button->time
                );

                /* Need to mark the text widget's button as 0 or
                 * else it will keep marking.
                 */
                GTK_TEXT(widget)->button = 0;
                gtk_grab_remove(widget);
            }
          }
      }

      reenterant = FALSE;
      return(TRUE);
}

/*
 *    Menu mapping from a button "pressed" signal callback.
 */
void EditorButtonMenuMapCB(GtkButton *button, gpointer data)
{
      static gboolean reenterant = FALSE;
      GtkWidget *w;
      editor_struct *editor = EDITOR(data);
      if((button == NULL) || (editor == NULL))
          return;

      if(reenterant)
          return;
      else 
          reenterant = TRUE;

#define DO_BUTTON_PRESSED     \
{ \
 if(button != NULL) \
 { \
  gtk_grab_remove(GTK_WIDGET(button)); \
  \
  while(gtk_events_pending() > 0) \
   gtk_main_iteration(); \
  \
  button->in_button = 1; \
  button->button_down = 1; \
  gtk_signal_emit_by_name(GTK_OBJECT(button), "pressed"); \
 } \
}
      /* New menu button */
      if(GTK_WIDGET(button) == editor->new_btn)
      {
          w = editor->new_btn_new_menu;
          if(w != NULL)
          {
            /* Map menu */
            gtk_menu_popup(
               GTK_MENU(w),
               NULL, NULL,
               EditorMenuMapPositionCB, button,
               1, GDK_CURRENT_TIME
            );
            DO_BUTTON_PRESSED
          }
      }
      /* Find menu button */
      else if(GTK_WIDGET(button) == editor->find_btn)
      {
          w = editor->find_bar_find_menu;
          if(w != NULL)
          {
            /* Map menu */
            gtk_menu_popup(
               GTK_MENU(w),
               NULL, NULL,
               EditorMenuMapPositionCB, button,
               1, GDK_CURRENT_TIME
            );
            DO_BUTTON_PRESSED
          }
      }
      /* Replace menu button */
      else if(GTK_WIDGET(button) == editor->replace_btn)
      {
          w = editor->find_bar_replace_menu;
          if(w != NULL)
          {
            /* Map menu */
            gtk_menu_popup(
               GTK_MENU(w),
               NULL, NULL,
               EditorMenuMapPositionCB, button,
               1, GDK_CURRENT_TIME
            );
            DO_BUTTON_PRESSED
          }
      }

#undef DO_BUTTON_PRESSED

      reenterant = FALSE;
}

/*
 *    Menu hide callback.
 *
 *    Used for buttons which map menus, when the menu for a button
 *    unmaps (hides) then function handles the "hide" signal and sets
 *    the associated button released by emitting the "released"
 *    signal.
 */
void EditorMenuHideCB(GtkWidget *widget, gpointer data)
{
      static gboolean reenterant = FALSE;
      GtkWidget *w;
      editor_struct *editor = EDITOR(data);
      if((widget == NULL) || (editor == NULL))
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

#define DO_BUTTON_RELEASE     \
{ \
 if(w != NULL) \
 { \
  GTK_BUTTON(w)->in_button = 0; \
  gtk_signal_emit_by_name(GTK_OBJECT(w), "released"); \
 } \
}

      /* New menu? */
      if(widget == editor->new_btn_new_menu)
      {
          w = editor->new_btn;
          DO_BUTTON_RELEASE
      }
      /* Find menu? */
      else if(widget == editor->find_bar_find_menu)
      {
          w = editor->find_btn;
          DO_BUTTON_RELEASE
      }
      /* Replace menu? */
      else if(widget == editor->find_bar_replace_menu)
      {
          w = editor->replace_btn;
          DO_BUTTON_RELEASE
      }

#undef DO_BUTTON_RELEASE

      reenterant = FALSE;
      return;
}


/*
 *    Layout ctree expand/collapse callback.
 */
void EditorLayoutCTreeDoExpandCB(GtkWidget *widget, gpointer data)
{
      GtkCTree *ctree;
      GtkCTreeNode *branch; 
      GtkCTreeRow *branch_row;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return;

      if(!editor->initialized)
          return;

      ctree = (GtkCTree *)editor->layout_ctree;
      if(ctree == NULL)
          return;

      branch = editor->selected_branch;
      if(branch == NULL)
          return;

      branch_row = GTK_CTREE_ROW(branch);
      if(branch_row == NULL)
          return;
      
      if(branch_row->is_leaf)
          return;

      if(branch_row->expanded)
          gtk_ctree_collapse(ctree, branch);
      else
          gtk_ctree_expand(ctree, branch);

      return;
}


/*
 *    Layout ctree select row callback.
 */
void EditorLayoutCTreeSelectCB(
      GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
      gint row;
      GtkCList *clist;
      editor_item_struct *item;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (ctree == NULL))
          return;

      if(GTK_WIDGET(ctree) == editor->layout_ctree)
      {
          clist = (GtkCList *)ctree;

          EditorSetBusy(editor);       

          /* A branch selected previously? */
          if(editor->selected_branch != NULL)
          {
            /* Unselect signal will have the values applied, we don't
             * need to apply values here.
             */
          }

          /* Update selected branch */
          editor->selected_branch = branch;

          /* Set DND drag icon */
          EditorDNDSetIcon(
            editor, 
            EditorBranchGetData(ctree, branch)
          );

          /* Scroll if row not visibile */
          row = gtk_clist_find_row_from_data(
            clist,
            EditorBranchGetData(ctree, branch)
          );
          if(row > -1)
          {
            if(gtk_clist_row_is_visible(clist, row) !=
                GTK_VISIBILITY_FULL
            )
                gtk_clist_moveto(
                  clist,
                  row, 0,           /* Row, column */
                  0.5, 0.0    /* Row, column */
                );
          }

          /* Fetch values for newly selected branch */
          if(branch != NULL)
          {
            gchar *buf;

            /* Procedure to fetch values from the given branch
             * then updates the editor's panel widgets and menus.
             */
            EditorDoFetchValues(editor, branch);

            /* Notify about selection */
            item = EditorBranchGetData(
                (GtkCTree *)clist, branch
            );
            if(item != NULL)
            {
                switch(item->type)
                {
                  case EditorItemTypeFile:
                  buf = g_strdup_printf(
                      "Selected manual page \"%s\"",
                      item->name
                  );
                  EditorSetStatusMessage(editor, buf);
                  g_free(buf);
                  break;

                  case EditorItemTypeHeader:
                  EditorSetStatusMessage(editor, "Selected header");
                  break;

                  case EditorItemTypeSection:
                  buf = g_strdup_printf(
                      "Selected section \"%s\"",
                      item->section_name
                  );
                  EditorSetStatusMessage(editor, buf);
                  g_free(buf);
                  break;

                  default:
                  EditorSetStatusMessage(editor, "Selected unsupported type");
                  break;
                }
            }
            else
            {
                EditorSetStatusMessage(editor, "");
            }
          }
          else
          {
            EditorSetStatusMessage(editor, "");
          }

          EditorSetReady(editor);
      }
}

/*
 *    Layout ctree unselect row callback.
 */
void EditorLayoutCTreeUnselectCB(
      GtkCTree *ctree, GtkCTreeNode *branch, gint column, gpointer data
)
{
      static gboolean reenterant = FALSE;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (ctree == NULL))
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      if(GTK_WIDGET(ctree) == editor->layout_ctree)
      {
          /* Apply values from the editor's widgets and place them
           * into the just unselected branch.
           */
          EditorDoApplyValues(editor, branch);

          if(branch == editor->selected_branch)
            editor->selected_branch = NULL;

          /* Clear values on editor's editing widgets, this is so
           * user does not type in something into those widgets
           * while no branch is selected (thus any changes would be
           * lost when a branch gets selected again)
           */
          EditorDoClearValues(editor);

          /* Clear undo/redo list on editor, this is so that they can't
           * get applied by the user on the newly selected branch's
           * data
           */
          EditorResetUndos(editor);
      }

      reenterant = FALSE;
}

/*
 *    Editor layout ctree branch expand (and collapse) callback, called
 *    whenever a branch is expanded or collapsed.
 */
void EditorLayoutCTreeExpandCB(
      GtkCTree *ctree, GtkCTreeNode *node, gpointer data
)
{
      static gboolean reenterant = FALSE;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (ctree == NULL))
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;
 
      if(GTK_WIDGET(ctree) == editor->layout_ctree)
      {
          /* Update layout clist column width */
          gtk_clist_set_column_width(
            (GtkCList *)ctree,
            0,                /* Column */
            gtk_clist_optimal_column_width((GtkCList *)ctree, 0)
          );

      }

      reenterant = FALSE;
}


/*
 *    Syntax highlight timeout callback.
 *
 *    For performing syntax highlighting a delayed time after text has
 *    changed or been deleted on the section_text widget.
 *
 *    If editor is currently processing then this operation will not be
 *    performed.
 *
 *    This operation will only be performed once, the value of the
 *    editor's synhl_timeout_cb_id will be set to (guint)(-1) after
 *    this function has been called and this function always returns
 *    FALSE to indicate this function is not to be called again
 *    unless we add a new timeout callback.
 */
gint EditorSyntaxHighlightTimeoutCB(gpointer data)
{
      static gboolean reenterant = FALSE;
      editor_struct *editor = EDITOR(data);
      if(editor == NULL)
          return(FALSE);

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

      /* Reset synhl_timeout_cb_id, since this function will not be
       * called again untill we add a new timeout callback.
       */
      editor->synhl_timeout_cb_id = (guint)(-1);

      if(!editor->initialized)
      {
          reenterant = FALSE;
          return(FALSE);
      }

      if(editor->processing)
      {
          reenterant = FALSE;
          return(FALSE);
      }

      /* Syntax highlighting */
      EditorSyntaxHighlight(
          editor, editor->section_text,
          editor->synhl_timeout_cb_cursor_pos,
          editor->synhl_timeout_cb_start_pos,
          editor->synhl_timeout_cb_end_pos
      );

      editor->synhl_timeout_cb_cursor_pos = -1;
      editor->synhl_timeout_cb_start_pos = -1;
      editor->synhl_timeout_cb_end_pos = -1;

      reenterant = FALSE;
      return(FALSE);
}


/*
 *    Editor GtkText widget key event callback, this is to check
 *    when the cursor has moved so that the position value on the
 *    status bar is properly set.
 *
 *    This function always returns FALSE.
 */
gint EditorTextKeyEventCB(
      GtkEditable *editable, GdkEventKey *key, gpointer data
)
{
      static gboolean reenterant = FALSE;
      int c, i, row, column;
      int cur_pos, length;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (editable == NULL))
          return(TRUE);

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

      /* Get cursor position */
      cur_pos = (gint)gtk_editable_get_position(editable);

      /* Get length of buffer */
      length = (gint)gtk_text_get_length(GTK_TEXT(editable));

      if(cur_pos >= length)
          cur_pos = length - 1;
      if(cur_pos < 0)
          cur_pos = 0;

      for(i = 0, row = 0, column = 0; i < cur_pos; i++)
      {
          c = GTK_TEXT_INDEX(GTK_TEXT(editable), i);
          if((gchar)c == '\n')
          {
            column = 0;
            row++;
          }
          else
          {
            column++;
          }
      }

      EditorSetStatusPosition(editor, row, column);

      reenterant = FALSE;
      return(TRUE);
}


/*
 *    Text change callback.
 */
void EditorTextChangeCB(GtkEditable *editable, gpointer data)
{
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (editable == NULL))
          return;


      return;
}

/*
 *    Editor text widget insert text callback.
 */
void EditorTextInsertCB(
      GtkEditable *editable, const gchar *text, gint length,
      gint *position, gpointer data
)
{
      static gboolean reenterant = FALSE;
      int i;
      gint start_pos, end_pos, text_len;
      gpointer u;
      editor_undo_remove_chars_struct *u_remove;
      GtkWidget *w;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (editable == NULL) || (position == NULL))
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      /* Do not process this signal if editor is processing an undo or
       * redo since this signal may be called during the undo process.
       */
      if(editor_processing_undo == editor)
      {
          reenterant = FALSE;
          return;
      }

      w = GTK_WIDGET(editable);

      /* Get positions */
      start_pos = (*position);
      end_pos = start_pos + length;

      /* Get text length */
      text_len = end_pos - start_pos;
      if(text_len < 0)
          text_len *= -1;  

      /* Sanitize positions */
      if(end_pos < start_pos)
      {
          gint tmp_pos = end_pos;
          end_pos = start_pos;
          start_pos = tmp_pos;
      }

      /* No text length? */
      if(text_len == 0)
      {
          reenterant = FALSE;
          return;
      }

      /* Sanitize total */
      if(editor->total_undos < 0)
          editor->total_undos = 0;

/* Macro to allocate a new undo structure of type EDITOR_UNDO_REMOVE_CHARS
 * on the editor->undo list. Uses variables i, u, editor, reenterant, w,
 * start_pos, and u_remove. Will return this function if there was an
 * error and u_remove will be set properly to the new structure.
 */
#define DO_NEW_UNDO_REMOVE_STRUCT   \
{ \
 i = editor->total_undos; \
 editor->total_undos = i + 1; \
 editor->undo = (gpointer *)g_realloc( \
  editor->undo, \
  editor->total_undos * sizeof(gpointer) \
 ); \
 if(editor->undo == NULL) \
 { \
  editor->total_undos = 0; \
  reenterant = FALSE; \
  return; \
 } \
 /* Shift */ \
 for(i = editor->total_undos - 1; i > 0; i--) \
  editor->undo[i] = editor->undo[i - 1]; \
 /* Add new struct to beginning of list */ \
 u = EditorUndoNew( \
  editor, EDITOR_UNDO_REMOVE_CHARS, \
  "Insert Characters" \
 ); \
 editor->undo[0] = u;   /* Record new struct to undo list */ \
 \
 /* Set initial values for new structure if possible */ \
 u_remove = (editor_undo_remove_chars_struct *)u; \
 if(u_remove != NULL) \
 { \
  u_remove->w = w; \
  u_remove->position = start_pos; \
 } \
}

      /* Get first undo structure, allocate as needed if it is NULL
       * or is not of type EDITOR_UNDO_REMOVE_CHARS.
       */
      if(editor->total_undos > 0)
          u = editor->undo[0];
      else
          u = NULL;
      u_remove = (editor_undo_remove_chars_struct *)u;
      if((u == NULL) ? 1 : ((*(int *)u) != EDITOR_UNDO_REMOVE_CHARS))
      {
          DO_NEW_UNDO_REMOVE_STRUCT
          if(u_remove == NULL)
          {
            reenterant = FALSE;
            return;
          } 

          EditorItemSetToplevelHasChanges(
            editor, editor->selected_branch, TRUE
          );
          EditorUpdateMenus(editor);
      }
      u = NULL;       /* Don't use u past this point */

      /* Sanitize length on undo structure */
      if(u_remove->length < 0)
          u_remove->length = 0;

      /* Are the new positions contiguous? */
      if((u_remove->position + u_remove->length) == start_pos)
      {
          u_remove->length += text_len;
      }
      else
      {
          /* New segment being inserted, need to allocate new undo
           * structure.
           */
          DO_NEW_UNDO_REMOVE_STRUCT
          if(u_remove == NULL)
          {
            reenterant = FALSE;
            return;
          }
          u_remove->position = start_pos;
          u_remove->length = text_len;

          EditorItemSetToplevelHasChanges(
            editor, editor->selected_branch, TRUE
          );
          EditorUpdateMenus(editor);
      }


      /* Remove excess undo structures */
      if(editor->total_undos > editor->max_undos)
      {
          int max = MAX(editor->max_undos, 0);

          /* Delete all undo structures greater than the max */
          for(i = editor->total_undos - 1; i >= max; i--)
            EditorUndoDelete(editor, editor->undo[i]);

          /* Update total and reallocate pointer array */
          editor->total_undos = max;
          if(editor->total_undos > 0)
          {
            editor->undo = (gpointer *)g_realloc(
                editor->undo,
                editor->total_undos * sizeof(gpointer)
            );
            if(editor->undo == NULL)
                editor->total_undos = 0;
          }   
          else
          {
            g_free(editor->undo);
            editor->undo = NULL;
            editor->total_undos = 0;
          }
      }


      /* Mark currently selected branch item data as having changes */
      EditorItemSetToplevelHasChanges(
          editor, editor->selected_branch, TRUE
      );

#undef DO_NEW_UNDO_REMOVE_STRUCT

      /* If this is the editor's section_text widget then add a timeout
       * callback for syntax highlighting.
       */
      if(GTK_WIDGET(editable) == editor->section_text)
      {
          /* Cancel previous syntax highlighting as needed */
          if(editor->synhl_timeout_cb_id != (guint)(-1))
          {
            gtk_timeout_remove(editor->synhl_timeout_cb_id);
            editor->synhl_timeout_cb_id = (guint)(-1);
          }

          /* Set cursor position as needed and if less than */
          if(editor->synhl_timeout_cb_cursor_pos < 0)
            editor->synhl_timeout_cb_cursor_pos = start_pos;
          else
            editor->synhl_timeout_cb_cursor_pos = MIN(
                editor->synhl_timeout_cb_cursor_pos,
                start_pos
            );

          /* Set start position as needed and if less than */
          if(editor->synhl_timeout_cb_start_pos < 0)
            editor->synhl_timeout_cb_start_pos = start_pos;
          else
            editor->synhl_timeout_cb_start_pos = MIN(
                editor->synhl_timeout_cb_start_pos,
                start_pos
            );

          /* Set end position as needed and if greater than */
          if(editor->synhl_timeout_cb_end_pos < 0)
            editor->synhl_timeout_cb_end_pos = end_pos;
          else
            editor->synhl_timeout_cb_end_pos = MAX(
                editor->synhl_timeout_cb_end_pos,
                end_pos
            );

          /* Add new timeout callback for syntax highlighting */
          editor->synhl_timeout_cb_id = gtk_timeout_add(
            500,
            EditorSyntaxHighlightTimeoutCB,
            editor
          );
      }

      reenterant = FALSE;
}

/*
 *    Editor text widget delete text callback.
 */
void EditorTextDeleteCB(
      GtkEditable *editable, gint start_pos, gint end_pos, gpointer data
)
{
      static gboolean reenterant = FALSE;
      int i;
      gint text_len;
      gchar *text_buf;
      GtkWidget *w;
      gpointer u;
      editor_undo_insert_chars_struct *u_insert;
      editor_struct *editor = EDITOR(data);
      if((editor == NULL) || (editable == NULL))
          return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      /* Do not process this signal if editor is processing an undo or
       * redo since this signal may be called during the undo process.
       */
      if(editor_processing_undo == editor)
      {
          reenterant = FALSE;
          return;
      }

      w = GTK_WIDGET(editable);


      /* Get text length */
      text_len = end_pos - start_pos;
      if(text_len < 0)
          text_len *= -1;

      /* Sanitize positions */
      if(end_pos < start_pos)
      {
          gint tmp_pos = end_pos;
          end_pos = start_pos;
          start_pos = tmp_pos;
      }

      /* No text length? */
      if(text_len == 0)
      {
          reenterant = FALSE;
          return;
      }


      /* Sanitize total */
      if(editor->total_undos < 0)
          editor->total_undos = 0;


/* Macro to allocate a new undo structure of type EDITOR_UNDO_INSERT_CHARS
 * on the editor->undo list. Uses variables i, u, editor, reenterant, w,
 * start_pos, and u_insert. Will return this function if there was an
 * error and u_insert will be set properly to the new structure.
 */
#define DO_NEW_UNDO_INSERT_STRUCT   \
{ \
 i = editor->total_undos; \
 editor->total_undos = i + 1; \
 editor->undo = (gpointer *)g_realloc( \
  editor->undo, \
  editor->total_undos * sizeof(gpointer) \
 ); \
 if(editor->undo == NULL) \
 { \
  editor->total_undos = 0; \
  reenterant = FALSE; \
  return; \
 } \
 /* Shift */ \
 for(i = editor->total_undos - 1; i > 0; i--) \
  editor->undo[i] = editor->undo[i - 1]; \
 /* Add new struct to beginning of list */ \
 u = EditorUndoNew( \
  editor, EDITOR_UNDO_INSERT_CHARS, \
  "Remove Characters" \
 ); \
 editor->undo[0] = u;   /* Record new struct to undo list */ \
 \
 /* Set initial values for new structure if possible */ \
 u_insert = (editor_undo_insert_chars_struct *)u; \
 if(u_insert != NULL) \
 { \
  u_insert->w = w; \
  u_insert->position = start_pos; \
 } \
}

      /* Get first undo structure, allocate as needed if it is NULL
       * or is not of type EDITOR_UNDO_INSERT_CHARS.
       */
      if(editor->total_undos > 0)
          u = editor->undo[0];
      else
          u = NULL;
      u_insert = (editor_undo_insert_chars_struct *)u;
      if((u == NULL) ? 1 : ((*(int *)u) != EDITOR_UNDO_INSERT_CHARS))
      {
          DO_NEW_UNDO_INSERT_STRUCT
          if(u_insert == NULL)
          {
            reenterant = FALSE;
            return;
          }

          EditorItemSetToplevelHasChanges(
            editor, editor->selected_branch, TRUE
          );
          EditorUpdateMenus(editor);
      }
      u = NULL;   /* Don't use u past this point */

      /* Sanitize length on undo structure */
      if(u_insert->length < 0)
          u_insert->length = 0;

      /* Are the new positions contiguous? */
      if(u_insert->position == start_pos)
      {
          /* Contiguous forward delete */
          int old_len = u_insert->length;


          /* Get characters that are about to be deleted */
          text_buf = gtk_editable_get_chars(
            GTK_EDITABLE(w), start_pos, end_pos
          );

          /* Increase allocation for text on undo structure */
          u_insert->length += text_len;
          u_insert->characters = (gchar *)g_realloc(
            u_insert->characters,
            (u_insert->length + 1) * sizeof(gchar)
          );
          if(u_insert->characters == NULL)
          {
            u_insert->length = 0;
          }
          else
          {
            if(text_buf != NULL)
                memcpy(
                  &u_insert->characters[old_len],
                  text_buf,
                  text_len * sizeof(gchar)
                );
            u_insert->characters[u_insert->length] = '\0';
          } 

          g_free(text_buf);
          text_buf = NULL;
      }
      else if(u_insert->position == end_pos)
      {
          /* Contiguous backward delete */
          int old_len = u_insert->length;


          /* Get characters that are about to be deleted */
          text_buf = gtk_editable_get_chars(
            GTK_EDITABLE(w), start_pos, end_pos
          );

          /* Increase allocation for text on undo structure */
          u_insert->length += text_len;
          u_insert->characters = (gchar *)g_realloc(
            u_insert->characters,
            (u_insert->length + 1) * sizeof(gchar)
          );
          if(u_insert->characters == NULL)
          {
            u_insert->length = 0;
          }
          else
          {
            if(old_len > 0)
                memmove(
                  &u_insert->characters[text_len],
                  u_insert->characters,
                  old_len * sizeof(gchar)
                );

            if(text_buf != NULL)
                memcpy(
                  u_insert->characters,
                  text_buf,
                  text_len * sizeof(gchar)
                );
            u_insert->characters[u_insert->length] = '\0';
          }

          g_free(text_buf);
          text_buf = NULL;

          /* Need to update position */
          u_insert->position = start_pos;
      }
      else
      {
          /* New segment being deleted, need to allocate new undo
           * structure.
           */
          int old_len;

          DO_NEW_UNDO_INSERT_STRUCT
          if(u_insert == NULL)
          {
            reenterant = FALSE;
            return;
          }

          old_len = u_insert->length;

          /* Get characters that are about to be deleted */
          text_buf = gtk_editable_get_chars(
            GTK_EDITABLE(w), start_pos, end_pos
          );

          /* Increase allocation for text on undo structure */
          u_insert->length += text_len;
          u_insert->characters = (gchar *)g_realloc(
            u_insert->characters,
            (u_insert->length + 1) * sizeof(gchar)
          );
          if(u_insert->characters == NULL)
          {
            u_insert->length = 0;
          }
          else
          {
            if(text_buf != NULL)
                memcpy(
                  &u_insert->characters[old_len],
                  text_buf,
                  text_len * sizeof(gchar)
                );
            u_insert->characters[u_insert->length] = '\0';
          }

          g_free(text_buf);
          text_buf = NULL;

          EditorItemSetToplevelHasChanges(
            editor, editor->selected_branch, TRUE
          );
          EditorUpdateMenus(editor);
      }


      /* Remove excess undo structures */
      if(editor->total_undos > editor->max_undos)
      {
          int max = MAX(editor->max_undos, 0);

          /* Delete all undo structures greater than the max */
          for(i = editor->total_undos - 1; i >= max; i--)
            EditorUndoDelete(editor, editor->undo[i]);

          /* Update total and reallocate pointer array */
          editor->total_undos = max;
          if(editor->total_undos > 0)
          {
            editor->undo = (gpointer *)g_realloc(
                editor->undo,
                editor->total_undos * sizeof(gpointer)
            );
            if(editor->undo == NULL)
                editor->total_undos = 0;
          }
          else
          {
            g_free(editor->undo);
            editor->undo = NULL;
            editor->total_undos = 0;
          }
      }


      /* Mark currently selected branch item data as having changes */
      EditorItemSetToplevelHasChanges(
          editor, editor->selected_branch, TRUE
      );

#undef DO_NEW_UNDO_INSERT_STRUCT

      /* If this is the editor's section_text widget then add a timeout
       * callback for syntax highlighting.
       */
      if(GTK_WIDGET(editable) == editor->section_text)
      {
          /* Cancel previous syntax highlighting as needed */
          if(editor->synhl_timeout_cb_id != (guint)(-1))
          {
            gtk_timeout_remove(editor->synhl_timeout_cb_id);
            editor->synhl_timeout_cb_id = (guint)(-1);
          }

          /* Set cursor position as needed and if less than */
          if(editor->synhl_timeout_cb_cursor_pos < 0)
            editor->synhl_timeout_cb_cursor_pos = start_pos;
          else
            editor->synhl_timeout_cb_cursor_pos = MIN(
                editor->synhl_timeout_cb_cursor_pos,
                start_pos
            );

          /* Set start position as needed and if less than */
          if(editor->synhl_timeout_cb_start_pos < 0)
            editor->synhl_timeout_cb_start_pos = start_pos;
          else
            editor->synhl_timeout_cb_start_pos = MIN(
                editor->synhl_timeout_cb_start_pos,
                start_pos
            );

          /* Set end position as needed and if greater than */
          if(editor->synhl_timeout_cb_end_pos < 0)
            editor->synhl_timeout_cb_end_pos = end_pos;
          else
            editor->synhl_timeout_cb_end_pos = MAX(
                editor->synhl_timeout_cb_end_pos,
                end_pos
            );

          /* Add new timeout callback for syntax highlighting */
          editor->synhl_timeout_cb_id = gtk_timeout_add(
            500,
            EditorSyntaxHighlightTimeoutCB,
            editor
          );
      }

      reenterant = FALSE;
}

Generated by  Doxygen 1.6.0   Back to index