/* 
 * gnc-main-window.c -- GtkWindow which represents the
 *	GnuCash main window.
 *
 * Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, contact:
 *
 * Free Software Foundation           Voice:  +1-617-542-5942
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
 * Boston, MA  02110-1301,  USA       gnu@gnu.org
 */

/** @addtogroup Windows
    @{ */
/** @addtogroup GncMainWindow Main Window functions.
    @{ */
/** @file gnc-main-window.c
    @brief Functions for adding content to a window.
    @author Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
    @author Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
*/

#include "config.h"

#include <gnome.h>
#include <glib/gi18n.h>
#include <libguile.h>
#include "guile-mappings.h"

#include "gnc-plugin.h"
#include "gnc-plugin-manager.h"
#include "gnc-main-window.h"

#include "dialog-preferences.h"
#include "dialog-reset-warnings.h"
#include "dialog-transfer.h"
#include "dialog-utils.h"
#include "file-utils.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-file.h"
#include "gnc-gkeyfile-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-gui-query.h"
#include "gnc-hooks.h"
#include "gnc-session.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-version.h"
#include "gnc-window.h"
#include "gnc-main.h"
#include "gnc-gconf-utils.h"
// +JSLED
#include "gnc-html.h"

/** Names of signals generated by the main window. */
enum {
  PAGE_ADDED,
  PAGE_CHANGED,
  LAST_SIGNAL
};

/** This label is used to provide a mapping from a visible page widget
 *  back to the corresponding GncPluginPage object. */
#define PLUGIN_PAGE_LABEL "plugin-page"

#define PLUGIN_PAGE_CLOSE_BUTTON "close-button"

#define KEY_SHOW_CLOSE_BUTTON	"tab_close_buttons"
#define KEY_TAB_POSITION	"tab_position"

#define GNC_MAIN_WINDOW_NAME "GncMainWindow"

/* Static Globals *******************************************************/

/** The debugging module that this .o belongs to.  */
static QofLogModule log_module = GNC_MOD_GUI;
/** A pointer to the parent class of an embedded window. */
static GObjectClass *parent_class = NULL;
/** An identifier that indicates a "main" window. */
static GQuark window_type = 0;
/** A list of all extant main windows. This is for convenience as the
 *  same information can be obtained from the object tracking code. */
static GList *active_windows = NULL;

/* Declarations *********************************************************/
static void gnc_main_window_class_init (GncMainWindowClass *klass);
static void gnc_main_window_init (GncMainWindow *window, GncMainWindowClass *klass);
static void gnc_main_window_finalize (GObject *object);
static void gnc_main_window_destroy (GtkObject *object);

static void gnc_main_window_setup_window (GncMainWindow *window);
static void gnc_window_main_window_init (GncWindowIface *iface);

/* Callbacks */
static void gnc_main_window_add_widget (GtkUIManager *merge, GtkWidget *widget, GncMainWindow *window);
static void gnc_main_window_switch_page (GtkNotebook *notebook, GtkNotebookPage *notebook_page, gint pos, GncMainWindow *window);
#ifdef HAVE_GTK_2_10
static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window);
#endif
static void gnc_main_window_plugin_added (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
static void gnc_main_window_plugin_removed (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);

/* Command callbacks */
static void gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_window_raise (GtkAction *action, GtkRadioAction *current, GncMainWindow *window);
static void gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window);
static void gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window);

static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
static gboolean gnc_main_window_popup_menu_cb (GtkWidget *widget, GncPluginPage *page);

static GtkAction *gnc_main_window_find_action (GncMainWindow *window, const gchar *name);

/** The instance private data structure for an embedded window
 *  object. */
typedef struct GncMainWindowPrivate
{
	/** The dock (vbox) at the top of the window containing the
	 *  menubar and toolbar.  These items are generated bu the UI
	 *  manager and stored here when the UI manager provides them
	 *  to the main window. */
	GtkWidget *menu_dock;
	/* The toolbar created by the UI manager.  This pointer
	 * provides easy access for showing/hiding the toolbar. */
	GtkWidget *toolbar;
	/** The notebook containing all the pages in this window. */
	GtkWidget *notebook;
	/** A pointer to the status bar at the bottom edge of the
	 *  window.  This pointer provides easy access for
	 *  updating/showing/hiding the status bar. */
	GtkWidget *statusbar;
	/** A pointer to the progress bar at the bottom right of the
	 *  window that is contained in the status bar.  This pointer
	 *  provides easy access for updating the progressbar. */
	GtkWidget *progressbar;

	/** The group of all actions provided by the main window
	 *  itself.  This does not include any action provided by menu
	 *  or content plugins. */
	GtkActionGroup *action_group;

	/** A list of all pages that are installed in this window. */
	GList *installed_pages;
	/** A list of pages in order of use (most recent -> least recent) */
	GList *usage_order;
	/** The currently selected page. */
	GncPluginPage *current_page;
	/** The identifier for this window's engine event handler. */
	gint event_handler_id;

	/** A hash table of all action groups that have been installed
	 *  into this window. The keys are the name of an action
	 *  group, the values are structures of type
	 *  MergedActionEntry. */
	GHashTable *merged_actions_table;
} GncMainWindowPrivate;

#define GNC_MAIN_WINDOW_GET_PRIVATE(o)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_MAIN_WINDOW, GncMainWindowPrivate))

/** This data structure maintains information about one action groups
 *  that has been installed in this window. */
typedef struct {
	/** The merge identifier for this action group.  This number
	 *  is provided by the UI manager. */
	guint merge_id;
	/** The action group itself.  This contains all actions added
	 *  by a single menu or content plugin. */
	GtkActionGroup *action_group;
} MergedActionEntry;

/** A holding place for all the signals generated by the main window
 *  code. */
static guint main_window_signals[LAST_SIGNAL] = { 0 };


/** An array of all of the actions provided by the main window code.
 *  This includes some placeholder actions for the menus that are
 *  visible in the menu bar but have no action associated with
 *  them. */
