Files
gnucash/src/gnome-utils/gnc-main-window.c
Geert Janssens 6a6fe1690e Use a normalized uri format internally to refer to data stores.
Data stores for GC can be a file (xml or sqlite3) or a database
one some server (mysql or postgres).
Wherever it makes sense internally, data stores will be referred to
via a normalized uri:
protocol://user:password@host:port/path
Depending on the context and story type some of these parts are optional or unused.

To achieve this, a new utility interface has been setup:
gnc_uri_<xxx>_<yyy>
that can be used to manipulate the uris or convert from non-normalized
formats to normalized and back.
For example, when the user selects a file in the Open or Save As dialog,
gnc_uri_get_normalized_uri will convert the file into a normalized uri.
Or when the actual filename is needed this can be extracted with
gnc_uri_get_path.
You can also test if a uri defines a file or something else with
gnc_uri_is_file_uri.

For the complete documentation, see src/core-utils/gnc-uri-uitls.h

This commit installs gnc-uri-utils and modifies the source where it makes
sense to use its convenience functions. This concerns all functions that
had to deal with file access in some way or another, the history module
and the functions that generate the history menu list and the window titles.

Note that gnc-uri-utils replaces xaccResolveFilePath and xaccResolveUrl in all cases.
xaccResolveUrl has been removed, because gnc-uri-utils fully replaces its functionality.
xaccResolveFilePath is used internally in gnc-uri-utils to ensure an absolute path
is always returned (in case of a file uri, not for db uris). But it has been renamed to
gnc_resolve_file_path to be more consistent with the other functions.

Lastly, this commit also adds a first implementation to work with a keyring to
store and retrieve passwords, althoug

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18842 57a11ea4-9604-0410-9ed3-97b8803252fd
2010-03-05 20:15:31 +00:00

4178 lines
132 KiB
C

