diff --git "a/\"src/gnome-utils/gnc-main-window.c100644 01901c6c8752987f426cff1e6534ba0b27867fae\\tsrc/gnome-utils/ui/Makefile.am100644 94c76a9e4c6a8f6ceaacfff0c1ed4c9e3d4128e1\\tsrc/gnome-utils/ui/gnc-main-window-ui.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui-quartz.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui.xml\"" "b/\"src/gnome-utils/gnc-main-window.c100644 01901c6c8752987f426cff1e6534ba0b27867fae\\tsrc/gnome-utils/ui/Makefile.am100644 94c76a9e4c6a8f6ceaacfff0c1ed4c9e3d4128e1\\tsrc/gnome-utils/ui/gnc-main-window-ui.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui-quartz.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui.xml\"" new file mode 100644 index 0000000000..dbfdae9089 --- /dev/null +++ "b/\"src/gnome-utils/gnc-main-window.c100644 01901c6c8752987f426cff1e6534ba0b27867fae\\tsrc/gnome-utils/ui/Makefile.am100644 94c76a9e4c6a8f6ceaacfff0c1ed4c9e3d4128e1\\tsrc/gnome-utils/ui/gnc-main-window-ui.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui-quartz.xml0 0000000000000000000000000000000000000000\\tsrc/gnome-utils/ui/gnc-windows-menu-ui.xml\"" @@ -0,0 +1,4349 @@ +/* + * gnc-main-window.c -- GtkWindow which represents the + * GnuCash main window. + * + * Copyright (C) 2003 Jan Arne Petersen + * Copyright (C) 2003,2005,2006 David Hampton + * + * 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 + @author Copyright (C) 2003,2005,2006 David Hampton +*/ +#include "config.h" + +#include +#include +#include +#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-uri-utils.h" +#include "core-utils/gnc-version.h" +#include "gnc-window.h" +#include "gnc-main.h" +#include "gnc-gconf-utils.h" +// +JSLED +//#include "gnc-html.h" +#include "gnc-autosave.h" +#include "print-session.h" +#ifdef MAC_INTEGRATION +#include +#endif + +/** 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 PLUGIN_PAGE_TAB_LABEL "label" + +#define KEY_SHOW_CLOSE_BUTTON "tab_close_buttons" +#define KEY_TAB_NEXT_RECENT "tab_next_recent" +#define KEY_TAB_POSITION "tab_position" +#define KEY_TAB_WIDTH "tab_width" + +#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); +static void gnc_main_window_update_all_menu_items (void); + +/* 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); +static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window); +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); +static void gnc_main_window_engine_commit_error_callback( gpointer data, QofBackendError errcode ); + +/* Command callbacks */ +static void gnc_main_window_cmd_page_setup (GtkAction *action, GncMainWindow *window); +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); + +#ifdef MAC_INTEGRATION +static void gnc_quartz_shutdown(GtkOSXApplication *theApp, gpointer data); +static gboolean gnc_quartz_should_quit(GtkOSXApplication *theApp, GncMainWindow *window); +static void gnc_quartz_set_menu(GncMainWindow* window); +#endif + +/** 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 */ + + { "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL }, + { "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL }, + { + "FilePrintAction", GTK_STOCK_PRINT, N_("_Print..."), "p", + N_("Print the currently active page"), NULL + }, +#ifndef GTK_STOCK_PAGE_SETUP +# define GTK_STOCK_PAGE_SETUP NULL +#endif + { + "FilePageSetupAction", GTK_STOCK_PAGE_SETUP, N_("Pa_ge Setup..."), "p", + N_("Specify the page size and orientation for printing"), + G_CALLBACK (gnc_main_window_cmd_page_setup) + }, + { + "FilePropertiesAction", GTK_STOCK_PROPERTIES, N_("Proper_ties"), "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"), "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 messages 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 +}; + + +/* This data structure holds the tooltops for all notebook tabs. + * Typically these are used to provide the full path of a register + * page. */ +static GtkTooltips *tips = NULL; + +/************************************************************ + * * + ************************************************************/ +#define WINDOW_COUNT "WindowCount" +#define WINDOW_STRING "Window %d" +#define WINDOW_GEOMETRY "WindowGeometry" +#define WINDOW_POSITION "WindowPosition" +#define WINDOW_MAXIMIZED "WindowMaximized" +#define TOOLBAR_VISIBLE "ToolbarVisible" +#define STATUSBAR_VISIBLE "StatusbarVisible" +#define SUMMARYBAR_VISIBLE "SummarybarVisible" +#define WINDOW_FIRSTPAGE "FirstPage" +#define WINDOW_PAGECOUNT "PageCount" +#define WINDOW_PAGEORDER "PageOrder" +#define PAGE_TYPE "PageType" +#define PAGE_NAME "PageName" +#define PAGE_STRING "Page %d" + +typedef struct +{ + GKeyFile *key_file; + const gchar *group_name; + gint window_num; + gint page_num; + gint page_offset; +} GncMainWindowSaveData; + + +/* Iterator function to walk all pages in all windows, calling the + * specified function for each page. */ +void +gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data) +{ + GncMainWindowPrivate *priv; + GncMainWindow *window; + GncPluginPage *page; + GList *w, *p; + + ENTER(" "); + for (w = active_windows; w; w = g_list_next(w)) + { + window = w->data; + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + for (p = priv->installed_pages; p; p = g_list_next(p)) + { + page = p->data; + fn(page, user_data); + } + } + LEAVE(" "); +} + + +/** 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_plugin_page_set_use_new_window(page, FALSE); + 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, "ViewAccountTreeAction"); + 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_mins = + _("If you don't save, changes from the past %d minutes will be discarded."); + const gchar *message_hours = + _("If you don't save, changes from the past %d hours and %d minutes will be discarded."); + const gchar *message_days = + _("If you don't save, changes from the past %d days and %d hours will be discarded."); + time_t oldest_change; + gint minutes, hours, days; + + session = gnc_get_current_session(); + book = qof_session_get_book(session); + filename = qof_session_get_url(session); + if (filename == NULL) + filename = _(""); + if ((tmp = strrchr(filename, '/')) != NULL) + filename = tmp + 1; + + /* Remove any pending auto-save timeouts */ + gnc_autosave_remove_timer(book); + + 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; + hours = minutes / 60; + minutes = minutes % 60; + days = hours / 24; + hours = hours % 24; + if (days > 0) + { + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + message_days, days, hours); + } + else if (hours > 0) + { + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + message_hours, hours, minutes); + } + else + { + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + message_mins, 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 (QofInstance *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. + * + * As a side-effect, the save action is set sensitive iff the book + * is dirty. + * + * @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; + gchar *filename = NULL; + const gchar *book_id = NULL; + const gchar *dirty = ""; + gchar *title; + GtkAction* action; + + /* The save action is sensitive if the book is dirty */ + action = gnc_main_window_find_action (window, "FileSaveAction"); + if (action != NULL) + { + gtk_action_set_sensitive(action, FALSE); + } + if (gnc_current_session_exist()) + { + book_id = qof_session_get_url (gnc_get_current_session ()); + book = gnc_get_current_book(); + if (qof_instance_is_dirty(QOF_INSTANCE(book))) + { + dirty = "*"; + if (action != NULL) + { + gtk_action_set_sensitive(action, TRUE); + } + } + } + + if (!book_id) + filename = g_strdup(_("Unsaved Book")); + else + { + if ( gnc_uri_is_file_uri ( book_id ) ) + { + /* The filename is a true file. + * The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */ + gchar *path = gnc_uri_get_path ( book_id ); + filename = g_path_get_basename ( path ); + g_free ( path ); + } + else + { + /* The filename is composed of database connection parameters. + * For this we will show access_method://username@database[:port] */ + filename = gnc_uri_normalize_uri (book_id, FALSE); + } + } + + 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) + * but several developers prefer to use it anyway. */ + title = g_strdup_printf("%s%s - %s - GnuCash", dirty, filename, + gnc_plugin_page_get_page_name(page)); + } + else + { + title = g_strdup_printf("%s%s - GnuCash", dirty, filename); + } + g_free( 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(); + + /* Auto-save feature */ + gnc_autosave_dirty_handler(book, dirty); +} + +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); + gnc_main_window_update_all_titles(); +#ifndef MAC_INTEGRATION + gnc_main_window_update_all_menu_items(); +#endif +} + + +/** 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); +#ifdef MAC_INTEGRATION + { + GtkOSXApplication *theApp = + g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); + gtk_osxapplication_sync_menubar(theApp); + } +#endif + 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)); + if (action_list) + { + 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); + g_free(title); + 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(""); +#ifndef MAC_INTEGRATION + /* 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); + } +#endif + 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_tab_close_one_page (GncPluginPage *page, + gpointer user_data) +{ + gboolean *new_value = user_data; + 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 all windows. This function + * calls gnc_main_window_update_tab_close() for each plugin page 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_tab_close (GConfEntry *entry, gpointer user_data) +{ + gboolean new_value; + + ENTER(" "); + new_value = gconf_value_get_bool(entry->value); + gnc_main_window_foreach_page( + gnc_main_window_update_tab_close_one_page, + &new_value); + LEAVE(" "); +} + + +/** Update the width of the label in the tab of a notebook page. This + * function adjusts both the width and the ellipsize mode so that the tab + * label looks correct. The special check for a zero value handles the + * case where a user hasn't set a tab width and the gconf default isn't + * detected. + * + * @internal + * + * @param page The GncPluginPage whose notebook tab should be updated. + * + * @param new_value The new width of the label in the tab. + */ +static void +gnc_main_window_update_tab_width_one_page (GncPluginPage *page, + gpointer user_data) +{ + gint *new_value = user_data; + GtkWidget *label; + + ENTER("page %p, visible %d", page, *new_value); + label = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL); + if (!label) + { + LEAVE("no label"); + return; + } + + if (*new_value != 0) + { + gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_max_width_chars(GTK_LABEL(label), *new_value); + } + else + { + gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE); + gtk_label_set_max_width_chars(GTK_LABEL(label), 100); + } + LEAVE(" "); +} + + +/** Update the tab label width in all pages in all windows. This function + * calls gnc_main_window_update_tab_width() for each plugin page in the + * application. + * + * @internal + * + * @param entry A pointer to the GConfEntry which describes the new + * size of the tab label width. + * + * @param user_data Unused. + */ +static void +gnc_main_window_update_tab_width (GConfEntry *entry, gpointer user_data) +{ + gint new_value; + + ENTER(" "); + new_value = gconf_value_get_float(entry->value); + gnc_main_window_foreach_page( + gnc_main_window_update_tab_width_one_page, + &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, *event_box; + 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; + + if (!page->notebook_page) + { + LEAVE("invalid notebook_page"); + return FALSE; + } + + event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), + page->notebook_page); + + tab_hbox = gtk_bin_get_child(GTK_BIN(event_box)); + + 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); +} + +static gboolean +main_window_find_tab_event (GncMainWindow *window, + GncPluginPage *page, + GtkWidget **event_p) +{ + GncMainWindowPrivate *priv; + GtkWidget *event_box; + + ENTER("window %p, page %p, event %p", + window, page, event_p); + *event_p = NULL; + + if (!page->notebook_page) + { + LEAVE("invalid notebook_page"); + return FALSE; + } + + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), + page->notebook_page); + if (GTK_IS_EVENT_BOX(event_box)) + { + *event_p = event_box; + LEAVE("event %p", *event_p); + return (TRUE); + } + + LEAVE("event %p", *event_p); + return (FALSE); +} + +void +main_window_update_page_name (GncPluginPage *page, + const gchar *name_in) +{ + GncMainWindow *window; + GncMainWindowPrivate *priv; + GtkWidget *label, *entry, *event_box; + gchar *name, *old_page_name, *old_page_long_name; + + ENTER(" "); + + if ((name_in == NULL) || (*name_in == '\0')) + { + LEAVE("no string"); + return; + } + name = g_strstrip(g_strdup(name_in)); + + /* Optimization, if the name hasn't changed, don't update X. */ + if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page))) + { + g_free(name); + LEAVE("empty string or name unchanged"); + return; + } + + old_page_name = g_strdup( gnc_plugin_page_get_page_name(page)); + old_page_long_name = g_strdup( gnc_plugin_page_get_page_long_name(page)); + + /* Update the plugin */ + gnc_plugin_page_set_page_name(page, name); + + /* Update the notebook tab */ + window = GNC_MAIN_WINDOW(page->window); + if (!window) + { + LEAVE("no window widget available"); + return; + } + + if (main_window_find_tab_items(window, page, &label, &entry)) + gtk_label_set_text(GTK_LABEL(label), name); + + /* Update Tooltip on notebook Tab */ + if (old_page_long_name && old_page_name + && g_strrstr(old_page_long_name, old_page_name) != NULL) + { + gchar *new_page_long_name; + gint string_position; + + string_position = strlen(old_page_long_name) - strlen(old_page_name); + new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, NULL); + + gnc_plugin_page_set_page_long_name(page, new_page_long_name); + + if (main_window_find_tab_event(window, page, &event_box)) + gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), event_box, new_page_long_name, NULL); + + g_free(new_page_long_name); + } + + /* Update the notebook menu */ + if (page->notebook_page) + { + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + 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(old_page_long_name); + g_free(old_page_name); + g_free(name); + LEAVE("done"); +} + + +void +main_window_update_page_color (GncPluginPage *page, + const gchar *color_in) +{ + GncMainWindow *window; + GncMainWindowPrivate *priv; + GtkWidget *event_box; + GdkColor tab_color; + gchar *color_string; + + + ENTER(" "); + + if ((color_in == NULL) || (*color_in == '\0')) + { + LEAVE("no string"); + return; + } + color_string = g_strstrip(g_strdup(color_in)); + + /* Optimization, if the color hasn't changed, don't update. */ + if (*color_string == '\0' || 0 == safe_strcmp(color_string, gnc_plugin_page_get_page_color(page))) + { + g_free(color_string); + LEAVE("empty string or color unchanged"); + return; + } + + /* Update the plugin */ + window = GNC_MAIN_WINDOW(page->window); + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + gnc_plugin_page_set_page_color(page, color_string); + + /* Update the notebook tab */ + main_window_find_tab_event(window, page, &event_box); + + if (gdk_color_parse(color_string, &tab_color)) + { + gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, &tab_color); + gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, &tab_color); + } + else + { + gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, NULL); + gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, NULL); + } + g_free(color_string); + 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_tab_close, + NULL); + gnc_gconf_general_register_cb (KEY_TAB_WIDTH, + gnc_main_window_update_tab_width, + 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); + + tips = gtk_tooltips_new(); +} + + +/** 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 window 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; + GtkWidget *old_window; + + window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL); + gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); + + 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_title(window); + gnc_main_window_update_all_menu_items(); + + gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window ); + + return window; +} + +/************************************************************ + * Utility Functions * + ************************************************************/ + +static void +gnc_main_window_engine_commit_error_callback( gpointer data, + QofBackendError errcode ) +{ + GncMainWindow* window = GNC_MAIN_WINDOW(data); + GtkWidget* dialog; + const gchar *reason = _("Unable to save to database."); + if ( errcode == ERR_BACKEND_READONLY ) + reason = _("Unable to save to database: Book is marked read-only."); + dialog = gtk_message_dialog_new( GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + reason ); + gtk_dialog_run(GTK_DIALOG (dialog)); + gtk_widget_destroy(dialog); + +} + +/** 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); + gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE); + 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); + if (gnc_gconf_get_bool(GCONF_GENERAL, KEY_TAB_NEXT_RECENT, NULL)) + { + 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, *event_box; + const gchar *icon, *text, *color_string; + GtkWidget *image; + GList *tmp; + gint width; + GdkColor tab_color; + + 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. + */ + width = gnc_gconf_get_float(GCONF_GENERAL, KEY_TAB_WIDTH, NULL); + icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon; + label = gtk_label_new (gnc_plugin_page_get_page_name(page)); + if (width != 0) + { + gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_max_width_chars(GTK_LABEL(label), width); + } + 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); + } + else + gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0); + + event_box = gtk_event_box_new(); + /* Note: this doesn't work properly on Windows with gtk+2.18.x (last + * with 2.18.7). Setting the eventbox visible with that version results + * in the tab's text being invisible. See bug #610675 for more on this. + */ + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), TRUE); + gtk_widget_show(event_box); + gtk_container_add(GTK_CONTAINER(event_box), tab_hbox); + color_string = gnc_plugin_page_get_page_color(page); + if (color_string == NULL) color_string = ""; + if (gdk_color_parse(color_string, &tab_color)) + { + gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, &tab_color); + gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, &tab_color); + } + else + { + gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, NULL); + gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, NULL); + } + + text = gnc_plugin_page_get_page_long_name(page); + if (text) + { + gtk_tooltips_set_tip(tips, event_box, text, NULL); + } + + 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, event_box, 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; + + if (!GNC_IS_MAIN_WINDOW (page->window)) + 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, + GtkToggleActionEntry *toggle_actions, + guint n_toggle_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); + if (toggle_actions != NULL && n_toggle_actions > 0) + { + gtk_action_group_add_toggle_actions (entry->action_group, + toggle_actions, n_toggle_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); +} + + +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); + g_signal_connect (G_OBJECT (priv->notebook), "page-reordered", + G_CALLBACK (gnc_main_window_page_reordered), window); + 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); +#ifndef MAC_INTEGRATION + gtk_action_group_add_radio_actions (priv->action_group, + radio_entries, n_radio_entries, + 0, + G_CALLBACK(gnc_main_window_cmd_window_raise), + window); +#endif + 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(" "); +} + +#ifdef MAC_INTEGRATION +static void +gnc_quartz_shutdown (GtkOSXApplication *theApp, gpointer data) +{ + gnc_shutdown(0); +} + +static gboolean +gnc_quartz_should_quit (GtkOSXApplication *theApp, GncMainWindow *window) +{ + QofSession *session; + gboolean needs_save, do_shutdown; + + gboolean finished = gnc_main_window_all_finish_pending(); + if (!finished) + return TRUE; + session = gnc_get_current_session(); + needs_save = qof_book_not_saved(qof_session_get_book(session)) && + !gnc_file_save_in_progress(); + if (needs_save && gnc_main_window_prompt_for_save(GTK_WIDGET(window))) + return TRUE; + gnc_shutdown(0); + return FALSE; +} + +static void +gnc_quartz_set_menu(GncMainWindow* window) +{ + GtkOSXApplicationMenuGroup *group; + GtkOSXApplication *theApp = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); + GtkWidget *menu; + GtkWidget *item; + + menu = gtk_ui_manager_get_widget (window->ui_merge, "/menubar"); + if (GTK_IS_MENU_ITEM (menu)) + menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu)); + gtk_widget_hide(menu); + gtk_osxapplication_set_menu_bar (theApp, GTK_MENU_SHELL (menu)); + + item = gtk_ui_manager_get_widget (window->ui_merge, + "/menubar/File/FileQuit"); + if (GTK_IS_MENU_ITEM (item)) + gtk_widget_hide (GTK_WIDGET (item)); + + /* the about group */ + group = gtk_osxapplication_add_app_menu_group (theApp); + + item = gtk_ui_manager_get_widget (window->ui_merge, + "/menubar/Help/HelpAbout"); + if (GTK_IS_MENU_ITEM (item)) + gtk_osxapplication_add_app_menu_item (theApp, group, + GTK_MENU_ITEM (item)); + + /* the preferences group */ + group = gtk_osxapplication_add_app_menu_group (theApp); + + item = gtk_ui_manager_get_widget (window->ui_merge, + "/menubar/Edit/EditPreferences"); + if (GTK_IS_MENU_ITEM (item)) + gtk_osxapplication_add_app_menu_item (theApp, group, + GTK_MENU_ITEM (item)); + + item = gtk_ui_manager_get_widget (window->ui_merge, + "/menubar/Help"); + gtk_osxapplication_set_help_menu(theApp, GTK_MENU_ITEM(item)); + item = gtk_ui_manager_get_widget (window->ui_merge, + "/menubar/Windows"); + gtk_osxapplication_set_window_menu(theApp, GTK_MENU_ITEM(item)); + g_signal_connect(theApp, "NSApplicationBlockTermination", + G_CALLBACK(gnc_quartz_should_quit), window); + +} +#endif //MAC_INTEGRATION + +/* 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(" "); +} + +/** 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(" "); +} + +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_page_setup (GtkAction *action, + GncMainWindow *window) +{ + GtkWindow *gtk_window; + + g_return_if_fail(GNC_IS_MAIN_WINDOW(window)); + + gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window)); + gnc_ui_page_setup(gtk_window); +} + +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_is_procedure (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 *label, *entry; + + ENTER(" "); + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + page = priv->current_page; + if (!page) + { + LEAVE("No current page"); + return; + } + + if (!main_window_find_tab_items(window, page, &label, &entry)) + { + LEAVE("can't find required widgets"); + 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; + + ENTER("action %p,window %p", action, window); + + /* Setup */ + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + page = priv->current_page; + if (!page) + { + LEAVE("invalid page"); + return; + } + if (!page->notebook_page) + { + LEAVE("invalid notebook_page"); + return; + } + + notebook = GTK_NOTEBOOK (priv->notebook); + 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); + + LEAVE("page moved"); +} + +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-2010 Contributors"; + gchar **authors, **documenters, *license, *message; + GdkPixbuf *logo; + + logo = gnc_gnome_get_gdkpixbuf ("gnucash-icon-48x48.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); +} + + +/************************************************************ + * * + ************************************************************/ + +void +gnc_main_window_show_all_windows(void) +{ + GList *window_iter; +#ifdef MAC_INTEGRATION + GtkOSXApplication *theApp = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); +#endif + for (window_iter = active_windows; window_iter != NULL; window_iter = window_iter->next) + { + gtk_widget_show(GTK_WIDGET(window_iter->data)); +#ifdef MAC_INTEGRATION + gnc_quartz_set_menu(window_iter->data); +#endif + } +#ifdef MAC_INTEGRATION + g_signal_connect(theApp, "NSApplicationWillTerminate", + G_CALLBACK(gnc_quartz_shutdown), NULL); + gtk_osxapplication_ready(theApp); +#endif +} + +/** Get a pointer to the first active top level window or NULL + * if there is none. + * + * @return A pointer to a GtkWindow object. */ +GtkWidget * +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 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); + } +} + +GtkUIManager *gnc_main_window_get_uimanager (GncMainWindow *window) +{ + g_assert(window); + return window->ui_merge; +} + +/** @} */ +/** @} */