static GtkActionEntry gnc_menu_actions [] =
{
	/* Toplevel */

	{ "FileAction", NULL, N_("_File"), NULL, NULL, NULL, },
	{ "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
	{ "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
	{ "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
	{ "TransactionAction", NULL, N_("Tra_nsaction"), NULL, NULL, NULL },
	{ "ReportsAction", NULL, N_("_Reports"), NULL, NULL, NULL },
	{ "ToolsAction", NULL, N_("_Tools"), NULL, NULL, NULL },
	{ "ExtensionsAction", NULL, N_("E_xtensions"), NULL, NULL, NULL },
	{ "WindowsAction", NULL, N_("_Windows"), NULL, NULL, NULL },
	{ "HelpAction", NULL, N_("_Help"), NULL, NULL, NULL },

	/* File menu */

	{ "FileNewMenuAction", GTK_STOCK_NEW, N_("_New"), "", NULL, NULL },
	{ "FileOpenMenuAction", GTK_STOCK_OPEN, N_("_Open"), "", NULL, NULL },
	{ "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL },
	{ "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL },
	{ "FilePrintAction", GTK_STOCK_PRINT, N_("_Print..."), "<control>p", 
	  N_("Print the currently active page"), NULL },
	{ "FilePropertiesAction", GTK_STOCK_PROPERTIES, N_("Proper_ties"), "<Alt>Return",
	  N_("Edit the properties of the current file"),
	  G_CALLBACK (gnc_main_window_cmd_file_properties) },
	{ "FileCloseAction", GTK_STOCK_CLOSE, N_("_Close"), NULL,
	  N_("Close the currently active page"),
	  G_CALLBACK (gnc_main_window_cmd_file_close) },
	{ "FileQuitAction", GTK_STOCK_QUIT, N_("_Quit"), NULL,
	  N_("Quit this application"),
	  G_CALLBACK (gnc_main_window_cmd_file_quit) },

	/* Edit menu */

	{ "EditCutAction", GTK_STOCK_CUT, N_("Cu_t"), NULL, 
	  N_("Cut the current selection and copy it to clipboard"),
	  G_CALLBACK (gnc_main_window_cmd_edit_cut) },
	{ "EditCopyAction", GTK_STOCK_COPY, N_("_Copy"), NULL, 
	  N_("Copy the current selection to clipboard"),
	  G_CALLBACK (gnc_main_window_cmd_edit_copy) },
	{ "EditPasteAction", GTK_STOCK_PASTE, N_("_Paste"), NULL,
	  N_("Paste the clipboard content at the cursor position"),
	  G_CALLBACK (gnc_main_window_cmd_edit_paste) },
	{ "EditPreferencesAction", GTK_STOCK_PREFERENCES, N_("Pr_eferences"), NULL,
	  N_("Edit the global preferences of GnuCash"),
	  G_CALLBACK (gnc_main_window_cmd_edit_preferences) },

	/* View menu */

	{ "ViewSortByAction", NULL, N_("_Sort By..."), NULL, 
	  N_("Select sorting criteria for this page view"), NULL },
	{ "ViewFilterByAction", NULL, N_("_Filter By..."), NULL, 
	  N_("Select the account types that should be displayed."), NULL },
	{ "ViewRefreshAction", GTK_STOCK_REFRESH, N_("_Refresh"), "<control>r",
	  N_("Refresh this window"),
	  G_CALLBACK (gnc_main_window_cmd_view_refresh) },

	/* Actions menu */

	{ "ScrubMenuAction", NULL, N_("_Check & Repair"), NULL, NULL, NULL },
	{ "ActionsForgetWarningsAction", NULL, N_("Reset _Warnings..."), NULL,
	  N_("Reset the state of all warning message so they will be shown again."),
	  G_CALLBACK (gnc_main_window_cmd_actions_reset_warnings) },
	{ "ActionsRenamePageAction", NULL, N_("Re_name Page"), NULL,
	  N_("Rename this page."),
	  G_CALLBACK (gnc_main_window_cmd_actions_rename_page) },

	/* Windows menu */

	{ "WindowNewAction", NULL, N_("_New Window"), NULL,
	  N_("Open a new top-level GnuCash window."),
	  G_CALLBACK (gnc_main_window_cmd_window_new) },
	{ "WindowMovePageAction", NULL, N_("New Window with _Page"), NULL,
	  N_("Move the current page to a new top-level GnuCash window."),
	  G_CALLBACK (gnc_main_window_cmd_window_move_page) },

	/* Help menu */

	{ "HelpTutorialAction", GNOME_STOCK_BOOK_BLUE, N_("Tutorial and Concepts _Guide"), NULL,
	  N_("Open the GnuCash Tutorial"),
	  G_CALLBACK (gnc_main_window_cmd_help_tutorial) },
	{ "HelpContentsAction", GTK_STOCK_HELP, N_("_Contents"), "F1",
	  N_("Open the GnuCash Help"),
	  G_CALLBACK (gnc_main_window_cmd_help_contents) },
	{ "HelpAboutAction", GNOME_STOCK_ABOUT, N_("_About"), NULL,
	  N_("About GnuCash"),
	  G_CALLBACK (gnc_main_window_cmd_help_about) },
};
/** The number of actions provided by the main window. */
static guint gnc_menu_n_actions = G_N_ELEMENTS (gnc_menu_actions);

/** An array of all of the toggle action provided by the main window
 *  code. */
static GtkToggleActionEntry toggle_actions [] =
{
	{ "ViewToolbarAction", NULL, N_("_Toolbar"), NULL,
	  N_("Show/hide the toolbar on this window"),
	  G_CALLBACK (gnc_main_window_cmd_view_toolbar), TRUE },
	{ "ViewSummaryAction", NULL, N_("Su_mmary Bar"), NULL,
	  N_("Show/hide the summary bar on this window"),
	  G_CALLBACK (gnc_main_window_cmd_view_summary), TRUE },
	{ "ViewStatusbarAction", NULL, N_("Stat_us Bar"), NULL,
	  N_("Show/hide the status bar on this window"),
	  G_CALLBACK (gnc_main_window_cmd_view_statusbar), TRUE },
};
/** The number of toggle actions provided by the main window. */
static guint n_toggle_actions = G_N_ELEMENTS (toggle_actions);

/** An array of all of the radio action provided by the main window
 *  code. */
static GtkRadioActionEntry radio_entries [] =
{
	{ "Window0Action", NULL, N_("Window _1"), NULL, NULL, 0 },
	{ "Window1Action", NULL, N_("Window _2"), NULL, NULL, 1 },
	{ "Window2Action", NULL, N_("Window _3"), NULL, NULL, 2 },
	{ "Window3Action", NULL, N_("Window _4"), NULL, NULL, 3 },
	{ "Window4Action", NULL, N_("Window _5"), NULL, NULL, 4 },
	{ "Window5Action", NULL, N_("Window _6"), NULL, NULL, 5 },
	{ "Window6Action", NULL, N_("Window _7"), NULL, NULL, 6 },
	{ "Window7Action", NULL, N_("Window _8"), NULL, NULL, 7 },
	{ "Window8Action", NULL, N_("Window _9"), NULL, NULL, 8 },
	{ "Window9Action", NULL, N_("Window _0"), NULL, NULL, 9 },
};
/** The number of radio actions provided by the main window. */
static guint n_radio_entries = G_N_ELEMENTS (radio_entries);


/** These are the "important" actions provided by the main window.
 *  Their labels will appear when the toolbar is set to "Icons and
 *  important text" (e.g. GTK_TOOLBAR_BOTH_HORIZ) mode. */
static const gchar *gnc_menu_important_actions[] = {
  "FileCloseAction",
  NULL,
};


/** The following are in the main window so they will always be
 *  present in the menu structure, but they are never sensitive.
 *  These actions should be overridden in child windows where they
 *  have meaning. */
static const gchar *always_insensitive_actions[] = {
	"FilePrintAction",
	NULL
};


/** The following items in the main window should be made insensitive
 *  at startup time.  The sensitivity will be changed by some later
 *  event. */
static const gchar *initially_insensitive_actions[] = {
	"FileCloseAction",
	NULL
};


/** The following are in the main window so they will always be
 *  present in the menu structure, but they are always hidden.
 *  These actions should be overridden in child windows where they
 *  have meaning. */
static const gchar *always_hidden_actions[] = {
	"ViewSortByAction",
	"ViewFilterByAction",
	NULL
};


/** If a page is flagged as immutable, then the following actions
 *  cannot be performed on that page. */
static const gchar *immutable_page_actions[] = {
	"FileCloseAction",
	NULL
};


/** The following actions can only be performed if there are multiple
 *  pages in a window. */
static const gchar *multiple_page_actions[] = {
	"WindowMovePageAction",
	NULL
};


/************************************************************
 *                                                          *
 ************************************************************/
#define WINDOW_COUNT		"Window Count"
#define WINDOW_STRING		"Window %d"
#define WINDOW_GEOMETRY		"Window Geometry"
#define WINDOW_POSITION		"Window Position"
#define WINDOW_MAXIMIZED	"Window Maximized"
#define TOOLBAR_VISIBLE		"Toolbar Visible"
#define STATUSBAR_VISIBLE	"Statusbar Visible"
#define SUMMARYBAR_VISIBLE	"Summarybar Visible"
#define WINDOW_FIRSTPAGE	"First Page"
#define WINDOW_PAGECOUNT	"Page Count"
#define WINDOW_PAGEORDER	"Page Order"
#define PAGE_TYPE		"Page Type"
#define PAGE_NAME		"Page Name"
#define PAGE_STRING		"Page %d"

typedef struct {
  GKeyFile *key_file;
  const gchar *group_name;
  gint window_num;
  gint page_num;
  gint page_offset;
} GncMainWindowSaveData;


/** Restore a single page to a window.  This function calls a page
 *  specific function to create the actual page.  It then handles all
 *  the common tasks such as insuring the page is installed into a
 *  window, updating the page name, and anything else that might be
 *  common to all pages.
 *
 *  @param window The GncMainWindow where the new page will be
 *  installed.
 *
 *  @param data A data structure containing state about the
 *  window/page restoration process. */
static void
gnc_main_window_restore_page (GncMainWindow *window, 
                              GncMainWindowSaveData *data)
{
  GncMainWindowPrivate *priv;
  GncPluginPage *page;
  gchar *page_group, *page_type = NULL, *name = NULL;
  const gchar *class_type;
  GError *error = NULL;

  ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
	window, data, data->key_file, data->window_num, data->page_offset, 
        data->page_num);

  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  page_group = g_strdup_printf(PAGE_STRING, 
                               data->page_offset + data->page_num);
  page_type = g_key_file_get_string(data->key_file, page_group,
				    PAGE_TYPE, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      page_group, PAGE_TYPE, error->message);
    goto cleanup;
  }

  /* See if the page already exists. */
  page = g_list_nth_data(priv->installed_pages, data->page_num);
  if (page) {
    class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
    if (strcmp(page_type, class_type) != 0) {
      g_warning("error: page types don't match: state %s, existing page %s",
		page_type, class_type);
      goto cleanup;
    }
  } else {
    /* create and install the page */
    page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
					 data->key_file, page_group);
    if (page) {
      /* Does the page still need to be installed into the window? */
      if (page->window == NULL) {
      	gnc_main_window_open_page(window, page);
      }

      /* Restore the page name */
      name = g_key_file_get_string(data->key_file, page_group,
				       PAGE_NAME, &error);
      if (error) {
	g_warning("error reading group %s key %s: %s",
		  page_group, PAGE_NAME, error->message);
	/* Fall through and still show the page. */
      } else {
	DEBUG("updating page name for %p to %s.", page, name);
	main_window_update_page_name(page, name);
	g_free(name);
      }
    }
  }

  LEAVE("ok");
 cleanup:
  if (error)
    g_error_free(error);
  if (page_type)
    g_free(page_type);
  g_free(page_group);
}


/** Restore all the pages in a given window.  This function restores
 *  all the window specific attributes, then calls a helper function
 *  to restore all the pages that are contained in the window.
 *
 *  @param window The GncMainWindow whose pages should be restored.
 *
 *  @param data A data structure containing state about the
 *  window/page restoration process. */
static void
gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
{
  GncMainWindowPrivate *priv;
  GtkAction *action;
  gint *pos, *geom, *order;
  gsize length;
  gboolean max, visible, desired_visibility;
  gchar *window_group;
  gint page_start, page_count, i;
  GError *error = NULL;

  /* Setup */
  ENTER("window %p, data %p (key file %p, window %d)",
	window, data, data->key_file, data->window_num);
  window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);

  /* Get this window's notebook info */
  page_count = g_key_file_get_integer(data->key_file,
				      window_group, WINDOW_PAGECOUNT, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, WINDOW_PAGECOUNT, error->message);
    goto cleanup;
  }
  if (page_count == 0) {
    /* Shound never happen, but has during alpha testing. Having this
     * check doesn't hurt anything. */
    goto cleanup;
  }
  page_start = g_key_file_get_integer(data->key_file,
				      window_group, WINDOW_FIRSTPAGE, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, WINDOW_FIRSTPAGE, error->message);
    goto cleanup;
  }

  /* Build a window if we don't already have one */
  if (window == NULL) {
    DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
    DEBUG("active_windows %p.", active_windows);
    if (active_windows)
      DEBUG("first window %p.", active_windows->data);
    window = gnc_main_window_new();
    gtk_widget_show(GTK_WIDGET(window));
  }

  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);

  /* Get the window coordinates, etc. */
  geom = g_key_file_get_integer_list(data->key_file, window_group,
				     WINDOW_GEOMETRY, &length, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, WINDOW_GEOMETRY, error->message);
    g_error_free(error);
    error = NULL;
  } else if (length != 2) {
    g_warning("invalid number of values for group %s key %s",
	      window_group, WINDOW_GEOMETRY);
  } else {
    gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
    DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
  }
  /* keep the geometry for a test whether the windows position
     is offscreen */

  pos = g_key_file_get_integer_list(data->key_file, window_group,
				    WINDOW_POSITION, &length, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, WINDOW_POSITION, error->message);
    g_error_free(error);
    error = NULL;
  } else if (length != 2) {
    g_warning("invalid number of values for group %s key %s",
	      window_group, WINDOW_POSITION);
  } else if ((pos[0] + (geom ? geom[0] : 0) < 0) ||
	     (pos[0] > gdk_screen_width()) ||
	     (pos[1] + (geom ? geom[1] : 0) < 0) ||
	     (pos[1] > gdk_screen_height())) {
//    g_debug("position %dx%d, size%dx%d is offscreen; will not move",
//	    pos[0], pos[1], geom[0], geom[1]);
  } else {
    gtk_window_move(GTK_WINDOW(window), pos[0], pos[1]);
    DEBUG("window (%p) position %dx%d", window, pos[0], pos[1]);
  }
  if (geom) {
    g_free(geom);
  }
  if (pos) {
    g_free(pos);
  }

  max = g_key_file_get_boolean(data->key_file, window_group,
			       WINDOW_MAXIMIZED, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, WINDOW_MAXIMIZED, error->message);
    g_error_free(error);
    error = NULL;
  } else if (max) {
    gtk_window_maximize(GTK_WINDOW(window));
  }

  /* Common view menu items */
  action = gnc_main_window_find_action(window, "ViewToolbarAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
					      TOOLBAR_VISIBLE, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, TOOLBAR_VISIBLE, error->message);
    g_error_free(error);
    error = NULL;
  } else if (visible != desired_visibility) {
    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action),desired_visibility);
  }

  action = gnc_main_window_find_action(window, "ViewSummaryAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
					      SUMMARYBAR_VISIBLE, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, TOOLBAR_VISIBLE, error->message);
    g_error_free(error);
    error = NULL;
  } else if (visible != desired_visibility) {
    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action),desired_visibility);
  }

  action = gnc_main_window_find_action(window, "ViewStatusbarAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
					      STATUSBAR_VISIBLE, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      window_group, TOOLBAR_VISIBLE, error->message);
    g_error_free(error);
    error = NULL;
  } else if (visible != desired_visibility) {
    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action),desired_visibility);
  }

  /* Now populate the window with pages. */
  for (i = 0; i < page_count; i++) {
    data->page_offset = page_start;
    data->page_num = i;
    gnc_main_window_restore_page(window, data);

    /* give the page a chance to display */
    while (gtk_events_pending ())
      gtk_main_iteration ();
  }

  /* Restore page ordering within the notebook. Use +1 notation so the
   * numbers in the page order match the page sections, at least for
   * the one window case. */
  order = g_key_file_get_integer_list(data->key_file, window_group,
 				      WINDOW_PAGEORDER, &length, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
 	      window_group, WINDOW_PAGEORDER, error->message);
    g_error_free(error);
    error = NULL;
  } else if (length != page_count) {
    g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %d",
 	      window_group, WINDOW_PAGEORDER, length, page_count);
  } else {
    /* Dump any list that might exist */
    g_list_free(priv->usage_order);
    priv->usage_order = NULL;
    /* Now rebuild the list from the key file. */
    for (i = 0; i < length; i++) {
      gpointer page = g_list_nth_data(priv->installed_pages, order[i] - 1);
      if (page) {
 	priv->usage_order = g_list_append(priv->usage_order, page);
      }
    }
    gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
 				   order[0] - 1);
  }
  if (order) {
    g_free(order);
  }

  LEAVE("window %p", window);
 cleanup:
  if (error)
    g_error_free(error);
  g_free(window_group);
}

void
gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
{
  gint i, window_count;
  GError *error = NULL;
  GncMainWindowSaveData data;
  GncMainWindow *window;

  /* We use the same struct for reading and for writing, so we cast
     away the const. */
  data.key_file = (GKeyFile *) keyfile;
  window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP, 
                                        WINDOW_COUNT, &error);
  if (error) {
    g_warning("error reading group %s key %s: %s",
	      STATE_FILE_TOP, WINDOW_COUNT, error->message);
    g_error_free(error);
    LEAVE("can't read count");
    return;
  }

  /* Restore all state information on the open windows.  Window
     numbers in state file are 1-based. GList indices are 0-based. */
  gnc_set_busy_cursor (NULL, TRUE);
  for (i = 0; i < window_count; i++) {
    data.window_num = i;
    window = g_list_nth_data(active_windows, i);
    gnc_main_window_restore_window(window, &data);
  }
  gnc_unset_busy_cursor (NULL);
}

void
gnc_main_window_restore_default_state(void)
{
    GtkAction *action;
    GncMainWindow *window;
    
    /* The default state should be to have an Account Tree page open
     * in the window. */
    DEBUG("no saved state file");
    window = g_list_nth_data(active_windows, 0);
    action = gnc_main_window_find_action(window, "FileNewAccountTreeAction");
    gtk_action_activate(action);
}

/** Save the state of a single page to a disk.  This function handles
 *  all the common tasks such as saving the page type and name, and
 *  anything else that might be common to all pages.  It then calls a
 *  page specific function to save the actual page.
 *
 *  @param page The GncPluginPage whose state should be saved.
 *
 *  @param data A data structure containing state about the
 *  window/page saving process. */
static void
gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
{
  gchar *page_group;
  const gchar *plugin_name, *page_name;

  ENTER("page %p, data %p (key file %p, window %d, page %d)",
	page, data, data->key_file, data->window_num, data->page_num);
  plugin_name = gnc_plugin_page_get_plugin_name(page);
  page_name = gnc_plugin_page_get_page_name(page);
  if (!plugin_name || !page_name) {
      LEAVE("not saving invalid page");
      return;
  }
  page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
  g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
  g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);

  gnc_plugin_page_save_page(page, data->key_file, page_group);
  g_free(page_group);
  LEAVE(" ");
}


/** Saves all the pages in a single window to a disk.  This function
 *  saves all the window specific attributes, then calls a helper
 *  function to save all the pages that are contained in the window.
 *
 *  @param window The GncMainWindow whose pages should be saved.
 *
 *  @param data A data structure containing state about the
 *  window/page saving process. */