/*
* gnc-main-window.c -- GtkWindow which represents the
* GnuCash main window.
*
* Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
* Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact:
*
* Free Software Foundation Voice: +1-617-542-5942
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
* Boston, MA 02110-1301, USA gnu@gnu.org
*/
/** @addtogroup Windows
@{ */
/** @addtogroup GncMainWindow Main Window functions.
@{ */
/** @file gnc-main-window.c
@brief Functions for adding content to a window.
@author Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
@author Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
*/
#include "config.h"
#include <gnome.h>
#include <glib/gi18n.h>
#include <libguile.h>
#include "guile-mappings.h"
#include "gnc-plugin.h"
#include "gnc-plugin-manager.h"
#include "gnc-main-window.h"
#include "dialog-preferences.h"
#include "dialog-reset-warnings.h"
#include "dialog-transfer.h"
#include "dialog-utils.h"
#include "file-utils.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-file.h"
#include "gnc-gkeyfile-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-gui-query.h"
#include "gnc-hooks.h"
#include "gnc-session.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-uri-utils.h"
#include "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 <igemacintegration/ige-mac-menu.h>
#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);
static GtkAction *gnc_main_window_find_action (GncMainWindow *window, const gchar *name);
#ifdef MAC_INTEGRATION
static void gtk_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 */
{ "FileNewMenuAction", GTK_STOCK_NEW, N_("_New"), "", NULL, NULL },
{ "FileOpenMenuAction", GTK_STOCK_OPEN, N_("_Open"), "", NULL, NULL },
{ "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL },
{ "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL },
{
"FilePrintAction", GTK_STOCK_PRINT, N_("_Print..."), "<control>p",
N_("Print the currently active page"), NULL
},
#ifndef GTK_STOCK_PAGE_SETUP
# define GTK_STOCK_PAGE_SETUP NULL
#endif
{
"FilePageSetupAction", GTK_STOCK_PAGE_SETUP, N_("Pa_ge Setup..."), "<control><shift>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"), "<Alt>Return",
N_("Edit the properties of the current file"),
G_CALLBACK (gnc_main_window_cmd_file_properties)
},
{
"FileCloseAction", GTK_STOCK_CLOSE, N_("_Close"), NULL,
N_("Close the currently active page"),
G_CALLBACK (gnc_main_window_cmd_file_close)
},
{
"FileQuitAction", GTK_STOCK_QUIT, N_("_Quit"), NULL,
N_("Quit this application"),
G_CALLBACK (gnc_main_window_cmd_file_quit)
},
/* Edit menu */
{
"EditCutAction", GTK_STOCK_CUT, N_("Cu_t"), NULL,
N_("Cut the current selection and copy it to clipboard"),
G_CALLBACK (gnc_main_window_cmd_edit_cut)
},
{
"EditCopyAction", GTK_STOCK_COPY, N_("_Copy"), NULL,
N_("Copy the current selection to clipboard"),
G_CALLBACK (gnc_main_window_cmd_edit_copy)
},
{
"EditPasteAction", GTK_STOCK_PASTE, N_("_Paste"), NULL,
N_("Paste the clipboard content at the cursor position"),
G_CALLBACK (gnc_main_window_cmd_edit_paste)
},
{
"EditPreferencesAction", GTK_STOCK_PREFERENCES, N_("Pr_eferences"), NULL,
N_("Edit the global preferences of GnuCash"),
G_CALLBACK (gnc_main_window_cmd_edit_preferences)
},
/* View menu */
{
"ViewSortByAction", NULL, N_("_Sort By..."), NULL,
N_("Select sorting criteria for this page view"), NULL
},
{
"ViewFilterByAction", NULL, N_("_Filter By..."), NULL,
N_("Select the account types that should be displayed."), NULL
},
{
"ViewRefreshAction", GTK_STOCK_REFRESH, N_("_Refresh"), "<control>r",
N_("Refresh this window"),
G_CALLBACK (gnc_main_window_cmd_view_refresh)
},
/* Actions menu */
{ "ScrubMenuAction", NULL, N_("_Check & Repair"), NULL, NULL, NULL },
{
"ActionsForgetWarningsAction", NULL, N_("Reset _Warnings..."), NULL,
N_("Reset the state of all warning 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, "FileNewAccountTreeAction");
gtk_action_activate(action);
}
/** Save the state of a single page to a disk. This function handles
* all the common tasks such as saving the page type and name, and
* anything else that might be common to all pages. It then calls a
* page specific function to save the actual page.
*
* @param page The GncPluginPage whose state should be saved.
*
* @param data A data structure containing state about the
* window/page saving process. */
static void
gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
{
gchar *page_group;
const gchar *plugin_name, *page_name;
ENTER("page %p, data %p (key file %p, window %d, page %d)",
page, data, data->key_file, data->window_num, data->page_num);
plugin_name = gnc_plugin_page_get_plugin_name(page);
page_name = gnc_plugin_page_get_page_name(page);
if (!plugin_name || !page_name)
{
LEAVE("not saving invalid page");
return;
}
page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
gnc_plugin_page_save_page(page, data->key_file, page_group);
g_free(page_group);
LEAVE(" ");
}
/** Saves all the pages in a single window to a disk. This function
* saves all the window specific attributes, then calls a helper
* function to save all the pages that are contained in the window.
*
* @param window The GncMainWindow whose pages should be saved.
*
* @param data A data structure containing state about the
* window/page saving process. */
static void
gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
{
GncMainWindowPrivate *priv;
GtkAction *action;
gint i, num_pages, coords[4], *order;
gboolean maximized, visible;
gchar *window_group;
/* Setup */
ENTER("window %p, data %p (key file %p, window %d)",
window, data, data->key_file, data->window_num);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
/* Check for bogus window structures. */
num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
if (0 == num_pages)
{
LEAVE("empty window %p", window);
return;
}
/* Save this window's notebook info */
window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
g_key_file_set_integer(data->key_file, window_group,
WINDOW_PAGECOUNT, num_pages);
g_key_file_set_integer(data->key_file, window_group,
WINDOW_FIRSTPAGE, data->page_num);
/* Save page ordering within the notebook. Use +1 notation so the
* numbers in the page order match the page sections, at least for
* the one window case. */
order = g_malloc(sizeof(gint) * num_pages);
for (i = 0; i < num_pages; i++)
{
gpointer page = g_list_nth_data(priv->usage_order, i);
order[i] = g_list_index(priv->installed_pages, page) + 1;
}
g_key_file_set_integer_list(data->key_file, window_group,
WINDOW_PAGEORDER, order, num_pages);
g_free(order);
/* Save the window coordinates, etc. */
gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
maximized = (gdk_window_get_state((GTK_WIDGET(window))->window)
& GDK_WINDOW_STATE_MAXIMIZED) != 0;
g_key_file_set_integer_list(data->key_file, window_group,
WINDOW_POSITION, &coords[0], 2);
g_key_file_set_integer_list(data->key_file, window_group,
WINDOW_GEOMETRY, &coords[2], 2);
g_key_file_set_boolean(data->key_file, window_group,
WINDOW_MAXIMIZED, maximized);
DEBUG("window (%p) position %dx%d, size %dx%d, %s", window, coords[0], coords[1],
coords[2], coords[3],
maximized ? "maximized" : "not maximized");
/* Common view menu items */
action = gnc_main_window_find_action(window, "ViewToolbarAction");
visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
g_key_file_set_boolean(data->key_file, window_group,
TOOLBAR_VISIBLE, visible);
action = gnc_main_window_find_action(window, "ViewSummaryAction");
visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
g_key_file_set_boolean(data->key_file, window_group,
SUMMARYBAR_VISIBLE, visible);
action = gnc_main_window_find_action(window, "ViewStatusbarAction");
visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
g_key_file_set_boolean(data->key_file, window_group,
STATUSBAR_VISIBLE, visible);
/* Save individual pages in this window */
g_list_foreach(priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
g_free(window_group);
LEAVE("window %p", window);
}
void
gnc_main_window_save_all_windows(GKeyFile *keyfile)
{
GncMainWindowSaveData data;
/* Set up the iterator data structures */
data.key_file = keyfile;
data.window_num = 1;
data.page_num = 1;
g_key_file_set_integer(data.key_file,
STATE_FILE_TOP, WINDOW_COUNT,
g_list_length(active_windows));
/* Dump all state information on the open windows */
g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
}
gboolean
gnc_main_window_finish_pending (GncMainWindow *window)
{
GncMainWindowPrivate *priv;
GList *item;
g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
for (item = priv->installed_pages; item; item = g_list_next(item))
{
if (!gnc_plugin_page_finish_pending(item->data))
{
return FALSE;
}
}
return TRUE;
}
gboolean
gnc_main_window_all_finish_pending (void)
{
const GList *windows, *item;
windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
for (item = windows; item; item = g_list_next(item))
{
if (!gnc_main_window_finish_pending(item->data))
{
return FALSE;
}
}
return TRUE;
}
/** See if the page already exists. For each open window, look
* through the list of pages installed in that window and see if the
* specified page is there.
*
* @internal
*
* @param page The page to search for.
*
* @return TRUE if the page is present in the window, FALSE otherwise.
*/
static gboolean
gnc_main_window_page_exists (GncPluginPage *page)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
GList *walker;
for (walker = active_windows; walker; walker = g_list_next(walker))
{
window = walker->data;
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
if (g_list_find(priv->installed_pages, page))
{
return TRUE;
}
}
return FALSE;
}
/** This function prompts the user to save the file with a dialog that
* follows the HIG guidelines.
*
* @internal
*
* @returns This function returns TRUE if the user clicked the Cancel
* button. It returns FALSE if the closing of the window should
* continue.
*/
static gboolean
gnc_main_window_prompt_for_save (GtkWidget *window)
{
QofSession *session;
QofBook *book;
GtkWidget *dialog;
gint response;
const gchar *filename, *tmp;
const gchar *title = _("Save changes to file %s before closing?");
/* This should be the same message as in gnc-file.c */
const gchar *message_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 = _("<unknown>");
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 = gnc_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
{
gchar *protocol = NULL;
gchar *hostname = NULL;
gchar *username = NULL;
gchar *password = NULL;
gchar *path = NULL;
guint32 port = 0;
gnc_uri_get_components (book_id, &protocol, &hostname,
port, &username, &password, &path);
if ( gnc_uri_is_file_protocol ( (const gchar*) protocol ) )
{
/* The filename is a true file.
* The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
filename = g_path_get_basename ( 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);
}
g_free(protocol);
g_free(hostname);
g_free(username);
g_free(password);
g_free(path);
}
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();
gnc_main_window_update_all_menu_items();
}
/** 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
{
GtkWidget *menu = gtk_ui_manager_get_widget(window->ui_merge, "/menubar");
ige_mac_menu_sync(GTK_MENU_SHELL( menu ));
}
#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));
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("");
/* First update the entries for all existing windows */
g_list_foreach(active_windows,
(GFunc)gnc_main_window_update_menu_item,
NULL);
g_list_foreach(active_windows,
(GFunc)gnc_main_window_update_radio_button,
NULL);
/* Now hide any entries that aren't being used. */
data.visible = FALSE;
for (i = g_list_length(active_windows); i < n_radio_entries; i++)
{
data.action_name = g_strdup_printf("Window%dAction", i);
label = g_strdup_printf("Window _%d", (i - 1) % 10);
data.label = gettext(label);
g_list_foreach(active_windows,
(GFunc)gnc_main_window_update_one_menu_action,
&data);
g_free(data.action_name);
g_free(label);
}
LEAVE(" ");
}
/** Show/hide the close box on the tab of a notebook page. This
* function first checks to see if the specified page has a close
* box, and if so, sets its visibility to the requested state.
*
* @internal
*
* @param page The GncPluginPage whose notebook tab should be updated.
*
* @param new_value A pointer to the boolean that indicates whether
* or not the close button should be visible.
*/
static void
gnc_main_window_update_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;
GList *children, *tmp;
ENTER("window %p, page %p, label_p %p, entry_p %p",
window, page, label_p, entry_p);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
*label_p = *entry_p = NULL;
tab_hbox = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
page->notebook_page);
children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
for (tmp = children; tmp; tmp = g_list_next(tmp))
{
widget = tmp->data;
if (GTK_IS_EVENT_BOX(widget))
{
*label_p = gtk_bin_get_child(GTK_BIN(widget));
}
else if (GTK_IS_ENTRY(widget))
{
*entry_p = widget;
}
}
g_list_free(children);
LEAVE("label %p, entry %p", *label_p, *entry_p);
return (*label_p && *entry_p);
}
void
main_window_update_page_name (GncPluginPage *page,
const gchar *name_in)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
GtkWidget *label, *entry;
gchar *name;
ENTER(" ");
if ((name_in == NULL) || (*name_in == '\0'))
{
LEAVE("no string");
return;
}
name = g_strstrip(g_strdup(name_in));
/* 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;
}
/* Update the plugin */
window = GNC_MAIN_WINDOW(page->window);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
gnc_plugin_page_set_page_name(page, name);
/* Update the notebook tab */
main_window_find_tab_items(window, page, &label, &entry);
gtk_label_set_text(GTK_LABEL(label), name);
/* Update the notebook menu */
label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
page->notebook_page);
gtk_label_set_text(GTK_LABEL(label), name);
/* Force an update of the window title */
gnc_main_window_update_title(window);
g_free(name);
LEAVE("done");
}
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 */
if (gdk_color_parse(color_string, &tab_color))
{
}
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 view The new object instance created by the object system.
*
* @param klass A pointer to the class data structure for this
* object. */
static void
gnc_main_window_init (GncMainWindow *window,
GncMainWindowClass *klass)
{
GncMainWindowPrivate *priv;
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
priv->merged_actions_table =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
priv->event_handler_id =
qof_event_register_handler(gnc_main_window_event_handler, window);
gnc_main_window_setup_window (window);
#ifdef MAC_INTEGRATION
gtk_quartz_set_menu(window);
#endif
gnc_gobject_tracking_remember(G_OBJECT(window),
G_OBJECT_CLASS(klass));
}
/** Finalize the GncMainWindow object. This function is called from
* the G_Object level to complete the destruction of the object. It
* should release any memory not previously released by the destroy
* function (i.e. the private data structure), then chain up to the
* parent's destroy function.
*
* @param object The object being destroyed.
*
* @internal
*/
static void
gnc_main_window_finalize (GObject *object)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
window = GNC_MAIN_WINDOW (object);
priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
if (active_windows == NULL)
{
/* Oops. User killed last window and we didn't catch it. */
g_idle_add((GSourceFunc)gnc_shutdown, 0);
}
gnc_gobject_tracking_forget(object);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gnc_main_window_destroy (GtkObject *object)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
GncPluginManager *manager;
GList *plugins;
g_return_if_fail (object != NULL);
g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
window = GNC_MAIN_WINDOW (object);
active_windows = g_list_remove (active_windows, window);
/* Do these things once */
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
if (priv->merged_actions_table)
{
/* Close any pages in this window */
while (priv->current_page)
gnc_main_window_close_page(priv->current_page);
if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
gnc_window_set_progressbar_window(NULL);
/* Update the "Windows" menu in all other windows */
gnc_main_window_update_all_menu_items();
gnc_gconf_remove_notification(G_OBJECT(window), DESKTOP_GNOME_INTERFACE,
GNC_MAIN_WINDOW_NAME);
gnc_gconf_remove_notification(G_OBJECT(window), GCONF_GENERAL,
GNC_MAIN_WINDOW_NAME);
qof_event_unregister_handler(priv->event_handler_id);
priv->event_handler_id = 0;
g_hash_table_destroy (priv->merged_actions_table);
priv->merged_actions_table = NULL;
/* GncPluginManager stuff */
manager = gnc_plugin_manager_get ();
plugins = gnc_plugin_manager_get_plugins (manager);
g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
g_list_free (plugins);
}
GTK_OBJECT_CLASS (parent_class)->destroy (object);
}
/* Create a new gnc main window plugin.
*/
GncMainWindow *
gnc_main_window_new (void)
{
GncMainWindow *window;
gncUIWidget old_window;
window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL);
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;
dialog = gtk_message_dialog_new( GTK_WINDOW(window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"Unable to save to database" );
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;
GtkWidget *image;
GList *tmp;
gint width;
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);
g_object_set_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, 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);
}
event_box = gtk_event_box_new();
gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
gtk_widget_show(event_box);
gtk_container_add(GTK_CONTAINER(event_box), label);
gtk_box_pack_start (GTK_BOX (tab_hbox), event_box, TRUE, TRUE, 0);
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, tab_hbox, label);
LEAVE("");
}
/* Remove a data plugin page from a window and display the previous
* page. If the page removed was the last page in the window, and
* there is more than one window open, then the entire window will be
* destroyed.
*/
void
gnc_main_window_close_page (GncPluginPage *page)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
if (!page || !page->notebook_page)
return;
if (!gnc_plugin_page_finish_pending(page))
return;
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,
const gchar *filename,
gpointer user_data)
{
GncMainWindowPrivate *priv;
GncMainWindowActionData *data;
MergedActionEntry *entry;
GError *error = NULL;
gchar *pathname;
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
g_return_if_fail (group_name != NULL);
g_return_if_fail (actions != NULL);
g_return_if_fail (n_actions > 0);
g_return_if_fail (filename != NULL);
data = g_new0 (GncMainWindowActionData, 1);
data->window = window;
data->data = user_data;
pathname = gnc_gnome_locate_ui_file (filename);
if (pathname == NULL)
return;
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
entry = g_new0 (MergedActionEntry, 1);
entry->action_group = gtk_action_group_new (group_name);
gnc_gtk_action_group_set_translation_domain (entry->action_group, GETTEXT_PACKAGE);
gtk_action_group_add_actions (entry->action_group, actions, n_actions, data);
gtk_ui_manager_insert_action_group (window->ui_merge, entry->action_group, 0);
entry->merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge, pathname, &error);
g_assert(entry->merge_id || error);
if (entry->merge_id)
{
gtk_ui_manager_ensure_update (window->ui_merge);
g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
}
else
{
g_critical("Failed to load ui file.\n Filename %s\n Error %s",
filename, error->message);
g_error_free(error);
g_free(entry);
}
g_free(pathname);
}
/* Remove a set of actions from the specified window. This function
* should not need to be called directly by plugin implementors. It
* will automatically be called when a plugin is removed from a
* window.
*/
void
gnc_main_window_unmerge_actions (GncMainWindow *window,
const gchar *group_name)
{
GncMainWindowPrivate *priv;
MergedActionEntry *entry;
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
g_return_if_fail (group_name != NULL);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
if (priv->merged_actions_table == NULL)
return;
entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
if (entry == NULL)
return;
gtk_ui_manager_remove_action_group (window->ui_merge, entry->action_group);
gtk_ui_manager_remove_ui (window->ui_merge, entry->merge_id);
gtk_ui_manager_ensure_update (window->ui_merge);
g_hash_table_remove (priv->merged_actions_table, group_name);
}
/* Force a full update of the user interface for the specified
* window. This can be an expensive function, but is needed because
* the gtk ui manager doesn't always seem to update properly when
* actions are changed.
*/
void
gnc_main_window_actions_updated (GncMainWindow *window)
{
GtkActionGroup *force;
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
/* Unfortunately gtk_ui_manager_ensure_update doesn't work
* here. Force a full update by adding and removing an empty
* action group.
*/
force = gtk_action_group_new("force_update");
gtk_ui_manager_insert_action_group (window->ui_merge, force, 0);
gtk_ui_manager_ensure_update (window->ui_merge);
gtk_ui_manager_remove_action_group (window->ui_merge, force);
g_object_unref(force);
}
static GtkAction *
gnc_main_window_find_action (GncMainWindow *window, const gchar *name)
{
GtkAction *action = NULL;
const GList *groups, *tmp;
groups = gtk_ui_manager_get_action_groups(window->ui_merge);
for (tmp = groups; tmp; tmp = g_list_next(tmp))
{
action = gtk_action_group_get_action(GTK_ACTION_GROUP(tmp->data), name);
if (action)
break;
}
return action;
}
/* Retrieve a specific set of user interface actions from a window.
* This function can be used to get an group of action to be
* manipulated when the front page of a window has changed.
*/
GtkActionGroup *
gnc_main_window_get_action_group (GncMainWindow *window,
const gchar *group_name)
{
GncMainWindowPrivate *priv;
MergedActionEntry *entry;
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
g_return_val_if_fail (group_name != NULL, NULL);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
if (priv->merged_actions_table == NULL)
return NULL;
entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
if (entry == NULL)
return NULL;
return entry->action_group;
}
static void
gnc_main_window_update_toolbar (GncMainWindow *window)
{
GtkToolbarStyle style;
GSList *list;
ENTER("window %p", window);
style = gnc_get_toolbar_style();
list = gtk_ui_manager_get_toplevels(window->ui_merge, GTK_UI_MANAGER_TOOLBAR);
g_slist_foreach(list, (GFunc)gtk_toolbar_set_style, GINT_TO_POINTER(style));
g_slist_free(list);
LEAVE("");
}
static void
gnc_main_window_update_tab_position (GncMainWindow *window)
{
GtkPositionType position = GTK_POS_TOP;
gchar *conf_string;
GncMainWindowPrivate *priv;
ENTER ("window %p", window);
conf_string = gnc_gconf_get_string (GCONF_GENERAL,
KEY_TAB_POSITION, NULL);
if (conf_string)
{
position = gnc_enum_from_nick (GTK_TYPE_POSITION_TYPE,
conf_string, GTK_POS_TOP);
g_free (conf_string);
}
priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), position);
LEAVE ("");
}
/*
* Based on code from Epiphany (src/ephy-window.c)
*/
static void
gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
{
GncMainWindowPrivate *priv;
GncPluginPage *page;
GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
GtkAction *action;
gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
page = priv->current_page;
if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
{
(GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
return;
}
if (GTK_IS_EDITABLE (widget))
{
gboolean has_selection;
has_selection = gtk_editable_get_selection_bounds
(GTK_EDITABLE (widget), NULL, NULL);
can_copy = has_selection;
can_cut = has_selection;
can_paste = TRUE;
}
else if (GTK_IS_TEXT_VIEW (widget))
{
gboolean has_selection;
GtkTextBuffer *text_buffer;
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
has_selection = gtk_text_buffer_get_selection_bounds
(text_buffer, NULL, NULL);
can_copy = has_selection;
can_cut = has_selection;
can_paste = TRUE;
}
else
{
#ifdef ORIGINAL_EPIPHANY_CODE
/* For now we assume all actions are possible */
can_copy = can_cut = can_paste = TRUE;
#else
/* If its not a GtkEditable, we don't know what to do
* with it. */
can_copy = can_cut = can_paste = FALSE;
#endif
}
action = gnc_main_window_find_action (window, "EditCopyAction");
gtk_action_set_sensitive (action, can_copy);
gtk_action_set_visible (action, !hide || can_copy);
action = gnc_main_window_find_action (window, "EditCutAction");
gtk_action_set_sensitive (action, can_cut);
gtk_action_set_visible (action, !hide || can_cut);
action = gnc_main_window_find_action (window, "EditPasteAction");
gtk_action_set_sensitive (action, can_paste);
gtk_action_set_visible (action, !hide || can_paste);
}
static void
gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
{
GtkAction *action;
action = gnc_main_window_find_action (window, "EditCopyAction");
gtk_action_set_sensitive (action, TRUE);
gtk_action_set_visible (action, TRUE);
action = gnc_main_window_find_action (window, "EditCutAction");
gtk_action_set_sensitive (action, TRUE);
gtk_action_set_visible (action, TRUE);
action = gnc_main_window_find_action (window, "EditPasteAction");
gtk_action_set_sensitive (action, TRUE);
gtk_action_set_visible (action, TRUE);
}
static void
gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
GncMainWindow *window)
{
gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
}
static void
gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
GncMainWindow *window)
{
gnc_main_window_enable_edit_actions_sensitivity (window);
}
static void
gnc_main_window_init_menu_updaters (GncMainWindow *window)
{
GtkWidget *edit_menu_item, *edit_menu;
edit_menu_item = gtk_ui_manager_get_widget
(window->ui_merge, "/menubar/Edit");
edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (edit_menu_item));
g_signal_connect (edit_menu, "show",
G_CALLBACK (gnc_main_window_edit_menu_show_cb), window);
g_signal_connect (edit_menu, "hide",
G_CALLBACK (gnc_main_window_edit_menu_hide_cb), window);
}
static void
gnc_main_window_gconf_changed (GConfClient *client,
guint cnxn_id,
GConfEntry *entry,
gpointer user_data)
{
GncMainWindow *window;
GConfValue *value;
const gchar *key, *key_tail;
window = GNC_MAIN_WINDOW(user_data);
key = gconf_entry_get_key(entry);
value = gconf_entry_get_value(entry);
if (!key || !value)
return;
key_tail = strrchr(key, '/');
if (key_tail != NULL)
key_tail++;
if (strcmp(key_tail, KEY_TOOLBAR_STYLE) == 0)
{
gnc_main_window_update_toolbar(window);
}
else if (strcmp(key_tail, KEY_TAB_POSITION) == 0)
{
gnc_main_window_update_tab_position(window);
}
}
/* CS: This callback functions will set the statusbar text to the
* "tooltip" property of the currently selected GtkAction.
*
* This code is directly copied from gtk+/test/testmerge.c.
* Thanks to (L)GPL! */
typedef struct _ActionStatus ActionStatus;
struct _ActionStatus
{
GtkAction *action;
GtkWidget *statusbar;
};
static void
action_status_destroy (gpointer data)
{
ActionStatus *action_status = data;
g_object_unref (action_status->action);
g_object_unref (action_status->statusbar);
g_free (action_status);
}
static void
set_tip (GtkWidget *widget)
{
ActionStatus *data;
gchar *tooltip;
data = g_object_get_data (G_OBJECT (widget), "action-status");
if (data)
{
g_object_get (data->action, "tooltip", &tooltip, NULL);
gtk_statusbar_push (GTK_STATUSBAR (data->statusbar), 0,
tooltip ? tooltip : "");
g_free (tooltip);
}
}
static void
unset_tip (GtkWidget *widget)
{
ActionStatus *data;
data = g_object_get_data (G_OBJECT (widget), "action-status");
if (data)
gtk_statusbar_pop (GTK_STATUSBAR (data->statusbar), 0);
}
static void
connect_proxy (GtkUIManager *merge,
GtkAction *action,
GtkWidget *proxy,
GtkWidget *statusbar)
{
if (GTK_IS_MENU_ITEM (proxy))
{
ActionStatus *data;
data = g_object_get_data (G_OBJECT (proxy), "action-status");
if (data)
{
g_object_unref (data->action);
g_object_unref (data->statusbar);
data->action = g_object_ref (action);
data->statusbar = g_object_ref (statusbar);
}
else
{
data = g_new0 (ActionStatus, 1);
data->action = g_object_ref (action);
data->statusbar = g_object_ref (statusbar);
g_object_set_data_full (G_OBJECT (proxy), "action-status",
data, action_status_destroy);
g_signal_connect (proxy, "select", G_CALLBACK (set_tip), NULL);
g_signal_connect (proxy, "deselect", G_CALLBACK (unset_tip), NULL);
}
}
}
/* CS: end copied code from gtk+/test/testmerge.c */
static void
gnc_main_window_setup_window (GncMainWindow *window)
{
GncMainWindowPrivate *priv;
GtkWidget *main_vbox;
guint merge_id;
GncPluginManager *manager;
GList *plugins;
GError *error = NULL;
gchar *filename;
ENTER(" ");
/* Catch window manager delete signal */
g_signal_connect (G_OBJECT (window), "delete-event",
G_CALLBACK (gnc_main_window_delete_event), window);
/* Create widgets and add them to the window */
main_vbox = gtk_vbox_new (FALSE, 0);
gtk_widget_show (main_vbox);
gtk_container_add (GTK_CONTAINER (window), main_vbox);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
priv->menu_dock = gtk_vbox_new (FALSE, 0);
gtk_widget_show (priv->menu_dock);
gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
FALSE, TRUE, 0);
priv->notebook = gtk_notebook_new ();
g_object_set(G_OBJECT(priv->notebook),
"scrollable", TRUE,
"enable-popup", TRUE,
(char *)NULL);
gtk_widget_show (priv->notebook);
g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
G_CALLBACK (gnc_main_window_switch_page), window);
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);
gtk_action_group_add_radio_actions (priv->action_group,
radio_entries, n_radio_entries,
0,
G_CALLBACK(gnc_main_window_cmd_window_raise),
window);
gnc_plugin_update_actions(priv->action_group,
initially_insensitive_actions,
"sensitive", FALSE);
gnc_plugin_update_actions(priv->action_group,
always_insensitive_actions,
"sensitive", FALSE);
gnc_plugin_update_actions(priv->action_group,
always_hidden_actions,
"visible", FALSE);
gnc_plugin_set_important_actions (priv->action_group,
gnc_menu_important_actions);
gtk_ui_manager_insert_action_group (window->ui_merge, priv->action_group, 0);
g_signal_connect (G_OBJECT (window->ui_merge), "add_widget",
G_CALLBACK (gnc_main_window_add_widget), window);
/* Use the "connect-proxy" signal for tooltip display in the
status bar */
g_signal_connect (G_OBJECT (window->ui_merge), "connect-proxy",
G_CALLBACK (connect_proxy), priv->statusbar);
filename = gnc_gnome_locate_ui_file("gnc-main-window-ui.xml");
/* Can't do much without a ui. */
g_assert (filename);
merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge,
filename, &error);
g_assert(merge_id || error);
if (merge_id)
{
gtk_window_add_accel_group (GTK_WINDOW (window),
gtk_ui_manager_get_accel_group(window->ui_merge));
gtk_ui_manager_ensure_update (window->ui_merge);
}
else
{
g_critical("Failed to load ui file.\n Filename %s\n Error %s",
filename, error->message);
g_error_free(error);
g_assert(merge_id != 0);
}
g_free(filename);
gnc_gconf_add_notification(G_OBJECT(window), GCONF_GENERAL,
gnc_main_window_gconf_changed,
GNC_MAIN_WINDOW_NAME);
gnc_gconf_add_notification(G_OBJECT(window), DESKTOP_GNOME_INTERFACE,
gnc_main_window_gconf_changed,
GNC_MAIN_WINDOW_NAME);
gnc_main_window_update_toolbar(window);
gnc_main_window_update_tab_position(window);
gnc_main_window_init_menu_updaters(window);
/* Testing */
/* Now update the "eXtensions" menu */
if (!gnc_is_extra_enabled())
{
GtkAction* action;
action = gtk_action_group_get_action(priv->action_group,
"ExtensionsAction");
gtk_action_set_visible(action, FALSE);
}
/* GncPluginManager stuff */
manager = gnc_plugin_manager_get ();
plugins = gnc_plugin_manager_get_plugins (manager);
g_list_foreach (plugins, gnc_main_window_add_plugin, window);
g_list_free (plugins);
g_signal_connect (G_OBJECT (manager), "plugin-added",
G_CALLBACK (gnc_main_window_plugin_added), window);
g_signal_connect (G_OBJECT (manager), "plugin-removed",
G_CALLBACK (gnc_main_window_plugin_removed), window);
LEAVE(" ");
}
#ifdef MAC_INTEGRATION
static void
gtk_quartz_set_menu(GncMainWindow* window)
{
IgeMacMenuGroup *group;
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);
ige_mac_menu_set_menu_bar (GTK_MENU_SHELL (menu));
item = gtk_ui_manager_get_widget (window->ui_merge,
"/menubar/File/FileQuit");
if (GTK_IS_MENU_ITEM (item))
ige_mac_menu_set_quit_menu_item (GTK_MENU_ITEM (item));
/* the about group */
group = ige_mac_menu_add_app_menu_group ();
item = gtk_ui_manager_get_widget (window->ui_merge,
"/menubar/Help/HelpAbout");
if (GTK_IS_MENU_ITEM (item))
ige_mac_menu_add_app_menu_item (group, GTK_MENU_ITEM (item), _("About GnuCash"));
/* the preferences group */
group = ige_mac_menu_add_app_menu_group ();
item = gtk_ui_manager_get_widget (window->ui_merge,
"/menubar/Edit/EditPreferences");
if (GTK_IS_MENU_ITEM (item))
ige_mac_menu_add_app_menu_item (group, GTK_MENU_ITEM (item), NULL);
}
#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;
/* Setup */
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
if (priv->current_page == NULL)
return;
notebook = GTK_NOTEBOOK (priv->notebook);
page = priv->current_page;
tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
/* Ref the page components, then remove it from its old window */
g_object_ref(page);
g_object_ref(tab_widget);
g_object_ref(menu_widget);
g_object_ref(page->notebook_page);
gnc_main_window_disconnect(window, page);
/* Create the new window */
new_window = gnc_main_window_new ();
gtk_widget_show(GTK_WIDGET(new_window));
/* Now add the page to the new window */
gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
/* Unref the page components now that we're done */
g_object_unref(page->notebook_page);
g_object_unref(menu_widget);
g_object_unref(tab_widget);
g_object_unref(page);
/* just a little debugging. :-) */
new_priv = GNC_MAIN_WINDOW_GET_PRIVATE(new_window);
DEBUG("Moved page %p from window %p to new window %p",
page, window, new_window);
DEBUG("Old window current is %p, new window current is %p",
priv->current_page, priv->current_page);
}
static void
gnc_main_window_cmd_window_raise (GtkAction *action,
GtkRadioAction *current,
GncMainWindow *old_window)
{
GncMainWindow *new_window;
gint value;
g_return_if_fail(GTK_IS_ACTION(action));
g_return_if_fail(GTK_IS_RADIO_ACTION(current));
g_return_if_fail(GNC_IS_MAIN_WINDOW(old_window));
ENTER("action %p, current %p, window %p", action, current, old_window);
value = gtk_radio_action_get_current_value(current);
new_window = g_list_nth_data(active_windows, value);
gtk_window_present(GTK_WINDOW(new_window));
/* revert the change in the radio group
* impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
g_idle_add((GSourceFunc)gnc_main_window_update_radio_button, old_window);
LEAVE(" ");
}
static void
gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window)
{
gnc_gnome_help (HF_GUIDE, NULL);
}
static void
gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window)
{
gnc_gnome_help (HF_HELP, NULL);
}
/** This is a helper function to find a data file and suck it into
* memory.
*
* @param partial The name of the file relative to the gnucash
* specific shared data directory.
*
* @return The text of the file or NULL. The caller is responsible
* for freeing this string.
*/
static gchar *
get_file (const gchar *partial)
{
gchar *filename, *text = NULL;
filename = gnc_gnome_locate_data_file(partial);
g_file_get_contents(filename, &text, NULL, NULL);
g_free(filename);
/* Anything there? */
if (text && *text)
return text;
/* Just a empty string or no string at all. */
if (text)
g_free(text);
return NULL;
}
/** This is a helper function to find a data file, suck it into
* memory, and split it into an array of strings.
*
* @param partial The name of the file relative to the gnucash
* specific shared data directory.
*
* @return The text of the file as an array of strings, or NULL. The
* caller is responsible for freeing all the strings and the array.
*/
static gchar **
get_file_strsplit (const gchar *partial)
{
gchar *text, **lines;
text = get_file(partial);
if (!text)
return NULL;
lines = g_strsplit_set(text, "\r\n", -1);
g_free(text);
return lines;
}
/** Create and display the "about" dialog for gnucash.
*
* @param action The GtkAction for the "about" menu item.
*
* @param window The main window whose menu item was activated.
*/
static void
gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
{
const gchar *fixed_message = _("The GnuCash personal finance manager. "
"The GNU way to manage your money!");
const gchar *copyright = "© 1997-2009 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;
for (window_iter = active_windows; window_iter != NULL; window_iter = window_iter->next)
{
gtk_widget_show(GTK_WIDGET(window_iter->data));
}
}
/** Get a pointer to the first active top level window or NULL
* if there is none.
*
* @return A pointer to a GtkWindow object. */
gncUIWidget
gnc_ui_get_toplevel (void)
{
GList *window;
for (window = active_windows; window; window = window->next)
if (gtk_window_is_active (GTK_WINDOW (window->data)))
return window->data;
return NULL;
}
/** Retrieve the gtk window associated with a main window object.
* This function is called via a vector off a generic window
* interface.
*
* @param window_in A pointer to a generic window. */
static GtkWindow *
gnc_main_window_get_gtk_window (GncWindow *window)
{
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
return GTK_WINDOW(window);
}
/** Retrieve the status bar associated with a main window object.
* This function is called via a vector off a generic window
* interface.
*
* @param window_in A pointer to a generic window. */
static GtkWidget *
gnc_main_window_get_statusbar (GncWindow *window_in)
{
GncMainWindowPrivate *priv;
GncMainWindow *window;
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
window = GNC_MAIN_WINDOW(window_in);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
return priv->statusbar;
}
/** Retrieve the progress bar associated with a main window object.
* This function is called via a vector off a generic window
* interface.
*
* @param window_in A pointer to a generic window. */
static GtkWidget *
gnc_main_window_get_progressbar (GncWindow *window_in)
{
GncMainWindowPrivate *priv;
GncMainWindow *window;
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
window = GNC_MAIN_WINDOW(window_in);
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
return priv->progressbar;
}
static void
gnc_main_window_all_ui_set_sensitive (GncWindow *unused, gboolean sensitive)
{
GncMainWindow *window;
GncMainWindowPrivate *priv;
GList *groupp, *groups, *winp, *tmp;
GtkWidget *close_button;
for (winp = active_windows; winp; winp = g_list_next(winp))
{
window = winp->data;
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
groups = gtk_ui_manager_get_action_groups(window->ui_merge);
for (groupp = groups; groupp; groupp = g_list_next(groupp))
{
gtk_action_group_set_sensitive(GTK_ACTION_GROUP(groupp->data), sensitive);
}
for (tmp = priv->installed_pages; tmp; tmp = g_list_next(tmp))
{
close_button = g_object_get_data(tmp->data, PLUGIN_PAGE_CLOSE_BUTTON);
if (!close_button)
continue;
gtk_widget_set_sensitive (close_button, sensitive);
}
}
}
/** Initialize the generic window interface for a main window.
*
* @param iface A pointer to the interface data structure to
* populate. */
static void
gnc_window_main_window_init (GncWindowIface *iface)
{
iface->get_gtk_window = gnc_main_window_get_gtk_window;
iface->get_statusbar = gnc_main_window_get_statusbar;
iface->get_progressbar = gnc_main_window_get_progressbar;
iface->ui_set_sensitive = gnc_main_window_all_ui_set_sensitive;
}
/* Set the window where all progressbar updates should occur. This
* is a wrapper around the gnc_window_set_progressbar_window()
* function.
*/
void
gnc_main_window_set_progressbar_window (GncMainWindow *window)
{
GncWindow *gncwin;
gncwin = GNC_WINDOW(window);
gnc_window_set_progressbar_window(gncwin);
}
/** Popup a contextual menu. This function ends up being called when
* the user right-clicks in the context of a window, or uses the
* keyboard context-menu request key combination (Shift-F10 by
* default).
*
* @param page This is the GncPluginPage corresponding to the visible
* page.
*
* @param event The event parameter passed to the "button-press"
* callback. May be null if there was no event (aka keyboard
* request).
*/
static void
do_popup_menu(GncPluginPage *page, GdkEventButton *event)
{
GtkUIManager *ui_merge;
GtkWidget *menu;
int button, event_time;
g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
ENTER("page %p, event %p", page, event);
ui_merge = gnc_plugin_page_get_ui_merge(page);
if (ui_merge == NULL)
{
LEAVE("no ui merge");
return;
}
menu = gtk_ui_manager_get_widget(ui_merge, "/MainPopup");
if (!menu)
{
LEAVE("no menu");
return;
}
if (event)
{
button = event->button;
event_time = event->time;
}
else
{
button = 0;
event_time = gtk_get_current_event_time ();
}
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, event_time);
LEAVE(" ");
}
/** Callback function invoked when the user requests that Gnucash
* popup the contextual menu via the keyboard context-menu request
* key combination (Shift-F10 by default).
*
* @param page This is the GncPluginPage corresponding to the visible
* page.
*
* @param widget Whatever widget had focus when the user issued the
* keyboard context-menu request.
*
* @return Always returns TRUE to indicate that the menu request was
* handled.
*/
static gboolean
gnc_main_window_popup_menu_cb (GtkWidget *widget,
GncPluginPage *page)
{
ENTER("widget %p, page %p", widget, page);
do_popup_menu(page, NULL);
LEAVE(" ");
return TRUE;
}
/* Callback function invoked when the user clicks in the content of
* any Gnucash window. If this was a "right-click" then Gnucash will
* popup the contextual menu.
*/
gboolean
gnc_main_window_button_press_cb (GtkWidget *whatever,
GdkEventButton *event,
GncPluginPage *page)
{
g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
ENTER("widget %p, event %p, page %p", whatever, event, page);
/* Ignore double-clicks and triple-clicks */
if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
{
do_popup_menu(page, event);
LEAVE("menu shown");
return TRUE;
}
LEAVE("other click");
return FALSE;
}
/* CS: Code copied from gtk/gtkactiongroup.c */
static gchar *
dgettext_swapped (const gchar *msgid,
const gchar *domainname)
{
/* CS: Pass this through dgettext if and only if msgid is
nonempty. */
return (msgid && *msgid) ? dgettext (domainname, msgid) : (gchar*) msgid;
}
/*
* This is copied into GnuCash from Gtk in order to fix problems when
* empty msgids were passed through gettext().
*
* See http://bugzilla.gnome.org/show_bug.cgi?id=326200 . If that bug
* is fixed in the gtk that we can rely open, then
* gnc_gtk_action_group_set_translation_domain can be replaced by
* gtk_action_group_set_translation_domain again.
*/
void
gnc_gtk_action_group_set_translation_domain (GtkActionGroup *action_group,
const gchar *domain)
{
g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
gtk_action_group_set_translate_func (action_group,
(GtkTranslateFunc)dgettext_swapped,
g_strdup (domain),
g_free);
}
/* CS: End of code copied from gtk/gtkactiongroup.c */
void
gnc_main_window_all_action_set_sensitive (const gchar *action_name,
gboolean sensitive)
{
GList *tmp;
GtkAction *action;
for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
{
action = gnc_main_window_find_action (tmp->data, action_name);
gtk_action_set_sensitive (action, sensitive);
}
}
/** @} */
/** @} */