static void
gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
{
  GncMainWindowPrivate *priv;
  GtkAction *action;
  gint i, num_pages, coords[4], *order;
  gboolean maximized, visible;
  gchar *window_group;

  /* Setup */
  ENTER("window %p, data %p (key file %p, window %d)",
	window, data, data->key_file, data->window_num);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);

  /* Check for bogus window structures. */
  num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
  if (0 == num_pages) {
    LEAVE("empty window %p", window);
    return;
  }

  /* Save this window's notebook info */
  window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
  g_key_file_set_integer(data->key_file, window_group,
			 WINDOW_PAGECOUNT, num_pages);
  g_key_file_set_integer(data->key_file, window_group,
			 WINDOW_FIRSTPAGE, data->page_num);

  /* Save page ordering within the notebook. Use +1 notation so the
   * numbers in the page order match the page sections, at least for
   * the one window case. */
  order = g_malloc(sizeof(gint) * num_pages);
  for (i = 0; i < num_pages; i++) {
    gpointer page = g_list_nth_data(priv->usage_order, i);
    order[i] = g_list_index(priv->installed_pages, page) + 1;
  }
  g_key_file_set_integer_list(data->key_file, window_group,
			      WINDOW_PAGEORDER, order, num_pages);
  g_free(order);

  /* Save the window coordinates, etc. */
  gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
  gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
  maximized = (gdk_window_get_state((GTK_WIDGET(window))->window)
	       & GDK_WINDOW_STATE_MAXIMIZED) != 0;
  g_key_file_set_integer_list(data->key_file, window_group,
			      WINDOW_POSITION, &coords[0], 2);
  g_key_file_set_integer_list(data->key_file, window_group,
			      WINDOW_GEOMETRY, &coords[2], 2);
  g_key_file_set_boolean(data->key_file, window_group,
			 WINDOW_MAXIMIZED, maximized);
  DEBUG("window (%p) position %dx%d, size %dx%d, %s", window,  coords[0], coords[1],
	coords[2], coords[3],
	maximized ? "maximized" : "not maximized");

  /* Common view menu items */
  action = gnc_main_window_find_action(window, "ViewToolbarAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  g_key_file_set_boolean(data->key_file, window_group,
			 TOOLBAR_VISIBLE, visible);
  action = gnc_main_window_find_action(window, "ViewSummaryAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  g_key_file_set_boolean(data->key_file, window_group,
			 SUMMARYBAR_VISIBLE, visible);
  action = gnc_main_window_find_action(window, "ViewStatusbarAction");
  visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
  g_key_file_set_boolean(data->key_file, window_group,
			 STATUSBAR_VISIBLE, visible);

  /* Save individual pages in this window */
  g_list_foreach(priv->installed_pages, (GFunc)gnc_main_window_save_page, data);

  g_free(window_group);
  LEAVE("window %p", window);
}

void
gnc_main_window_save_all_windows(GKeyFile *keyfile)
{
    GncMainWindowSaveData data;

    /* Set up the iterator data structures */
    data.key_file = keyfile;
    data.window_num = 1;
    data.page_num = 1;

    g_key_file_set_integer(data.key_file,
                           STATE_FILE_TOP, WINDOW_COUNT,
                           g_list_length(active_windows));
    /* Dump all state information on the open windows */
    g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
}


gboolean
gnc_main_window_finish_pending (GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GList *item;

	g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	for (item = priv->installed_pages; item; item = g_list_next(item)) {
	  if (!gnc_plugin_page_finish_pending(item->data)) {
	    return FALSE;
	  }
	}
	return TRUE;
}


gboolean
gnc_main_window_all_finish_pending (void)
{
	const GList *windows, *item;

	windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
	for (item = windows; item; item = g_list_next(item)) {
	  if (!gnc_main_window_finish_pending(item->data)) {
	    return FALSE;
	  }
	}
	return TRUE;
}


/** See if the page already exists.  For each open window, look
 *  through the list of pages installed in that window and see if the
 *  specified page is there.
 *
 *  @internal
 *
 *  @param page The page to search for.
 *
 *  @return TRUE if the page is present in the window, FALSE otherwise.
 */
static gboolean
gnc_main_window_page_exists (GncPluginPage *page)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;
	GList *walker;

	for (walker = active_windows; walker; walker = g_list_next(walker)) {
	  window = walker->data;
	  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	  if (g_list_find(priv->installed_pages, page)) {
	    return TRUE;
	  }
	}
	return FALSE;
}


/** This function prompts the user to save the file with a dialog that
 *  follows the HIG guidelines.
 *
 *  @internal
 *
 *  @returns This function returns TRUE if the user clicked the Cancel
 *  button.  It returns FALSE if the closing of the window should
 *  continue.
 */
static gboolean
gnc_main_window_prompt_for_save (GtkWidget *window)
{
  QofSession *session;
  QofBook *book;
  GtkWidget *dialog;
  gint response;
  const gchar *filename, *tmp;
  const gchar *title = _("Save changes to file %s before closing?");
  /* This should be the same message as in gnc-file.c */
  const gchar *message =
    _("If you don't save, changes from the past %d minutes will be discarded.");
  time_t oldest_change;
  gint minutes;

  session = gnc_get_current_session();
  book = qof_session_get_book(session);
  filename = qof_session_get_url(session);
  if (filename == NULL)
    filename = _("<unknown>");
  if ((tmp = strrchr(filename, '/')) != NULL)
    filename = tmp + 1;

  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
				  GTK_DIALOG_MODAL,
				  GTK_MESSAGE_WARNING,
				  GTK_BUTTONS_NONE,
				  title,
				  filename);
  oldest_change = qof_book_get_dirty_time(book);
  minutes = (time(NULL) - oldest_change) / 60 + 1;
  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
					   message, minutes);

  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
			 _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
			 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			 GTK_STOCK_SAVE, GTK_RESPONSE_APPLY,
			 NULL);
  gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
  response = gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy(dialog);

  switch (response) {
    case GTK_RESPONSE_APPLY:
      gnc_file_save();
      return FALSE;

    case GTK_RESPONSE_CLOSE:
      qof_book_mark_saved(book);
      return FALSE;

    default:
      return TRUE;
  }
}


static void
gnc_main_window_add_plugin (gpointer plugin,
			    gpointer window)
{
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (GNC_IS_PLUGIN (plugin));

	ENTER(" ");
	gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
				  GNC_MAIN_WINDOW (window),
				  window_type);
	LEAVE(" ");
}

static void
gnc_main_window_remove_plugin (gpointer plugin,
			       gpointer window)
{
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (GNC_IS_PLUGIN (plugin));

	ENTER(" ");
	gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
				       GNC_MAIN_WINDOW (window),
				       window_type);
	LEAVE(" ");
}


static gboolean
gnc_main_window_timed_quit (gpointer dummy)
{
  if (gnc_file_save_in_progress())
    return TRUE;

  gnc_shutdown (0);
  return FALSE;
}

static gboolean
gnc_main_window_quit(GncMainWindow *window)
{
    QofSession *session;
    gboolean needs_save, do_shutdown;

    session = gnc_get_current_session();
    needs_save = qof_book_not_saved(qof_session_get_book(session)) && 
        !gnc_file_save_in_progress();
    do_shutdown = !needs_save || 
        (needs_save && !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));

    if (do_shutdown) {
        g_timeout_add(250, gnc_main_window_timed_quit, NULL);
        return TRUE;
    }
    return FALSE;
}

static gboolean
gnc_main_window_delete_event (GtkWidget *window,
			      GdkEvent *event,
			      gpointer user_data)
{
  static gboolean already_dead = FALSE;

  if (already_dead)
    return TRUE;

  if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window))) {
    /* Don't close the window. */
    return TRUE;
  }

  if (g_list_length(active_windows) > 1)
    return FALSE;

  already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
  return TRUE;
}


/** This function handles any event notifications from the engine.
 *  The only event it currently cares about is the deletion of a book.
 *  When a book is deleted, it runs through all installed pages
 *  looking for pages that reference the just (about to be?) deleted
 *  book.  It closes any page it finds so there are no dangling
 *  references to the book.
 *
 *  @internal
 *
 *  @param entity     The guid the item being added, deleted, etc.
 *
 *  @param type       The type of the item being added, deleted, etc. This
 *                    function only cares about a type of GNC_ID_BOOK.
 *
 *  @param event_type The type of the event.  This function only cares
 *                    about an event type of QOF_EVENT_DESTROY.
 *
 *  @param user_data  A pointer to the window data structure.
 */
static void
gnc_main_window_event_handler (QofEntity *entity,  QofEventId event_type,
                               gpointer user_data, gpointer event_data)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;
	GncPluginPage *page;
	GList *item, *next;

	/* hard failures */
	g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));

	/* soft failures */
	if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
	  return;
	if (event_type !=  QOF_EVENT_DESTROY)
	  return;

	ENTER("entity %p, event %d, window %p, event data %p",
	      entity, event_type, user_data, event_data);
	window = GNC_MAIN_WINDOW(user_data);
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);

        /* This is not a typical list iteration.  We're removing while
         * we iterate, so we have to cache the 'next' pointer before
         * executing any code in the loop. */
	for (item = priv->installed_pages; item; item = next) {
	  next = g_list_next(item);
	  page = GNC_PLUGIN_PAGE(item->data);
	  if (gnc_plugin_page_has_book (page, (QofBook *)entity))
              gnc_main_window_close_page (page);
	}
	LEAVE(" ");
}


/** Generate a title for this window based upon the Gnome Human
 *  Interface Guidelines, v2.0.  This title will be used as both the
 *  window title and the title of the "Window" menu item associated
 *  with the window.
 *
 *  @param window The window whose title should be generated.
 *
 *  @return The title for the window.  It is the callers
 *  responsibility to free this string.
 *
 *  @internal
 */
static gchar *
gnc_main_window_generate_title (GncMainWindow *window)
{
  GncMainWindowPrivate *priv;
  GncPluginPage *page;
  QofBook *book;
  const gchar *filename = NULL, *dirty = "";
  gchar *title, *ptr;

  if (gnc_current_session_exist()) {
      filename = gnc_session_get_url (gnc_get_current_session ());
      book = gnc_get_current_book();
      if (qof_instance_is_dirty(QOF_INSTANCE(book)))
	dirty = "*";
 }

  if (!filename)
    filename = _("<no file>");
  else {
    /* The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
    ptr = g_utf8_strrchr(filename, -1, G_DIR_SEPARATOR);
    if (ptr != NULL)
      filename = g_utf8_next_char(ptr);
  }

  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  page = priv->current_page;
  if (page) {
    /* The Gnome HIG 2.0 recommends the application name not be used. (p16) */
    title = g_strdup_printf("%s%s - %s", dirty, filename,
			    gnc_plugin_page_get_page_name(page));
  } else {
    title = g_strdup_printf("%s%s", dirty, filename);
  }
  
  return title;
}


/** Update the title bar on the specified window.  This routine uses
 *  the gnc_main_window_generate_title() function to create the title.
 *  It is called whenever the user switched pages in a window, as the
 *  title includes the name of the current page.
 *
 *  @param window The window whose title should be updated.
 *
 *  @internal
 */
static void
gnc_main_window_update_title (GncMainWindow *window)
{
  gchar *title;

  title = gnc_main_window_generate_title(window);
  gtk_window_set_title(GTK_WINDOW(window), title);
  g_free(title);
}

static void
gnc_main_window_update_all_titles (void)
{
  g_list_foreach(active_windows,
		 (GFunc)gnc_main_window_update_title,
		 NULL);
}

static void
gnc_main_window_book_dirty_cb (QofBook *book,
			       gboolean dirty,
			       gpointer user_data)
{
  gnc_main_window_update_all_titles();
}

static void
gnc_main_window_attach_to_book (QofSession *session)
{
  QofBook *book;

  g_return_if_fail(session);

  book = qof_session_get_book(session);
  qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, NULL);
}


/** This data structure is used to describe the requested state of a
 *  GtkRadioAction, and us used to pass data among several
 *  functions. */
struct menu_update {
  /** The name of the GtkRadioAction to be updated. */
  gchar    *action_name;

  /** The new label for this GtkRadioAction. */
  gchar    *label;

  /** Whether or not the GtkRadioAction should be visible. */
  gboolean  visible;
};


/** Update the label on the specified GtkRadioAction in the specified
 *  window.  This action is displayed as a menu item in the "Windows"
 *  menu.  This function will end up being called whenever the front
 *  page is changed in any window, or whenever a window is added or
 *  deleted.
 *
 *  @param window The window whose menu item should be updated.
 *
 *  @param data A data structure containing the name of the
 *  GtkRadioAction, and describing the new state for this action.
 *
 *  @internal
 */
static void
gnc_main_window_update_one_menu_action (GncMainWindow *window,
					struct menu_update *data)
{
  GncMainWindowPrivate *priv;
  GtkAction* action;

  ENTER("window %p, action %s, label %s, visible %d", window,
	data->action_name, data->label, data->visible);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  action = gtk_action_group_get_action(priv->action_group, data->action_name);
  if (action)
    g_object_set(G_OBJECT(action),
		 "label", data->label,
		 "visible", data->visible,
		 (char *)NULL);
  LEAVE(" ");
}


/** Update the window selection GtkRadioAction for a specific window.
 *  This is fairly simple since the windows are listed in the same
 *  order that they appear in the active_windows list, so the index
 *  from the window list is used to generate the name of the action.
 *  If the code is ever changed to allow more than ten open windows in
 *  the menu, then the actions in the menu will need to be dynamically
 *  generated/deleted and it gets harder.
 *
 *  @param window The window whose menu item should be updated.
 *
 *  @internal
 */
static void
gnc_main_window_update_radio_button (GncMainWindow *window)
{
  GncMainWindowPrivate *priv;
  GtkAction *action, *first_action;
  GSList *action_list;
  gchar *action_name;
  gint index;

  ENTER("window %p", window);

  /* Show the new entry in all windows. */
  index = g_list_index(active_windows, window);
  if (index >= n_radio_entries) {
    LEAVE("window %d, only %d actions", index, n_radio_entries);
    return;
  }

  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  action_name = g_strdup_printf("Window%dAction", index);
  action = gtk_action_group_get_action(priv->action_group, action_name);

  /* Block the signal so as not to affect window ordering (top to
   * bottom) on the screen */
  action_list = gtk_radio_action_get_group(GTK_RADIO_ACTION(action));
  first_action = g_slist_last(action_list)->data;
  g_signal_handlers_block_by_func(G_OBJECT(first_action),
				  G_CALLBACK(gnc_main_window_cmd_window_raise), window);
  DEBUG("blocked signal on %p, set %p active, window %p", first_action, action, window);
  gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
  g_signal_handlers_unblock_by_func(G_OBJECT(first_action),
				    G_CALLBACK(gnc_main_window_cmd_window_raise), window);
  g_free(action_name);
  LEAVE(" ");
}


/** In every window that the user has open, update the "Window" menu
 *  item that points to the specified window.  This keeps the "Window"
 *  menu items consistent across all open windows.  (These items
 *  cannot be shared because of the way the GtkUIManager code works.)
 *
 *  This function is called whenever the user switches pages in a
 *  window, or whenever a window is added or deleted.
 *
 *  @param window The window whose menu item should be updated in all
 *  open windows.
 *
 *  @internal
 */
static void
gnc_main_window_update_menu_item (GncMainWindow *window)
{
  struct menu_update data;
  gchar **strings, *title, *expanded;
  gint index;

  ENTER("window %p", window);
  index = g_list_index(active_windows, window);
  if (index > n_radio_entries) {
    LEAVE("skip window %d (only %d entries)", index, n_radio_entries);
    return;
  }

  /* Figure out the label name. Add the accelerator if possible. */
  title = gnc_main_window_generate_title(window);
  strings = g_strsplit(title, "_", 0);
  expanded = g_strjoinv("__", strings);
  if (index < 10) {
    data.label = g_strdup_printf("_%d %s", (index + 1) % 10, expanded);
    g_free(expanded);
  } else {
    data.label = expanded;
  }
  g_strfreev(strings);

  data.visible = TRUE;
  data.action_name = g_strdup_printf("Window%dAction", index);
  g_list_foreach(active_windows,
		 (GFunc)gnc_main_window_update_one_menu_action,
		 &data);
  g_free(data.action_name);
  g_free(data.label);
  LEAVE(" ");
}

/** Update all menu entries for all window menu items in all windows.
 *  This function is called whenever a window is added or deleted.
 *  The worst case scenario is where the user has deleted the first
 *  window, so every single visible item needs to be updated.
 *
 *  @internal
 */
static void
gnc_main_window_update_all_menu_items (void)
{
  struct menu_update data;
  gchar *label;
  gint i;

  ENTER("");

  /* First update the entries for all existing windows */
  g_list_foreach(active_windows,
		 (GFunc)gnc_main_window_update_menu_item,
		 NULL);
  g_list_foreach(active_windows,
		 (GFunc)gnc_main_window_update_radio_button,
		 NULL);

  /* Now hide any entries that aren't being used. */
  data.visible = FALSE;
  for (i = g_list_length(active_windows); i < n_radio_entries; i++) {
    data.action_name = g_strdup_printf("Window%dAction", i);
    label = g_strdup_printf("Window _%d", (i - 1) % 10);
    data.label = gettext(label);

    g_list_foreach(active_windows,
		   (GFunc)gnc_main_window_update_one_menu_action,
		   &data);

    g_free(data.action_name);
    g_free(label);
  }
  LEAVE(" ");
}


/** Show/hide the close box on the tab of a notebook page.  This
 *  function first checks to see if the specified page has a close
 *  box, and if so, sets its visibility to the requested state.
 *
 *  @internal
 *
 *  @param page The GncPluginPage whose notebook tab should be updated.
 *
 *  @param new_value A pointer to the boolean that indicates whether
 *  or not the close button should be visible.
 */
static void
gnc_main_window_update_tabs_one_page (GncPluginPage *page,
				      gboolean *new_value)
{
  GtkWidget * close_button;

  ENTER("page %p, visible %d", page, *new_value);
  close_button = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON);
  if (!close_button) {
    LEAVE("no close button");
    return;
  }

  if (*new_value)
    gtk_widget_show (close_button);
  else
    gtk_widget_hide (close_button);
  LEAVE(" ");
}


/** Show/hide the close box on all pages in a given window.  This
 *  function calls the gnc_main_window_update_tabs_one_page() for each
 *  page in the window.
 *
 *  @internal
 *
 *  @param window The GncMainWindow whose notebook tabs should be
 *  updated.
 *
 *  @param new_value A pointer to the boolean that indicates whether
 *  or not the close button should be visible.
 */
static void
gnc_main_window_update_tabs_one_window (GncMainWindow *window, gboolean *new_value)
{
  GncMainWindowPrivate *priv;

  ENTER("window %p, visible %d", window, *new_value);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  g_list_foreach(priv->installed_pages,
		 (GFunc)gnc_main_window_update_tabs_one_page,
		 new_value);
  LEAVE(" ");
}


/** Show/hide the close box on all pages in all windows.  This
 *  function calls the gnc_main_window_update_tabs_one_window() for
 *  each open window in the application.
 *
 *  @internal
 *
 *  @param entry A pointer to the GConfEntry which describes the new
 *  state of whether close buttons should be visible on notebook tabs.
 *
 *  @param user_data Unused.
 */
static void
gnc_main_window_update_tabs (GConfEntry *entry, gpointer user_data)
{
  gboolean new_value;

  ENTER(" ");
  new_value = gconf_value_get_bool(entry->value);
  g_list_foreach(active_windows,
		 (GFunc)gnc_main_window_update_tabs_one_window,
		 &new_value);
  LEAVE(" ");
}


/************************************************************
 *                 Tab Label Implementation                 *
 ************************************************************/
static gboolean
main_window_find_tab_items (GncMainWindow *window,
			    GncPluginPage *page,
			    GtkWidget **label_p,
			    GtkWidget **entry_p)
{
  GncMainWindowPrivate *priv;
  GtkWidget *tab_hbox, *widget;
  GList *children, *tmp;

  ENTER("window %p, page %p, label_p %p, entry_p %p",
	window, page, label_p, entry_p);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  *label_p = *entry_p = NULL;
  tab_hbox = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
					page->notebook_page);
  children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
  for (tmp = children; tmp; tmp = g_list_next(tmp)) {
    widget = tmp->data;
    if (GTK_IS_LABEL(widget)) {
      *label_p = widget;
    } else if (GTK_IS_ENTRY(widget)) {
      *entry_p = widget;
    }
  }
  g_list_free(children);

  LEAVE("label %p, entry %p", *label_p, *entry_p);
  return (*label_p && *entry_p);
}

void
main_window_update_page_name (GncPluginPage *page,
			      const gchar *name_in)
{
  GncMainWindow *window;
  GncMainWindowPrivate *priv;
  GtkWidget *label, *entry;
  gchar *name;

  ENTER(" ");

  if ((name_in == NULL) || (*name_in == '\0')) {
    LEAVE("no string");
    return;
  }
  name = g_strstrip(g_strdup(name_in));
  if (*name == '\0') {
    g_free(name);
    LEAVE("empty string");
    return;
  }

  /* Update the plugin */
  window = GNC_MAIN_WINDOW(page->window);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  gnc_plugin_page_set_page_name(page, name);

  /* Update the notebook tab */
  main_window_find_tab_items(window, page, &label, &entry);
  gtk_label_set_text(GTK_LABEL(label), name);

  /* Update the notebook menu */
  label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
				       page->notebook_page);
  gtk_label_set_text(GTK_LABEL(label), name);
  
  /* Force an update of the window title */
  gnc_main_window_update_title(window);
  g_free(name);
  LEAVE("done");
}

static void
gnc_main_window_tab_entry_activate (GtkWidget *entry,
				    GncPluginPage *page)
{
  GtkWidget *label, *entry2;

  g_return_if_fail(GTK_IS_ENTRY(entry));
  g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));

  ENTER("");
  if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
				  page, &label, &entry2)) {
    LEAVE("can't find required widgets");
    return;
  }

  main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));

  gtk_widget_hide(entry);
  gtk_widget_show(label);
  LEAVE("");
}


static gboolean
gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
					GncPluginPage *page)
{
  ENTER("");
  gnc_main_window_tab_entry_activate(entry, page);
  LEAVE("");
  return FALSE;
}

static gboolean
gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
					   GdkEvent *event,
					   GncPluginPage *page)
{
  ENTER("");
  gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
  LEAVE("");
  return FALSE;
}

static gboolean
gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
					   GdkEventKey *event,
					   GncPluginPage *page)
{
  if (event->keyval == GDK_Escape) {
    GtkWidget *label, *entry2;

    g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
    g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);

    ENTER("");
    if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
				    page, &label, &entry2)) {
      LEAVE("can't find required widgets");
      return FALSE;
    }

    gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
    gtk_widget_hide(entry);
    gtk_widget_show(label);
    LEAVE("");
  }
  return FALSE;
}

/************************************************************
 *                   Widget Implementation                  *
 ************************************************************/

/*  Get the type of a gnc main window.
 */
GType
gnc_main_window_get_type (void)
{
	static GType gnc_main_window_type = 0;

	if (gnc_main_window_type == 0) {
		static const GTypeInfo our_info = {
			sizeof (GncMainWindowClass),
			NULL,
			NULL,
			(GClassInitFunc) gnc_main_window_class_init,
			NULL,
			NULL,
			sizeof (GncMainWindow),
			0,
			(GInstanceInitFunc) gnc_main_window_init
		};

		static const GInterfaceInfo plugin_info = {
		  (GInterfaceInitFunc) gnc_window_main_window_init,
		  NULL,
		  NULL
		};

		gnc_main_window_type = g_type_register_static (GTK_TYPE_WINDOW,
							       GNC_MAIN_WINDOW_NAME,
							       &our_info, 0);
		g_type_add_interface_static (gnc_main_window_type,
					     GNC_TYPE_WINDOW,
					     &plugin_info);
	}

	return gnc_main_window_type;
}


/** Initialize the class for a new gnucash main window.  This will set
 *  up any function pointers that override functions in the parent
 *  class, and also initialize the signals that this class of widget
 *  can generate.
 *
 *  @param klass The new class structure created by the object system.
 */
static void
gnc_main_window_class_init (GncMainWindowClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS(klass);

	parent_class = g_type_class_peek_parent (klass);

	window_type = g_quark_from_static_string ("gnc-main-window");

	object_class->finalize = gnc_main_window_finalize;

	/* GtkObject signals */
	gtkobject_class->destroy = gnc_main_window_destroy;

	g_type_class_add_private(klass, sizeof(GncMainWindowPrivate));

	/**
	 * GncMainWindow::page_added:
	 * @param window: the #GncMainWindow
	 * @param page: the #GncPluginPage
	 *
	 * The "page_added" signal is emitted when a new page is added
	 * to the notebook of a GncMainWindow.  This can be used to
	 * attach a signal from the page so that menu actions can be
	 * adjusted based upon events that occur within the page
	 * (e.g. an account is selected.)
	 */
	main_window_signals[PAGE_ADDED] =
	  g_signal_new ("page_added",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET (GncMainWindowClass, page_added),
			NULL, NULL,
			g_cclosure_marshal_VOID__OBJECT,
			G_TYPE_NONE, 1,
			G_TYPE_OBJECT);

	/**
	 * GncMainWindow::page_changed:
	 * @param window: the #GncMainWindow
	 * @param page: the #GncPluginPage
	 *
	 * The "page_changed" signal is emitted when a new page is
	 * selected in the notebook of a GncMainWindow.  This can be
	 * used to to adjust menu actions based upon which page is
	 * currently displayed in a window.
	 */
	main_window_signals[PAGE_CHANGED] =
	  g_signal_new ("page_changed",
			G_OBJECT_CLASS_TYPE (object_class),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
			NULL, NULL,
			g_cclosure_marshal_VOID__OBJECT,
			G_TYPE_NONE, 1,
			G_TYPE_OBJECT);

	gnc_gconf_general_register_cb (KEY_SHOW_CLOSE_BUTTON,
				       gnc_main_window_update_tabs,
				       NULL);
	gnc_hook_add_dangler(HOOK_BOOK_SAVED,
			     (GFunc)gnc_main_window_update_all_titles, NULL);
	gnc_hook_add_dangler(HOOK_BOOK_OPENED,
			     (GFunc)gnc_main_window_attach_to_book, NULL);
}


/** Initialize a new instance of a gnucash main window.  This function
 *  initializes the object private storage space.  It also adds the
 *  new object to a list (for memory tracking purposes).
 *
 *  @param view The new object instance created by the object system.
 *
 *  @param klass A pointer to the class data structure for this
 *  object. */
static void
gnc_main_window_init (GncMainWindow *window,
		      GncMainWindowClass *klass)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	priv->merged_actions_table =
	  g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

	priv->event_handler_id =
	  qof_event_register_handler(gnc_main_window_event_handler, window);

	gnc_main_window_setup_window (window);
	gnc_gobject_tracking_remember(G_OBJECT(window),
				      G_OBJECT_CLASS(klass));
}


/** Finalize the GncMainWindow object.  This function is called from
 *  the G_Object level to complete the destruction of the object.  It
 *  should release any memory not previously released by the destroy
 *  function (i.e. the private data structure), then chain up to the
 *  parent's destroy function.
 *
 *  @param object The object being destroyed.
 *
 *  @internal
 */
static void
gnc_main_window_finalize (GObject *object)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GNC_IS_MAIN_WINDOW (object));

	window = GNC_MAIN_WINDOW (object);
	priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);

	if (active_windows == NULL) {
	  /* Oops. User killed last window and we didn't catch it. */
	  g_idle_add((GSourceFunc)gnc_shutdown, 0);
	}

	gnc_gobject_tracking_forget(object);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}


static void
gnc_main_window_destroy (GtkObject *object)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;
	GncPluginManager *manager;
	GList *plugins;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GNC_IS_MAIN_WINDOW (object));

	window = GNC_MAIN_WINDOW (object);

	active_windows = g_list_remove (active_windows, window);

	/* Do these things once */
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->merged_actions_table) {

	  /* Close any pages in this window */
	  while (priv->current_page)
	    gnc_main_window_close_page(priv->current_page);

	  if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
	    gnc_window_set_progressbar_window(NULL);

	  /* Update the "Windows" menu in all other windows */
	  gnc_main_window_update_all_menu_items();

	  gnc_gconf_remove_notification(G_OBJECT(window), DESKTOP_GNOME_INTERFACE,
					GNC_MAIN_WINDOW_NAME);
	  gnc_gconf_remove_notification(G_OBJECT(window), GCONF_GENERAL,
					GNC_MAIN_WINDOW_NAME);

	  qof_event_unregister_handler(priv->event_handler_id);
	  priv->event_handler_id = 0;

	  g_hash_table_destroy (priv->merged_actions_table);
	  priv->merged_actions_table = NULL;

	  /* GncPluginManager stuff */
	  manager = gnc_plugin_manager_get ();
	  plugins = gnc_plugin_manager_get_plugins (manager);
	  g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
	  g_list_free (plugins);
	}
	GTK_OBJECT_CLASS (parent_class)->destroy (object);
}


/*  Create a new gnc main window plugin.
 */
GncMainWindow *
gnc_main_window_new (void)
{
	GncMainWindow *window;
	gncUIWidget old_window;

	window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL);
	old_window = gnc_ui_get_toplevel();
	if (old_window) {
	  gint width, height;
	  gtk_window_get_size (GTK_WINDOW (old_window), &width, &height);
	  gtk_window_resize (GTK_WINDOW (window), width, height);
	  if ((gdk_window_get_state((GTK_WIDGET(old_window))->window)
	       & GDK_WINDOW_STATE_MAXIMIZED) != 0)
	    gtk_window_maximize (GTK_WINDOW (window));
	}
	active_windows = g_list_append (active_windows, window);
	gnc_main_window_update_all_menu_items();
	return window;
}

/************************************************************
 *                     Utility Functions                    *
 ************************************************************/

/** Connect a GncPluginPage to the window.  This function will insert
 *  the page in to the window's notebook and its list of active pages.
 *  It will also emit the "inserted" signal on the page, and the
 *  "add_page" signal on the window.
 *
 *  @param window The window where the new page should be added.
 *
 *  @param page The GncPluginPage that should be added to the window.
 *  The visible widget for this plugin must have already been created.
 *
 *  @param tab_hbox The widget that should be added into the notebook
 *  tab for this page.  Generally this is a GtkLabel, but could also
 *  be a GtkHBox containing an icon and a label.
 *
 *  @param menu_label The widget that should be added into the
 *  notebook popup menu for this page.  This should be a GtkLabel.
 */
static void
gnc_main_window_connect (GncMainWindow *window,
			 GncPluginPage *page,
			 GtkWidget *tab_hbox,
			 GtkWidget *menu_label)
{
	GncMainWindowPrivate *priv;
	GtkNotebook *notebook;

	page->window = GTK_WIDGET(window);
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	notebook = GTK_NOTEBOOK (priv->notebook);
	priv->installed_pages = g_list_append (priv->installed_pages, page);
	priv->usage_order = g_list_prepend (priv->usage_order, page);
	gtk_notebook_append_page_menu (notebook, page->notebook_page,
				       tab_hbox, menu_label);
#ifdef HAVE_GTK_2_10
	gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
#endif
	gnc_plugin_page_inserted (page);
	gtk_notebook_set_current_page (notebook, -1);
	if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
	  (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
	g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);

	g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
			 G_CALLBACK(gnc_main_window_popup_menu_cb), page);
	g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
			 G_CALLBACK(gnc_main_window_button_press_cb), page);
}


/** Disconnect a GncPluginPage page from the window.  If this page is
 *  currently foremost in the window's notebook, its user interface
 *  actions will be disconnected and the page's summarybar widget (if
 *  any) will be removed.  The page is then removed from the window's
 *  notebook and its list of active pages.
 *
 *  @param window The window the page should be removed from.
 *
 *  @param page The GncPluginPage that should be removed from the
 *  window.
 *
 *  @internal
 */
static void
gnc_main_window_disconnect (GncMainWindow *window,
			    GncPluginPage *page)
{
	GncMainWindowPrivate *priv;
	GtkNotebook *notebook;
	GncPluginPage *new_page;
	gint page_num;

	/* Disconnect the callbacks */
	g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
			 G_CALLBACK(gnc_main_window_popup_menu_cb), page);
	g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
			 G_CALLBACK(gnc_main_window_button_press_cb), page);

	/* Disconnect the page and summarybar from the window */
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->current_page == page) {
		gnc_plugin_page_unmerge_actions (page, window->ui_merge);
		gnc_plugin_page_unselected (page);
		priv->current_page = NULL;
	}

	/* Remove it from the list of pages in the window */
	priv->installed_pages = g_list_remove (priv->installed_pages, page);
	priv->usage_order = g_list_remove (priv->usage_order, page);

	/* Switch to the last recently used page */
	notebook = GTK_NOTEBOOK (priv->notebook);
	new_page = g_list_nth_data (priv->usage_order, 0);
	if (new_page) {
	  page_num =  gtk_notebook_page_num(notebook, new_page->notebook_page);
	  gtk_notebook_set_current_page(notebook, page_num);
	}

	/* Remove the page from the notebook */
	page_num =  gtk_notebook_page_num(notebook, page->notebook_page);
	gtk_notebook_remove_page (notebook, page_num);

	if ( gtk_notebook_get_current_page(notebook) == -1) {
	  /* Need to synthesize a page changed signal when the last
	   * page is removed.  The notebook doesn't generate a signal
	   * for this, therefore the switch_page code in this file
	   * never gets called to generate this signal. */
	  gnc_main_window_switch_page(notebook, NULL, -1, window);
	  //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, NULL);
	}

	gnc_plugin_page_removed (page);

	gtk_ui_manager_ensure_update (window->ui_merge);
	gnc_window_set_status (GNC_WINDOW(window), page, NULL);
}


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


void
gnc_main_window_display_page (GncPluginPage *page)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;
	GtkNotebook *notebook;
	gint page_num;

	window = GNC_MAIN_WINDOW (page->window);
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	notebook = GTK_NOTEBOOK (priv->notebook);
	page_num = gtk_notebook_page_num(notebook, page->notebook_page);
	gtk_notebook_set_current_page (notebook, page_num);
	gtk_window_present(GTK_WINDOW(window));
}


/*  Display a data plugin page in a window.  If the page already
 *  exists in any window, then that window will be brought to the
 *  front and the notebook switch to display the specified page.  If
 *  the page is new then it will be added to the specified window.  If
 *  the window is NULL, the new page will be added to the first
 *  window.
 */
void
gnc_main_window_open_page (GncMainWindow *window,
			   GncPluginPage *page)
{
	GncMainWindowPrivate *priv;
	GtkWidget *tab_hbox;
	GtkWidget *label, *entry;
	const gchar *icon;
	GtkWidget *image;
	GList *tmp;

	ENTER("window %p, page %p", window, page);

	if (window)
	  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
	g_return_if_fail (gnc_plugin_page_has_books(page));

	if (gnc_main_window_page_exists(page)) {
	  gnc_main_window_display_page(page);
	  return;
	}

	/* Does the page want to be in a new window? */
	if (gnc_plugin_page_get_use_new_window(page)) {
	  /* See if there's a blank window. If so, use that. */
	  for (tmp = active_windows; tmp; tmp = g_list_next(tmp)) {
	    window = GNC_MAIN_WINDOW(tmp->data);
	    priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	    if (priv->installed_pages == NULL) {
	      break;
	    }
	  }
	  if (tmp == NULL)
	    window = gnc_main_window_new ();
	  gtk_widget_show(GTK_WIDGET(window));
	} else if ((window == NULL) && active_windows) {
	  window = active_windows->data;
	}

	page->window = GTK_WIDGET(window);
	page->notebook_page = gnc_plugin_page_create_widget (page);
	g_object_set_data (G_OBJECT (page->notebook_page),
			   PLUGIN_PAGE_LABEL, page);

	/*
	 * The page tab.
	 */
	icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
	label = gtk_label_new (gnc_plugin_page_get_page_name(page));
	gtk_widget_show (label);

	tab_hbox = gtk_hbox_new (FALSE, 6);
	gtk_widget_show (tab_hbox);

	if (icon != NULL) {
		image = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU);
		gtk_widget_show (image);
		gtk_box_pack_start (GTK_BOX (tab_hbox), image, FALSE, FALSE, 0);
	} 

	gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
  
	entry = gtk_entry_new();
	gtk_widget_hide (entry);
	gtk_box_pack_start (GTK_BOX (tab_hbox), entry, TRUE, TRUE, 0);
	g_signal_connect(G_OBJECT(entry), "activate",
			 G_CALLBACK(gnc_main_window_tab_entry_activate), page);
	g_signal_connect(G_OBJECT(entry), "focus-out-event",
			 G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
			 page);
	g_signal_connect(G_OBJECT(entry), "key-press-event",
			 G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
			 page);
	g_signal_connect(G_OBJECT(entry), "editing-done",
			 G_CALLBACK(gnc_main_window_tab_entry_editing_done),
			 page);

	/* Add close button - Not for immutable pages */
	if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE)) {
	  GtkWidget *close_image, *close_button;
	  GtkRequisition requisition;
	  
	  close_button = gtk_button_new();
	  gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
	  close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
	  gtk_widget_show(close_image);
	  gtk_widget_size_request(close_image, &requisition);
	  gtk_widget_set_size_request(close_button, requisition.width + 4,
				      requisition.height + 2);
	  gtk_button_set_alignment(GTK_BUTTON(close_button), 0.5, 0.5);
	  gtk_container_add(GTK_CONTAINER(close_button), close_image);
	  if (gnc_gconf_get_bool(GCONF_GENERAL, KEY_SHOW_CLOSE_BUTTON, NULL))
	    gtk_widget_show (close_button);
	  else
	    gtk_widget_hide (close_button);
     
	  g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
                      G_CALLBACK(gnc_main_window_close_page), page);

	  gtk_box_pack_start (GTK_BOX (tab_hbox), close_button, FALSE, FALSE, 0);

	  g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
	}

	/*
	 * The popup menu
	 */
	label = gtk_label_new (gnc_plugin_page_get_page_name(page));

	/*
	 * Now install it all in the window.
	 */
	gnc_main_window_connect(window, page, tab_hbox, label);

	LEAVE("");
}


/*  Remove a data plugin page from a window and display the previous
 *  page.  If the page removed was the last page in the window, and
 *  there is more than one window open, then the entire window will be
 *  destroyed.
 */
void
gnc_main_window_close_page (GncPluginPage *page)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;

	if (!page || !page->notebook_page)
	  return;

	if (!gnc_plugin_page_finish_pending(page))
	  return;

	window = GNC_MAIN_WINDOW (page->window);
	if (!window) {
	  g_warning("Page is not in a window.");
	  return;
	}

	gnc_main_window_disconnect(window, page);
	gnc_plugin_page_destroy_widget (page);
	g_object_unref(page);

	/* If this isn't the last window, go ahead and destroy the window. */
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->installed_pages == NULL) {
		if (g_list_length(active_windows) > 1) {
			gtk_widget_destroy(GTK_WIDGET(window));
		}
	}
}


/*  Retrieve a pointer to the page that is currently at the front of
 *  the specified window.  Any plugin that needs to manipulate its
 *  menus based upon the currently selected menu page should connect
 *  to the "page_changed" signal on a window.  The callback function
 *  from that signal can then call this function to obtain a pointer
 *  to the current page.
 */
GncPluginPage *
gnc_main_window_get_current_page (GncMainWindow *window)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	return priv->current_page;
}


/*  Manually add a set of actions to the specified window.  Plugins
 *  whose user interface is not hard coded (e.g. the menu-additions
 *  plugin) must create their actions at run time, then use this
 *  function to install them into the window.
 */
void
gnc_main_window_manual_merge_actions (GncMainWindow *window,
				      const gchar *group_name,
				      GtkActionGroup *group,
				      guint merge_id)
{
	GncMainWindowPrivate *priv;
	MergedActionEntry *entry;

	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (group_name != NULL);
	g_return_if_fail (GTK_IS_ACTION_GROUP(group));
	g_return_if_fail (merge_id > 0);

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	entry = g_new0 (MergedActionEntry, 1);
	entry->action_group = group;
	entry->merge_id = merge_id;
	gtk_ui_manager_ensure_update (window->ui_merge);
	g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
}


/*  Add a set of actions to the specified window.  This function
 *  should not need to be called directly by plugin implementors.
 *  Correctly assigning values to the GncPluginClass fields during
 *  plugin initialization will cause this routine to be automatically
 *  called.
 */
void
gnc_main_window_merge_actions (GncMainWindow *window,
			       const gchar *group_name,
			       GtkActionEntry *actions,
			       guint n_actions,
			       const gchar *filename,
			       gpointer user_data)
{
	GncMainWindowPrivate *priv;
	GncMainWindowActionData *data;
	MergedActionEntry *entry;
	GError *error = NULL;
	gchar *pathname;

	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (group_name != NULL);
	g_return_if_fail (actions != NULL);
	g_return_if_fail (n_actions > 0);
	g_return_if_fail (filename != NULL);

	data = g_new0 (GncMainWindowActionData, 1);
	data->window = window;
	data->data = user_data;

	pathname = gnc_gnome_locate_ui_file (filename);
	if (pathname == NULL)
	  return;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	entry = g_new0 (MergedActionEntry, 1);
	entry->action_group = gtk_action_group_new (group_name);
	gnc_gtk_action_group_set_translation_domain (entry->action_group, GETTEXT_PACKAGE);
	gtk_action_group_add_actions (entry->action_group, actions, n_actions, data);
	gtk_ui_manager_insert_action_group (window->ui_merge, entry->action_group, 0);
	entry->merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge, pathname, &error);
	g_assert(entry->merge_id || error);
	if (entry->merge_id) {
	  gtk_ui_manager_ensure_update (window->ui_merge);
	  g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
	} else {
	  g_critical("Failed to load ui file.\n  Filename %s\n  Error %s",
		     filename, error->message);
	  g_error_free(error);
	  g_free(entry);
	}
	g_free(pathname);
}


/*  Remove a set of actions from the specified window.  This function
 *  should not need to be called directly by plugin implementors.  It
 *  will automatically be called when a plugin is removed from a
 *  window.
 */
void
gnc_main_window_unmerge_actions (GncMainWindow *window,
				 const gchar *group_name)
{
	GncMainWindowPrivate *priv;
	MergedActionEntry *entry;

	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (group_name != NULL);

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->merged_actions_table == NULL)
		return;
	entry = g_hash_table_lookup (priv->merged_actions_table, group_name);

	if (entry == NULL)
		return;

	gtk_ui_manager_remove_action_group (window->ui_merge, entry->action_group);
	gtk_ui_manager_remove_ui (window->ui_merge, entry->merge_id);
	gtk_ui_manager_ensure_update (window->ui_merge);

	g_hash_table_remove (priv->merged_actions_table, group_name);
}


/*  Force a full update of the user interface for the specified
 *  window.  This can be an expensive function, but is needed because
 *  the gtk ui manager doesn't always seem to update properly when
 *  actions are changed.
 */
void
gnc_main_window_actions_updated (GncMainWindow *window)
{
	GtkActionGroup *force;

	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));

	/* Unfortunately gtk_ui_manager_ensure_update doesn't work
	 * here.  Force a full update by adding and removing an empty
	 * action group.
	 */
	force = gtk_action_group_new("force_update");
	gtk_ui_manager_insert_action_group (window->ui_merge, force, 0);
	gtk_ui_manager_ensure_update (window->ui_merge);
	gtk_ui_manager_remove_action_group (window->ui_merge, force);
	g_object_unref(force);
}


static GtkAction *
gnc_main_window_find_action (GncMainWindow *window, const gchar *name)
{
  GtkAction *action = NULL;
  const GList *groups, *tmp;

  groups = gtk_ui_manager_get_action_groups(window->ui_merge);
  for (tmp = groups; tmp; tmp = g_list_next(tmp)) {
    action = gtk_action_group_get_action(GTK_ACTION_GROUP(tmp->data), name);
    if (action)
      break;
  }
  return action;
}


/*  Retrieve a specific set of user interface actions from a window.
 *  This function can be used to get an group of action to be
 *  manipulated when the front page of a window has changed.
 */
GtkActionGroup *
gnc_main_window_get_action_group (GncMainWindow *window,
				  const gchar *group_name)
{
	GncMainWindowPrivate *priv;
	MergedActionEntry *entry;

	g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
	g_return_val_if_fail (group_name != NULL, NULL);

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->merged_actions_table == NULL)
		return NULL;
	entry = g_hash_table_lookup (priv->merged_actions_table, group_name);

	if (entry == NULL)
		return NULL;

	return entry->action_group;
}


static void
gnc_main_window_update_toolbar (GncMainWindow *window)
{
	GtkToolbarStyle style;
	GSList *list;

	ENTER("window %p", window);

	style = gnc_get_toolbar_style();
	list = gtk_ui_manager_get_toplevels(window->ui_merge, GTK_UI_MANAGER_TOOLBAR);
	g_slist_foreach(list, (GFunc)gtk_toolbar_set_style, GINT_TO_POINTER(style));
	g_slist_free(list);
	LEAVE("");
}

static void
gnc_main_window_update_tab_position (GncMainWindow *window)
{
	GtkPositionType position = GTK_POS_TOP;
	gchar *conf_string;
	GncMainWindowPrivate *priv;

	ENTER ("window %p", window);
	conf_string = gnc_gconf_get_string (GCONF_GENERAL,
					    KEY_TAB_POSITION, NULL);
	if (conf_string) {
		position = gnc_enum_from_nick (GTK_TYPE_POSITION_TYPE,
					       conf_string, GTK_POS_TOP);
		g_free (conf_string);
	}

	priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
	gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), position);

	LEAVE ("");
}

/*
 * Based on code from Epiphany (src/ephy-window.c)
 */
static void
gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
{
	GncMainWindowPrivate *priv;
	GncPluginPage *page;
	GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
	GtkAction *action;
	gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	page = priv->current_page;
	if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions) {
	  (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
	  return;
	}
	
	if (GTK_IS_EDITABLE (widget))
	{
		gboolean has_selection;

		has_selection = gtk_editable_get_selection_bounds
			(GTK_EDITABLE (widget), NULL, NULL);

		can_copy = has_selection;
		can_cut = has_selection;
		can_paste = TRUE;
	}
	else if (GTK_IS_TEXT_VIEW (widget))
	{
		gboolean has_selection;
		GtkTextBuffer *text_buffer;

		text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
		has_selection = gtk_text_buffer_get_selection_bounds
			(text_buffer, NULL, NULL);

		can_copy = has_selection;
		can_cut = has_selection;
		can_paste = TRUE;
	}
	else
	{
#ifdef ORIGINAL_EPIPHANY_CODE
		/* For now we assume all actions are possible */
		can_copy = can_cut = can_paste = TRUE;
#else
		/* If its not a GtkEditable, we don't know what to do
		 * with it. */
		can_copy = can_cut = can_paste = FALSE;
#endif
	}

	action = gnc_main_window_find_action (window, "EditCopyAction");
	gtk_action_set_sensitive (action, can_copy);
	gtk_action_set_visible (action, !hide || can_copy);
	action = gnc_main_window_find_action (window, "EditCutAction");
	gtk_action_set_sensitive (action, can_cut);
	gtk_action_set_visible (action, !hide || can_cut);
	action = gnc_main_window_find_action (window, "EditPasteAction");
	gtk_action_set_sensitive (action, can_paste);
	gtk_action_set_visible (action,  !hide || can_paste);
}

static void
gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
{
	GtkAction *action;

	action = gnc_main_window_find_action (window, "EditCopyAction");
	gtk_action_set_sensitive (action, TRUE);
	gtk_action_set_visible (action, TRUE);
	action = gnc_main_window_find_action (window, "EditCutAction");
	gtk_action_set_sensitive (action, TRUE);
	gtk_action_set_visible (action, TRUE);
	action = gnc_main_window_find_action (window, "EditPasteAction");
	gtk_action_set_sensitive (action, TRUE);
	gtk_action_set_visible (action, TRUE);
}

static void
gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
		   GncMainWindow *window)
{
	gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
}

static void
gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
		   GncMainWindow *window)
{
	gnc_main_window_enable_edit_actions_sensitivity (window);
}

static void
gnc_main_window_init_menu_updaters (GncMainWindow *window)
{
	GtkWidget *edit_menu_item, *edit_menu;

	edit_menu_item = gtk_ui_manager_get_widget
		(window->ui_merge, "/menubar/Edit");
	edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (edit_menu_item));

	g_signal_connect (edit_menu, "show",
			  G_CALLBACK (gnc_main_window_edit_menu_show_cb), window);
	g_signal_connect (edit_menu, "hide",
			  G_CALLBACK (gnc_main_window_edit_menu_hide_cb), window);
}

static void
gnc_main_window_gconf_changed (GConfClient *client,
			       guint cnxn_id,
			       GConfEntry *entry,
			       gpointer user_data)
{
	GncMainWindow *window;
	GConfValue *value;
	const gchar *key, *key_tail;

	window = GNC_MAIN_WINDOW(user_data);

	key = gconf_entry_get_key(entry);
	value = gconf_entry_get_value(entry);
	if (!key || !value)
	  return;

	key_tail = strrchr(key, '/');
	if (key_tail != NULL)
	  key_tail++;
	if (strcmp(key_tail, KEY_TOOLBAR_STYLE) == 0) {
	  gnc_main_window_update_toolbar(window);
	} else if (strcmp(key_tail, KEY_TAB_POSITION) == 0) {
	  gnc_main_window_update_tab_position(window);
	}
}

/* CS: This callback functions will set the statusbar text to the
 * "tooltip" property of the currently selected GtkAction.
 *
 * This code is directly copied from gtk+/test/testmerge.c. 
 * Thanks to (L)GPL! */
typedef struct _ActionStatus ActionStatus;
struct _ActionStatus {
  GtkAction *action;
  GtkWidget *statusbar;
};

static void
action_status_destroy (gpointer data)
{
  ActionStatus *action_status = data;

  g_object_unref (action_status->action);
  g_object_unref (action_status->statusbar);

  g_free (action_status);
}

static void
set_tip (GtkWidget *widget)
{
  ActionStatus *data;
  gchar *tooltip;
  
  data = g_object_get_data (G_OBJECT (widget), "action-status");
  
  if (data) 
    {
      g_object_get (data->action, "tooltip", &tooltip, NULL);
      
      gtk_statusbar_push (GTK_STATUSBAR (data->statusbar), 0, 
			  tooltip ? tooltip : "");
      
      g_free (tooltip);
    }
}

static void
unset_tip (GtkWidget *widget)
{
  ActionStatus *data;

  data = g_object_get_data (G_OBJECT (widget), "action-status");

  if (data)
    gtk_statusbar_pop (GTK_STATUSBAR (data->statusbar), 0);
}
		    
static void
connect_proxy (GtkUIManager *merge,
	       GtkAction    *action,
	       GtkWidget    *proxy,
	       GtkWidget    *statusbar)
{
  if (GTK_IS_MENU_ITEM (proxy)) 
    {
      ActionStatus *data;

      data = g_object_get_data (G_OBJECT (proxy), "action-status");
      if (data)
	{
	  g_object_unref (data->action);
	  g_object_unref (data->statusbar);

	  data->action = g_object_ref (action);
	  data->statusbar = g_object_ref (statusbar);
	}
      else
	{
	  data = g_new0 (ActionStatus, 1);

	  data->action = g_object_ref (action);
	  data->statusbar = g_object_ref (statusbar);

	  g_object_set_data_full (G_OBJECT (proxy), "action-status", 
				  data, action_status_destroy);
	  
	  g_signal_connect (proxy, "select",  G_CALLBACK (set_tip), NULL);
	  g_signal_connect (proxy, "deselect", G_CALLBACK (unset_tip), NULL);
	}
    }
}
/* CS: end copied code from gtk+/test/testmerge.c */

static void
gnc_main_window_setup_window (GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GtkWidget *main_vbox;
	guint merge_id;
	GncPluginManager *manager;
	GList *plugins;
	GError *error = NULL;
	gchar *filename;

	ENTER(" ");

	/* Catch window manager delete signal */
	g_signal_connect (G_OBJECT (window), "delete-event",
			  G_CALLBACK (gnc_main_window_delete_event), window);

	/* Create widgets and add them to the window */
	main_vbox = gtk_vbox_new (FALSE, 0);
	gtk_widget_show (main_vbox);
	gtk_container_add (GTK_CONTAINER (window), main_vbox);

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	priv->menu_dock = gtk_vbox_new (FALSE, 0);
	gtk_widget_show (priv->menu_dock);
	gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
			    FALSE, TRUE, 0);

	priv->notebook = gtk_notebook_new ();
	g_object_set(G_OBJECT(priv->notebook),
			      "scrollable", TRUE,
			      "enable-popup", TRUE,
			      (char *)NULL);
	gtk_widget_show (priv->notebook);
	g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
			  G_CALLBACK (gnc_main_window_switch_page), window);
#ifdef HAVE_GTK_2_10
	g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
			  G_CALLBACK (gnc_main_window_page_reordered), window);
#endif
	gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
			    TRUE, TRUE, 0);

	priv->statusbar = gtk_statusbar_new ();
	gtk_widget_show (priv->statusbar);
	gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
	                           FALSE, TRUE, 0);
        gtk_statusbar_set_has_resize_grip( GTK_STATUSBAR(priv->statusbar), TRUE );

	priv->progressbar = gtk_progress_bar_new ();
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
	gtk_widget_show (priv->progressbar);
	gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
			    FALSE, TRUE, 0);
	gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
					0.01);

	window->ui_merge = gtk_ui_manager_new ();

	/* Create menu and toolbar information */
	priv->action_group = gtk_action_group_new ("MainWindowActions");
	gnc_gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE);
	gtk_action_group_add_actions (priv->action_group, gnc_menu_actions,
                                  gnc_menu_n_actions, window);
	gtk_action_group_add_toggle_actions (priv->action_group, 
                                         toggle_actions, n_toggle_actions, 
                                         window);
	gtk_action_group_add_radio_actions (priv->action_group,
                                        radio_entries, n_radio_entries,
                                        0,
                                        G_CALLBACK(gnc_main_window_cmd_window_raise),
                                        window);
	gnc_plugin_update_actions(priv->action_group,
                              initially_insensitive_actions,
                              "sensitive", FALSE);
	gnc_plugin_update_actions(priv->action_group,
                              always_insensitive_actions,
                              "sensitive", FALSE);
	gnc_plugin_update_actions(priv->action_group,
                              always_hidden_actions,
                              "visible", FALSE);
	gnc_plugin_set_important_actions (priv->action_group,
                                      gnc_menu_important_actions);
	gtk_ui_manager_insert_action_group (window->ui_merge, priv->action_group, 0);

	g_signal_connect (G_OBJECT (window->ui_merge), "add_widget",
			  G_CALLBACK (gnc_main_window_add_widget), window);
	/* Use the "connect-proxy" signal for tooltip display in the
	   status bar */
	g_signal_connect (G_OBJECT (window->ui_merge), "connect-proxy",
                      G_CALLBACK (connect_proxy), priv->statusbar);

	filename = gnc_gnome_locate_ui_file("gnc-main-window-ui.xml");

	/* Can't do much without a ui. */
	g_assert (filename);

	merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge,
						    filename, &error);
	g_assert(merge_id || error);
	if (merge_id) {
	  gtk_window_add_accel_group (GTK_WINDOW (window),
                                  gtk_ui_manager_get_accel_group(window->ui_merge));
	  gtk_ui_manager_ensure_update (window->ui_merge);
	} else {
	  g_critical("Failed to load ui file.\n  Filename %s\n  Error %s",
                 filename, error->message);
	  g_error_free(error);
	  g_assert(merge_id != 0);
	}
	g_free(filename);

	gnc_gconf_add_notification(G_OBJECT(window), GCONF_GENERAL,
                               gnc_main_window_gconf_changed,
                               GNC_MAIN_WINDOW_NAME);
	gnc_gconf_add_notification(G_OBJECT(window), DESKTOP_GNOME_INTERFACE,
                               gnc_main_window_gconf_changed,
                               GNC_MAIN_WINDOW_NAME);
	gnc_main_window_update_toolbar(window);
	gnc_main_window_update_tab_position(window);

	gnc_main_window_init_menu_updaters(window);

    /* Testing */
	/* Now update the "eXtensions" menu */
	if (!gnc_is_extra_enabled()) {
	  GtkAction*  action;

	  action = gtk_action_group_get_action(priv->action_group, 
                                           "ExtensionsAction");
	  gtk_action_set_visible(action, FALSE);
	}

	/* GncPluginManager stuff */
	manager = gnc_plugin_manager_get ();
	plugins = gnc_plugin_manager_get_plugins (manager);
    g_list_foreach (plugins, gnc_main_window_add_plugin, window);
    g_list_free (plugins);

	g_signal_connect (G_OBJECT (manager), "plugin-added",
                      G_CALLBACK (gnc_main_window_plugin_added), window);
	g_signal_connect (G_OBJECT (manager), "plugin-removed",
                      G_CALLBACK (gnc_main_window_plugin_removed), window);

	LEAVE(" ");
}

/* Callbacks */
static void
gnc_main_window_add_widget (GtkUIManager *merge,
			    GtkWidget *widget,
			    GncMainWindow *window)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (GTK_IS_TOOLBAR (widget)) {
		priv->toolbar = widget;
	}

	gtk_box_pack_start (GTK_BOX (priv->menu_dock), widget, FALSE, FALSE, 0);
	gtk_widget_show (widget);
}

/** Should a summary bar be visible in this window?  In order to
 *  prevent synchronization issues, the "ViewSummaryBar"
 *  GtkToggleAction is the sole source of information for whether or
 *  not any summary bar should be visible in a window.
 *
 *  @param window A pointer to the window in question.
 *
 *  @param action If known, a pointer to the "ViewSummaryBar"
 *  GtkToggleAction.  If NULL, the function will look up this action.
 *
 *  @return TRUE if the summarybar should be visible.
 */
static gboolean
gnc_main_window_show_summarybar (GncMainWindow *window, GtkAction *action)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (action == NULL)
	  action = gtk_action_group_get_action(priv->action_group,
					       "ViewSummaryAction");
	if (action == NULL)
	  return TRUE;
	return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
}

/** This function is invoked when the GtkNotebook switches pages.  It
 *  is responsible for updating the rest of the window contents
 *  outside of the notebook.  I.E. Updating the user interface, the
 *  summary bar, etc.  This function also emits the "page_changed"
 *  signal from the window so that any plugin can also learn about the
 *  fact that the page has changed.
 *
 *  @internal
 */
static void
gnc_main_window_switch_page (GtkNotebook *notebook,
			     GtkNotebookPage *notebook_page,
			     gint pos,
			     GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GtkWidget *child;
	GncPluginPage *page;
	gboolean immutable, visible;

	ENTER("Notebook %p, page, %p, index %d, window %p",
	       notebook, notebook_page, pos, window);
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->current_page != NULL) {
		page = priv->current_page;
		gnc_plugin_page_unmerge_actions (page, window->ui_merge);
		gnc_plugin_page_unselected (page);
	}

	child = gtk_notebook_get_nth_page (notebook, pos);
	if (child) {
		page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
	} else {
		page = NULL;
	}

	priv->current_page = page;

	if (page != NULL) {
		/* Update the user interface (e.g. menus and toolbars */
		gnc_plugin_page_merge_actions (page, window->ui_merge);
		visible = gnc_main_window_show_summarybar(window, NULL);
		gnc_plugin_page_show_summarybar (page, visible);

		/* Allow page specific actions */
		gnc_plugin_page_selected (page);
		gnc_window_update_status (GNC_WINDOW(window), page);

		/* Update the page reference info */
		priv->usage_order = g_list_remove (priv->usage_order, page);
		priv->usage_order = g_list_prepend (priv->usage_order, page);
	}

	/* Update the menus based upon whether this is an "immutable" page. */
	immutable = page &&
	  g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
	gnc_plugin_update_actions(priv->action_group,
				  immutable_page_actions,
				  "sensitive", !immutable);
	gnc_plugin_update_actions(priv->action_group,
				  multiple_page_actions,
				  "sensitive",
				  g_list_length(priv->installed_pages) > 1);

	gnc_main_window_update_title(window);
	gnc_main_window_update_menu_item(window);

	g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
	LEAVE(" ");
}

#ifdef HAVE_GTK_2_10
/** This function is invoked when a GtkNotebook tab gets reordered by
 *  drag and drop. It adjusts the list installed_pages to reflect the new
 *  ordering so that GnuCash saves and restores the tabs correctly.
 *
 *  @internal
 */
static void
gnc_main_window_page_reordered (GtkNotebook *notebook,
				GtkWidget *child,
				guint pos,
				GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GncPluginPage *page;
	GList *old_link;

	ENTER("Notebook %p, child %p, index %d, window %p",
	       notebook, child, pos, window);
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));

	if (!child) return;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);

	page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
	if (!page) return;

	old_link = g_list_find (priv->installed_pages, page);
	if (!old_link) return;

	priv->installed_pages = g_list_delete_link (priv->installed_pages,
						    old_link);
	priv->installed_pages = g_list_insert (priv->installed_pages,
					       page, pos);

	LEAVE(" ");
}
#endif

static void
gnc_main_window_plugin_added (GncPlugin *manager,
			      GncPlugin *plugin,
			      GncMainWindow *window)
{
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (GNC_IS_PLUGIN (plugin));

	gnc_plugin_add_to_window (plugin, window, window_type);
}

static void
gnc_main_window_plugin_removed (GncPlugin *manager,
				GncPlugin *plugin,
				GncMainWindow *window)
{
	g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
	g_return_if_fail (GNC_IS_PLUGIN (plugin));

	gnc_plugin_remove_from_window (plugin, window, window_type);
}


/* Command callbacks */
static void
gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window)
{
  SCM func = scm_c_eval_string("gnc:main-window-properties-cb");
  if (!SCM_PROCEDUREP (func)) {
      PERR ("not a procedure\n");
      return;
  }
  scm_call_0(func);
}

static void
gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GncPluginPage *page;

	g_return_if_fail(GNC_IS_MAIN_WINDOW(window));

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	page = priv->current_page;
	gnc_main_window_close_page(page);
}

static void
gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window)
{
    if (!gnc_main_window_all_finish_pending())
        return;

    gnc_main_window_quit(window);
}

static void
gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window)
{
  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
  GtkTextBuffer *text_buffer;
  GtkClipboard *clipboard;
  gboolean editable;

  if (GTK_IS_EDITABLE (widget)) {
    gtk_editable_cut_clipboard (GTK_EDITABLE (widget));
  } else if (GTK_IS_TEXT_VIEW (widget)) {
    text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
    clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
					  GDK_SELECTION_CLIPBOARD);
    editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (widget));
    gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
  }
}

static void
gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window)
{
  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
  GtkTextBuffer *text_buffer;
  GtkClipboard *clipboard;

  if (GTK_IS_EDITABLE (widget)) {
    gtk_editable_copy_clipboard (GTK_EDITABLE (widget));
  } else if (GTK_IS_TEXT_VIEW (widget)) {
    text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
    clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
					  GDK_SELECTION_CLIPBOARD);
    gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
  }
}

static void
gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window)
{
  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
  GtkTextBuffer *text_buffer;
  GtkClipboard *clipboard;
  gboolean editable;

  if (GTK_IS_EDITABLE (widget)) {
    gtk_editable_paste_clipboard (GTK_EDITABLE (widget));
  } else if (GTK_IS_TEXT_VIEW (widget)) {
    text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
    clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
					  GDK_SELECTION_CLIPBOARD);
    editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (widget));
    gtk_text_buffer_paste_clipboard (text_buffer, clipboard, NULL, FALSE);
  }
}

static void
gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window)
{
	gnc_preferences_dialog ();
}

static void
gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window)
{
}

static void
gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window)
{
  gnc_reset_warnings_dialog(GTK_WIDGET(window));
}

static void
gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window)
{
  GncMainWindowPrivate *priv;
  GncPluginPage *page;
  GtkWidget *tab_hbox, *widget, *label = NULL, *entry = NULL;
  GList *children, *tmp;

  ENTER(" ");
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  page = priv->current_page;
  if (!page) {
    LEAVE("No current page");
    return;
  }
  
  tab_hbox = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
                                       page->notebook_page);
  children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
  for (tmp = children; tmp; tmp = g_list_next(tmp)) {
    widget = tmp->data;
    if (GTK_IS_LABEL(widget)) {
      label = widget;
    } else if (GTK_IS_ENTRY(widget)) {
      entry = widget;
    }
  }
  g_list_free(children);

  if (!label || !entry) {
    LEAVE("Missing label or entry.");
    return;
  }

  gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
  gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
  gtk_widget_hide(label);
  gtk_widget_show(entry);
  gtk_widget_grab_focus(entry);
  LEAVE("opened for editing");
}

static void
gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action))) {
		gtk_widget_show (priv->toolbar);
	} else {
		gtk_widget_hide (priv->toolbar);
	}
}

static void
gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window)
{
	GncMainWindowPrivate *priv;
	GList *item;
	gboolean visible;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	visible = gnc_main_window_show_summarybar(window, action);
	for (item = priv->installed_pages; item; item = g_list_next(item)) {
	  gnc_plugin_page_show_summarybar(item->data, visible);
	}
}

static void
gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window)
{
	GncMainWindowPrivate *priv;

	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action))) {
		gtk_widget_show (priv->statusbar);
	} else {
		gtk_widget_hide (priv->statusbar);
	}
}

static void
gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window)
{
	GncMainWindow *new_window;

	/* Create the new window */
	ENTER(" ");
	new_window = gnc_main_window_new ();
	gtk_widget_show(GTK_WIDGET(new_window));
	LEAVE(" ");
}

static void
gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window)
{
	GncMainWindowPrivate *priv, *new_priv;
	GncMainWindow *new_window;
	GncPluginPage *page;
	GtkNotebook *notebook;
	GtkWidget *tab_widget, *menu_widget;

	/* Setup */
	priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
	if (priv->current_page == NULL)
		return;
	notebook = GTK_NOTEBOOK (priv->notebook);
	page = priv->current_page;
	tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
	menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);

	/* Ref the page components, then remove it from its old window */
	g_object_ref(page);
	g_object_ref(tab_widget);
	g_object_ref(menu_widget);
	g_object_ref(page->notebook_page);
	gnc_main_window_disconnect(window, page);

	/* Create the new window */
	new_window = gnc_main_window_new ();
	gtk_widget_show(GTK_WIDGET(new_window));

	/* Now add the page to the new window */
	gnc_main_window_connect (new_window, page, tab_widget, menu_widget);

	/* Unref the page components now that we're done */
	g_object_unref(page->notebook_page);
	g_object_unref(menu_widget);
	g_object_unref(tab_widget);
	g_object_unref(page);

	/* just a little debugging. :-) */
	new_priv = GNC_MAIN_WINDOW_GET_PRIVATE(new_window);
	DEBUG("Moved page %p from window %p to new window %p",
	      page, window, new_window);
	DEBUG("Old window current is %p, new window current is %p",
	      priv->current_page, priv->current_page);
}

static void
gnc_main_window_cmd_window_raise (GtkAction *action,
				  GtkRadioAction *current,
				  GncMainWindow *old_window)
{
	GncMainWindow *new_window;
	gint value;

	g_return_if_fail(GTK_IS_ACTION(action));
	g_return_if_fail(GTK_IS_RADIO_ACTION(current));
	g_return_if_fail(GNC_IS_MAIN_WINDOW(old_window));
	
	ENTER("action %p, current %p, window %p", action, current, old_window);
	value = gtk_radio_action_get_current_value(current);
	new_window = g_list_nth_data(active_windows, value);
	gtk_window_present(GTK_WINDOW(new_window));

	/* revert the change in the radio group
     * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
	g_idle_add((GSourceFunc)gnc_main_window_update_radio_button, old_window);
	LEAVE(" ");
}

static void
gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window)
{
	gnc_gnome_help (HF_GUIDE, NULL);
}

static void
gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window)
{
	gnc_gnome_help (HF_HELP, NULL);
}

/** This is a helper function to find a data file and suck it into
 *  memory.
 *
 *  @param partial The name of the file relative to the gnucash
 *  specific shared data directory.
 *
 *  @return The text of the file or NULL. The caller is responsible
 *  for freeing this string.
 */
static gchar *
get_file (const gchar *partial)
{
  gchar *filename, *text = NULL;

  filename = gnc_gnome_locate_data_file(partial);
  g_file_get_contents(filename, &text, NULL, NULL);
  g_free(filename);

  /* Anything there? */
  if (text && *text)
    return text;

  /* Just a empty string or no string at all. */
  if (text)
    g_free(text);
  return NULL;
}


/** This is a helper function to find a data file, suck it into
 *  memory, and split it into an array of strings.
 *
 *  @param partial The name of the file relative to the gnucash
 *  specific shared data directory.
 *
 *  @return The text of the file as an array of strings, or NULL. The
 *  caller is responsible for freeing all the strings and the array.
 */
static gchar **
get_file_strsplit (const gchar *partial)
{
  gchar *text, **lines;

  text = get_file(partial);
  if (!text)
    return NULL;

  lines = g_strsplit_set(text, "\r\n", -1);
  g_free(text);
  return lines;
}


/** Create and display the "about" dialog for gnucash.
 *
 *  @param action The GtkAction for the "about" menu item.
 *
 *  @param window The main window whose menu item was activated.
 */
static void
gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
{
	const gchar *fixed_message = _("The GnuCash personal finance manager. "
				       "The GNU way to manage your money!");
	const gchar *copyright = "© 1997-2006 Contributors";
	gchar **authors, **documenters, *license, *message;
	GdkPixbuf *logo;

	logo = gnc_gnome_get_gdkpixbuf ("appicon.png");

	authors = get_file_strsplit("doc/AUTHORS");
	documenters = get_file_strsplit("doc/DOCUMENTERS");
	license = get_file("doc/LICENSE");
#ifdef GNUCASH_SVN
	/* Development version */
	message = g_strdup_printf(_("%s  This copy was built from svn r%s on %s."),
				  fixed_message, GNUCASH_SVN_REV, GNUCASH_BUILD_DATE);
#else
	message = g_strdup_printf(_("%s  This copy was built from r%s on %s."),
				  fixed_message, GNUCASH_SVN_REV, GNUCASH_BUILD_DATE);
#endif
	gtk_show_about_dialog
		     (GTK_WINDOW (window),
		      "authors", authors,
		      "documenters", documenters,
		      "comments", message,
		      "copyright", copyright,
		      "license", license,
		      "logo", logo,
		      "name", "GnuCash",
		      "translator-credits", _("translator_credits"),
		      "version", VERSION,
		      "website", "http://www.gnucash.org",
		      (gchar *)NULL);

	g_free(message);
	if (license)     g_free(license);
	if (documenters) g_strfreev(documenters);
	if (authors)     g_strfreev(authors);
	g_object_unref (logo);
}


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

/** Get a pointer to the first active top level window or NULL
 *  if there is none.
 *
 *  @return A pointer to a GtkWindow object. */
gncUIWidget
gnc_ui_get_toplevel (void)
{
  GList *window;

  for (window=active_windows; window; window=window->next)
    if (gtk_window_is_active (GTK_WINDOW (window->data)))
      return window->data;

  return NULL;
}


/** Retrieve the gtk window associated with a main window object.
 *  This function is called via a vector off a generic window
 *  interface.
 *
 *  @param window_in A pointer to a generic window. */
static GtkWindow *
gnc_main_window_get_gtk_window (GncWindow *window)
{
  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
  return GTK_WINDOW(window);
}


/** Retrieve the status bar associated with a main window object.
 *  This function is called via a vector off a generic window
 *  interface.
 *
 *  @param window_in A pointer to a generic window. */
static GtkWidget *
gnc_main_window_get_statusbar (GncWindow *window_in)
{
  GncMainWindowPrivate *priv;
  GncMainWindow *window;

  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);

  window = GNC_MAIN_WINDOW(window_in);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  return priv->statusbar;
}


/** Retrieve the progress bar associated with a main window object.
 *  This function is called via a vector off a generic window
 *  interface.
 *
 *  @param window_in A pointer to a generic window. */
static GtkWidget *
gnc_main_window_get_progressbar (GncWindow *window_in)
{
  GncMainWindowPrivate *priv;
  GncMainWindow *window;

  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);

  window = GNC_MAIN_WINDOW(window_in);
  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
  return priv->progressbar;
}


static void
gnc_main_window_all_ui_set_sensitive (GncWindow *unused, gboolean sensitive)
{
	GncMainWindow *window;
	GncMainWindowPrivate *priv;
	GList *groupp, *groups, *winp, *tmp;
	GtkWidget *close_button;

	for (winp = active_windows; winp; winp = g_list_next(winp)) {
	  window = winp->data;
	  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);

	  groups = gtk_ui_manager_get_action_groups(window->ui_merge);
	  for (groupp = groups; groupp; groupp = g_list_next(groupp)) {
	    gtk_action_group_set_sensitive(GTK_ACTION_GROUP(groupp->data), sensitive);
	  }

	  for (tmp = priv->installed_pages; tmp; tmp = g_list_next(tmp)) {
	    close_button = g_object_get_data(tmp->data, PLUGIN_PAGE_CLOSE_BUTTON);
	    if (!close_button)
	      continue;
	    gtk_widget_set_sensitive (close_button, sensitive);
	  }
	}
}


/** Initialize the generic window interface for a main window.
 *
 *  @param iface A pointer to the interface data structure to
 *  populate. */
static void
gnc_window_main_window_init (GncWindowIface *iface)
{
	iface->get_gtk_window  = gnc_main_window_get_gtk_window;
	iface->get_statusbar   = gnc_main_window_get_statusbar;
	iface->get_progressbar = gnc_main_window_get_progressbar;
	iface->ui_set_sensitive = gnc_main_window_all_ui_set_sensitive;
}


/*  Set the window where all progressbar updates should occur.  This
 *  is a wrapper around the gnc_window_set_progressbar_window()
 *  function.
 */
void
gnc_main_window_set_progressbar_window (GncMainWindow *window)
{
  GncWindow *gncwin;
  gncwin = GNC_WINDOW(window);
  gnc_window_set_progressbar_window(gncwin);
}


/** Popup a contextual menu.  This function ends up being called when
 *  the user right-clicks in the context of a window, or uses the
 *  keyboard context-menu request key combination (Shift-F10 by
 *  default).
 *
 *  @param page This is the GncPluginPage corresponding to the visible
 *  page.
 *
 *  @param event The event parameter passed to the "button-press"
 *  callback.  May be null if there was no event (aka keyboard
 *  request).
 */
static void
do_popup_menu(GncPluginPage *page, GdkEventButton *event)
{
  GtkUIManager *ui_merge;
  GtkWidget *menu;
  int button, event_time;

  g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));

  ENTER("page %p, event %p", page, event);
  ui_merge = gnc_plugin_page_get_ui_merge(page);
  if (ui_merge == NULL) {
    LEAVE("no ui merge");
    return;
  }

  menu = gtk_ui_manager_get_widget(ui_merge, "/MainPopup");
  if (!menu) {
    LEAVE("no menu");
    return;
  }

  if (event) {
    button = event->button;
    event_time = event->time;
  } else {
    button = 0;
    event_time = gtk_get_current_event_time ();
  }

  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, event_time);
  LEAVE(" ");
}


/** Callback function invoked when the user requests that Gnucash
 *  popup the contextual menu via the keyboard context-menu request
 *  key combination (Shift-F10 by default).
 *
 *  @param page This is the GncPluginPage corresponding to the visible
 *  page.
 *
 *  @param widget Whatever widget had focus when the user issued the
 *  keyboard context-menu request.
 *
 *  @return Always returns TRUE to indicate that the menu request was
 *  handled.
 */
static gboolean
gnc_main_window_popup_menu_cb (GtkWidget *widget,
			       GncPluginPage *page)
{
  ENTER("widget %p, page %p", widget, page);
  do_popup_menu(page, NULL);
  LEAVE(" ");
  return TRUE;
}


/*  Callback function invoked when the user clicks in the content of
 *  any Gnucash window.  If this was a "right-click" then Gnucash will
 *  popup the contextual menu.
 */
gboolean
gnc_main_window_button_press_cb (GtkWidget *whatever,
				 GdkEventButton *event,
				 GncPluginPage *page)
{
  g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);

  ENTER("widget %p, event %p, page %p", whatever, event, page);
  /* Ignore double-clicks and triple-clicks */
  if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
    do_popup_menu(page, event);
    LEAVE("menu shown");
    return TRUE;
  }

  LEAVE("other click");
  return FALSE;
}


/* CS: Code copied from gtk/gtkactiongroup.c */
static gchar *
dgettext_swapped (const gchar *msgid, 
		  const gchar *domainname)
{
  /* CS: Pass this through dgettext if and only if msgid is
     nonempty. */
  return (msgid && *msgid) ? dgettext (domainname, msgid) : (gchar*) msgid;
}

/*
 * This is copied into GnuCash from Gtk in order to fix problems when
 * empty msgids were passed through gettext(). 
 *
 * See http://bugzilla.gnome.org/show_bug.cgi?id=326200 . If that bug
 * is fixed in the gtk that we can rely open, then
 * gnc_gtk_action_group_set_translation_domain can be replaced by
 * gtk_action_group_set_translation_domain again.
 */
void 
gnc_gtk_action_group_set_translation_domain (GtkActionGroup *action_group,
					 const gchar    *domain)
{
  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));

  gtk_action_group_set_translate_func (action_group, 
				       (GtkTranslateFunc)dgettext_swapped,
				       g_strdup (domain),
				       g_free);
} 
/* CS: End of code copied from gtk/gtkactiongroup.c */

void
gnc_main_window_all_action_set_sensitive (const gchar *action_name,
					  gboolean sensitive)
{
	GList *tmp;
	GtkAction *action;

	for (tmp = active_windows; tmp; tmp = g_list_next(tmp)) {
	  action = gnc_main_window_find_action (tmp->data, action_name);
	  gtk_action_set_sensitive (action, sensitive);
	}
}

/** @} */
/** @} */
