1 /*
2  * gnc-main-window.c -- GtkWindow which represents the
3  *  GnuCash main window.
4  *
5  * Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
6  * Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of
11  * the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, contact:
20  *
21  * Free Software Foundation           Voice:  +1-617-542-5942
22  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
23  * Boston, MA  02110-1301,  USA       gnu@gnu.org
24  */
25 
26 /** @addtogroup Windows
27     @{ */
28 /** @addtogroup GncMainWindow Main Window functions.
29     @{ */
30 /** @file gnc-main-window.c
31     @brief Functions for adding content to a window.
32     @author Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
33     @author Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
34 */
35 #include <config.h>
36 
37 #include <glib/gi18n.h>
38 #include <gtk/gtk.h>
39 #include <gdk/gdk.h>
40 #include <gdk/gdkkeysyms.h>
41 
42 #include "gnc-plugin.h"
43 #include "gnc-plugin-manager.h"
44 #include "gnc-main-window.h"
45 
46 #include "dialog-options.h"
47 #include "dialog-preferences.h"
48 #include "dialog-reset-warnings.h"
49 #include "dialog-transfer.h"
50 #include "dialog-utils.h"
51 #include "engine-helpers.h"
52 #include "file-utils.h"
53 #include "gnc-component-manager.h"
54 #include "gnc-engine.h"
55 #include "gnc-features.h"
56 #include "gnc-file.h"
57 #include "gnc-filepath-utils.h"
58 #include "gnc-gkeyfile-utils.h"
59 #include "gnc-gnome-utils.h"
60 #include "gnc-gobject-utils.h"
61 #include "gnc-gui-query.h"
62 #include "gnc-hooks.h"
63 #include "gnc-icons.h"
64 #include "gnc-session.h"
65 #include "gnc-state.h"
66 #include "gnc-ui.h"
67 #include "gnc-ui-util.h"
68 #include <gnc-glib-utils.h>
69 #include "gnc-uri-utils.h"
70 #include "gnc-version.h"
71 #include "gnc-warnings.h"
72 #include "gnc-window.h"
73 #include "gnc-prefs.h"
74 #include "option-util.h"
75 // +JSLED
76 //#include "gnc-html.h"
77 #include "gnc-autosave.h"
78 #include "print-session.h"
79 #ifdef MAC_INTEGRATION
80 #include <gtkmacintegration/gtkosxapplication.h>
81 #endif
82 #ifdef HAVE_SYS_STAT_H
83 # define __need_system_sys_stat_h //To block Guile-2.0's evil substitute
84 # include <sys/types.h>
85 # include <sys/stat.h> // for stat(2)
86 #endif
87 
88 /** Names of signals generated by the main window. */
89 enum
90 {
91     PAGE_ADDED,
92     PAGE_CHANGED,
93     LAST_SIGNAL
94 };
95 
96 /** This label is used to provide a mapping from a visible page widget
97  *  back to the corresponding GncPluginPage object. */
98 #define PLUGIN_PAGE_LABEL "plugin-page"
99 
100 #define PLUGIN_PAGE_CLOSE_BUTTON "close-button"
101 #define PLUGIN_PAGE_TAB_LABEL    "label"
102 
103 #define GNC_PREF_SHOW_CLOSE_BUTTON    "tab-close-buttons"
104 #define GNC_PREF_TAB_NEXT_RECENT      "tab-next-recent"
105 #define GNC_PREF_TAB_POSITION_TOP     "tab-position-top"
106 #define GNC_PREF_TAB_POSITION_BOTTOM  "tab-position-bottom"
107 #define GNC_PREF_TAB_POSITION_LEFT    "tab-position-left"
108 #define GNC_PREF_TAB_POSITION_RIGHT   "tab-position-right"
109 #define GNC_PREF_TAB_WIDTH            "tab-width"
110 #define GNC_PREF_TAB_COLOR            "show-account-color-tabs"
111 #define GNC_PREF_SAVE_CLOSE_EXPIRES   "save-on-close-expires"
112 #define GNC_PREF_SAVE_CLOSE_WAIT_TIME "save-on-close-wait-time"
113 #define GNC_PREF_TAB_OPEN_ADJACENT    "tab-open-adjacent"
114 
115 #define GNC_MAIN_WINDOW_NAME "GncMainWindow"
116 
117 #define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
118 
119 /* Static Globals *******************************************************/
120 
121 /** The debugging module that this .o belongs to.  */
122 static QofLogModule log_module = GNC_MOD_GUI;
123 /** A pointer to the parent class of an embedded window. */
124 static GObjectClass *parent_class = NULL;
125 /** An identifier that indicates a "main" window. */
126 static GQuark window_type = 0;
127 /** A list of all extant main windows. This is for convenience as the
128  *  same information can be obtained from the object tracking code. */
129 static GList *active_windows = NULL;
130 /** Count down timer for the save changes dialog. If the timer reaches zero
131  *  any changes will be saved and the save dialog closed automatically */
132 static guint secs_to_save = 0;
133 #define MSG_AUTO_SAVE _("Changes will be saved automatically in %u seconds")
134 
135 /* Declarations *********************************************************/
136 static void gnc_main_window_class_init (GncMainWindowClass *klass);
137 static void gnc_main_window_init (GncMainWindow *window,
138 		                  void *data);
139 static void gnc_main_window_finalize (GObject *object);
140 static void gnc_main_window_destroy (GtkWidget *widget);
141 
142 static void gnc_main_window_setup_window (GncMainWindow *window);
143 static void gnc_window_main_window_init (GncWindowIface *iface);
144 #ifndef MAC_INTEGRATION
145 static void gnc_main_window_update_all_menu_items (void);
146 #endif
147 
148 /* Callbacks */
149 static void gnc_main_window_add_widget (GtkUIManager *merge, GtkWidget *widget, GncMainWindow *window);
150 static void gnc_main_window_switch_page (GtkNotebook *notebook, gpointer *notebook_page, gint pos, GncMainWindow *window);
151 static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window);
152 static void gnc_main_window_plugin_added (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
153 static void gnc_main_window_plugin_removed (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
154 static void gnc_main_window_engine_commit_error_callback( gpointer data, QofBackendError errcode );
155 
156 /* Command callbacks */
157 static void gnc_main_window_cmd_page_setup (GtkAction *action, GncMainWindow *window);
158 static void gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window);
159 static void gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window);
160 static void gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window);
161 static void gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window);
162 static void gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window);
163 static void gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window);
164 static void gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window);
165 static void gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window);
166 static void gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window);
167 static void gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window);
168 static void gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window);
169 static void gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window);
170 static void gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window);
171 static void gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window);
172 static void gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window);
173 #ifndef MAC_INTEGRATION
174 static void gnc_main_window_cmd_window_raise (GtkAction *action, GtkRadioAction *current, GncMainWindow *window);
175 #endif
176 static void gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window);
177 static void gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window);
178 static void gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window);
179 
180 static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
181 static GtkWidget *gnc_main_window_get_statusbar (GncWindow *window_in);
182 static void statusbar_notification_lastmodified(void);
183 static void gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data);
184 static void gnc_main_window_remove_prefs (GncMainWindow *window);
185 
186 #ifdef MAC_INTEGRATION
187 static void gnc_quartz_shutdown(GtkosxApplication *theApp, gpointer data);
188 static gboolean gnc_quartz_should_quit(GtkosxApplication *theApp, GncMainWindow *window);
189 static void gnc_quartz_set_menu(GncMainWindow* window);
190 #endif
191 
192 /** The instance private data structure for an embedded window
193  *  object. */
194 typedef struct GncMainWindowPrivate
195 {
196     /** The dock (vbox) at the top of the window containing the
197      *  menubar and toolbar.  These items are generated by the UI
198      *  manager and stored here when the UI manager provides them
199      *  to the main window. */
200     GtkWidget *menu_dock;
201     /** The toolbar created by the UI manager.  This pointer
202      * provides easy access for showing/hiding the toolbar. */
203     GtkWidget *toolbar;
204     /** The notebook containing all the pages in this window. */
205     GtkWidget *notebook;
206     /** Show account color as background on tabs */
207     gboolean show_color_tabs;
208     /** A pointer to the status bar at the bottom edge of the
209      *  window.  This pointer provides easy access for
210      *  updating/showing/hiding the status bar. */
211     GtkWidget *statusbar;
212     /** A pointer to the progress bar at the bottom right of the
213      *  window that is contained in the status bar.  This pointer
214      *  provides easy access for updating the progressbar. */
215     GtkWidget *progressbar;
216 
217     /** The group of all actions provided by the main window
218      *  itself.  This does not include any action provided by menu
219      *  or content plugins. */
220     GtkActionGroup *action_group;
221 
222     /** A list of all pages that are installed in this window. */
223     GList *installed_pages;
224     /** A list of pages in order of use (most recent -> least recent) */
225     GList *usage_order;
226     /** The currently selected page. */
227     GncPluginPage *current_page;
228     /** The identifier for this window's engine event handler. */
229     gint event_handler_id;
230     /** Array for window position. */
231     gint pos[2];
232     /** A hash table of all action groups that have been installed
233      *  into this window. The keys are the name of an action
234      *  group, the values are structures of type
235      *  MergedActionEntry. */
236     GHashTable *merged_actions_table;
237     /** Set when restoring plugin pages */
238     gboolean restoring_pages;
239 } GncMainWindowPrivate;
240 
241 GNC_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_WINDOW,
242                         G_ADD_PRIVATE (GncMainWindow)
243                         G_IMPLEMENT_INTERFACE (GNC_TYPE_WINDOW,
244 		                               gnc_window_main_window_init))
245 
246 #define GNC_MAIN_WINDOW_GET_PRIVATE(o)  \
247    ((GncMainWindowPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_MAIN_WINDOW))
248 
249 /** This data structure maintains information about one action groups
250  *  that has been installed in this window. */
251 typedef struct
252 {
253     /** The merge identifier for this action group.  This number
254      *  is provided by the UI manager. */
255     guint merge_id;
256     /** The action group itself.  This contains all actions added
257      *  by a single menu or content plugin. */
258     GtkActionGroup *action_group;
259 } MergedActionEntry;
260 
261 /** A holding place for all the signals generated by the main window
262  *  code. */
263 static guint main_window_signals[LAST_SIGNAL] = { 0 };
264 
265 
266 /** An array of all of the actions provided by the main window code.
267  *  This includes some placeholder actions for the menus that are
268  *  visible in the menu bar but have no action associated with
269  *  them. */
270 static GtkActionEntry gnc_menu_actions [] =
271 {
272     /* Toplevel */
273 
274     { "FileAction", NULL, N_("_File"), NULL, NULL, NULL, },
275     { "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
276     { "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
277     { "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
278     { "TransactionAction", NULL, N_("Tra_nsaction"), NULL, NULL, NULL },
279     { "ReportsAction", NULL, N_("_Reports"), NULL, NULL, NULL },
280     { "ToolsAction", NULL, N_("_Tools"), NULL, NULL, NULL },
281     { "ExtensionsAction", NULL, N_("E_xtensions"), NULL, NULL, NULL },
282     { "WindowsAction", NULL, N_("_Windows"), NULL, NULL, NULL },
283     { "HelpAction", NULL, N_("_Help"), NULL, NULL, NULL },
284 
285     /* File menu */
286 
287     { "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL },
288     { "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL },
289     {
290         "FilePrintAction", "document-print", N_("_Print..."), "<primary>p",
291         N_("Print the currently active page"), NULL
292     },
293 #ifndef GTK_STOCK_PAGE_SETUP
294 #    define GTK_STOCK_PAGE_SETUP NULL
295 #endif
296     {
297         "FilePageSetupAction", "document-page-setup", N_("Pa_ge Setup..."), "<primary><shift>p",
298         N_("Specify the page size and orientation for printing"),
299         G_CALLBACK (gnc_main_window_cmd_page_setup)
300     },
301     {
302         "FilePropertiesAction", "document-properties", N_("Proper_ties"), "<Alt>Return",
303         N_("Edit the properties of the current file"),
304         G_CALLBACK (gnc_main_window_cmd_file_properties)
305     },
306     {
307         "FileCloseAction", "window-close", N_("_Close"), "<primary>W",
308         N_("Close the currently active page"),
309         G_CALLBACK (gnc_main_window_cmd_file_close)
310     },
311     {
312         "FileQuitAction", "application-exit", N_("_Quit"), "<primary>Q",
313         N_("Quit this application"),
314         G_CALLBACK (gnc_main_window_cmd_file_quit)
315     },
316 
317     /* Edit menu */
318 
319     {
320         "EditCutAction", "edit-cut", N_("Cu_t"), "<primary>X",
321         N_("Cut the current selection and copy it to clipboard"),
322         G_CALLBACK (gnc_main_window_cmd_edit_cut)
323     },
324     {
325         "EditCopyAction", "edit-copy", N_("_Copy"), "<primary>C",
326         N_("Copy the current selection to clipboard"),
327         G_CALLBACK (gnc_main_window_cmd_edit_copy)
328     },
329     {
330         "EditPasteAction", "edit-paste", N_("_Paste"), "<primary>V",
331         N_("Paste the clipboard content at the cursor position"),
332         G_CALLBACK (gnc_main_window_cmd_edit_paste)
333     },
334     {
335         "EditPreferencesAction", "preferences-system", N_("Pr_eferences"), NULL,
336         N_("Edit the global preferences of GnuCash"),
337         G_CALLBACK (gnc_main_window_cmd_edit_preferences)
338     },
339 
340     /* View menu */
341 
342     {
343         "ViewSortByAction", NULL, N_("_Sort By..."), NULL,
344         N_("Select sorting criteria for this page view"), NULL
345     },
346     {
347         "ViewFilterByAction", NULL, N_("_Filter By..."), NULL,
348         N_("Select the account types that should be displayed."), NULL
349     },
350     {
351         "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
352         N_("Refresh this window"),
353         G_CALLBACK (gnc_main_window_cmd_view_refresh)
354     },
355 
356     /* Actions menu */
357 
358     { "ScrubMenuAction", NULL, N_("_Check & Repair"), NULL, NULL, NULL },
359     {
360         "ActionsForgetWarningsAction", NULL, N_("Reset _Warnings..."), NULL,
361         N_("Reset the state of all warning messages so they will be shown again."),
362         G_CALLBACK (gnc_main_window_cmd_actions_reset_warnings)
363     },
364     {
365         "ActionsRenamePageAction", NULL, N_("Re_name Page"), NULL,
366         N_("Rename this page."),
367         G_CALLBACK (gnc_main_window_cmd_actions_rename_page)
368     },
369 
370     /* Windows menu */
371 
372     {
373         "WindowNewAction", NULL, N_("_New Window"), NULL,
374         N_("Open a new top-level GnuCash window."),
375         G_CALLBACK (gnc_main_window_cmd_window_new)
376     },
377     {
378         "WindowMovePageAction", NULL, N_("New Window with _Page"), NULL,
379         N_("Move the current page to a new top-level GnuCash window."),
380         G_CALLBACK (gnc_main_window_cmd_window_move_page)
381     },
382 
383     /* Help menu */
384 
385     {
386         "HelpTutorialAction", "help-browser", N_("Tutorial and Concepts _Guide"), "<primary>H",
387         N_("Open the GnuCash Tutorial"),
388         G_CALLBACK (gnc_main_window_cmd_help_tutorial)
389     },
390     {
391         "HelpContentsAction", "help-browser", N_("_Contents"), "F1",
392         N_("Open the GnuCash Help"),
393         G_CALLBACK (gnc_main_window_cmd_help_contents)
394     },
395     {
396         "HelpAboutAction", "help-about", N_("_About"), NULL,
397         N_("About GnuCash"),
398         G_CALLBACK (gnc_main_window_cmd_help_about)
399     },
400 };
401 /** The number of actions provided by the main window. */
402 static guint gnc_menu_n_actions = G_N_ELEMENTS (gnc_menu_actions);
403 
404 /** An array of all of the toggle action provided by the main window
405  *  code. */
406 static GtkToggleActionEntry toggle_actions [] =
407 {
408     {
409         "ViewToolbarAction", NULL, N_("_Toolbar"), NULL,
410         N_("Show/hide the toolbar on this window"),
411         G_CALLBACK (gnc_main_window_cmd_view_toolbar), TRUE
412     },
413     {
414         "ViewSummaryAction", NULL, N_("Su_mmary Bar"), NULL,
415         N_("Show/hide the summary bar on this window"),
416         G_CALLBACK (gnc_main_window_cmd_view_summary), TRUE
417     },
418     {
419         "ViewStatusbarAction", NULL, N_("Stat_us Bar"), NULL,
420         N_("Show/hide the status bar on this window"),
421         G_CALLBACK (gnc_main_window_cmd_view_statusbar), TRUE
422     },
423 };
424 /** The number of toggle actions provided by the main window. */
425 static guint n_toggle_actions = G_N_ELEMENTS (toggle_actions);
426 
427 #ifndef MAC_INTEGRATION
428 /** An array of all of the radio action provided by the main window
429  *  code. */
430 static GtkRadioActionEntry radio_entries [] =
431 {
432     { "Window0Action", NULL, N_("Window _1"), NULL, NULL, 0 },
433     { "Window1Action", NULL, N_("Window _2"), NULL, NULL, 1 },
434     { "Window2Action", NULL, N_("Window _3"), NULL, NULL, 2 },
435     { "Window3Action", NULL, N_("Window _4"), NULL, NULL, 3 },
436     { "Window4Action", NULL, N_("Window _5"), NULL, NULL, 4 },
437     { "Window5Action", NULL, N_("Window _6"), NULL, NULL, 5 },
438     { "Window6Action", NULL, N_("Window _7"), NULL, NULL, 6 },
439     { "Window7Action", NULL, N_("Window _8"), NULL, NULL, 7 },
440     { "Window8Action", NULL, N_("Window _9"), NULL, NULL, 8 },
441     { "Window9Action", NULL, N_("Window _0"), NULL, NULL, 9 },
442 };
443 
444 /** The number of radio actions provided by the main window. */
445 static guint n_radio_entries = G_N_ELEMENTS (radio_entries);
446 #endif
447 
448 /** These are the "important" actions provided by the main window.
449  *  Their labels will appear when the toolbar is set to "Icons and
450  *  important text" (e.g. GTK_TOOLBAR_BOTH_HORIZ) mode. */
451 static const gchar *gnc_menu_important_actions[] =
452 {
453     "FileCloseAction",
454     NULL,
455 };
456 
457 
458 /** The following are in the main window so they will always be
459  *  present in the menu structure, but they are never sensitive.
460  *  These actions should be overridden in child windows where they
461  *  have meaning. */
462 static const gchar *always_insensitive_actions[] =
463 {
464     "FilePrintAction",
465     NULL
466 };
467 
468 
469 /** The following items in the main window should be made insensitive
470  *  at startup time.  The sensitivity will be changed by some later
471  *  event. */
472 static const gchar *initially_insensitive_actions[] =
473 {
474     "FileCloseAction",
475     NULL
476 };
477 
478 
479 /** The following are in the main window so they will always be
480  *  present in the menu structure, but they are always hidden.
481  *  These actions should be overridden in child windows where they
482  *  have meaning. */
483 static const gchar *always_hidden_actions[] =
484 {
485     "ViewSortByAction",
486     "ViewFilterByAction",
487     NULL
488 };
489 
490 
491 /** If a page is flagged as immutable, then the following actions
492  *  cannot be performed on that page. */
493 static const gchar *immutable_page_actions[] =
494 {
495     "FileCloseAction",
496     NULL
497 };
498 
499 
500 /** The following actions can only be performed if there are multiple
501  *  pages in a window. */
502 static const gchar *multiple_page_actions[] =
503 {
504     "WindowMovePageAction",
505     NULL
506 };
507 
508 
509 /************************************************************
510  *                                                          *
511  ************************************************************/
512 #define WINDOW_COUNT            "WindowCount"
513 #define WINDOW_STRING           "Window %d"
514 #define WINDOW_GEOMETRY         "WindowGeometry"
515 #define WINDOW_POSITION         "WindowPosition"
516 #define WINDOW_MAXIMIZED        "WindowMaximized"
517 #define TOOLBAR_VISIBLE         "ToolbarVisible"
518 #define STATUSBAR_VISIBLE       "StatusbarVisible"
519 #define SUMMARYBAR_VISIBLE      "SummarybarVisible"
520 #define WINDOW_FIRSTPAGE        "FirstPage"
521 #define WINDOW_PAGECOUNT        "PageCount"
522 #define WINDOW_PAGEORDER        "PageOrder"
523 #define PAGE_TYPE               "PageType"
524 #define PAGE_NAME               "PageName"
525 #define PAGE_STRING             "Page %d"
526 
527 typedef struct
528 {
529     GKeyFile *key_file;
530     const gchar *group_name;
531     gint window_num;
532     gint page_num;
533     gint page_offset;
534 } GncMainWindowSaveData;
535 
536 
537 gboolean
gnc_main_window_is_restoring_pages(GncMainWindow * window)538 gnc_main_window_is_restoring_pages (GncMainWindow *window)
539 {
540     GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
541     return priv->restoring_pages;
542 }
543 
544 
545 /*  Iterator function to walk all pages in all windows, calling the
546  *  specified function for each page. */
547 void
gnc_main_window_foreach_page(GncMainWindowPageFunc fn,gpointer user_data)548 gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data)
549 {
550     GncMainWindowPrivate *priv;
551     GncMainWindow *window;
552     GncPluginPage *page;
553     GList *w, *p;
554 
555     ENTER(" ");
556     for (w = active_windows; w; w = g_list_next(w))
557     {
558         window = w->data;
559         priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
560         for (p = priv->installed_pages; p; p = g_list_next(p))
561         {
562             page = p->data;
563             fn(page, user_data);
564         }
565     }
566     LEAVE(" ");
567 }
568 
569 
570 /** Restore a single page to a window.  This function calls a page
571  *  specific function to create the actual page.  It then handles all
572  *  the common tasks such as insuring the page is installed into a
573  *  window, updating the page name, and anything else that might be
574  *  common to all pages.
575  *
576  *  @param window The GncMainWindow where the new page will be
577  *  installed.
578  *
579  *  @param data A data structure containing state about the
580  *  window/page restoration process. */
581 static void
gnc_main_window_restore_page(GncMainWindow * window,GncMainWindowSaveData * data)582 gnc_main_window_restore_page (GncMainWindow *window,
583                               GncMainWindowSaveData *data)
584 {
585     GncMainWindowPrivate *priv;
586     GncPluginPage *page;
587     gchar *page_group, *page_type = NULL, *name = NULL;
588     const gchar *class_type;
589     GError *error = NULL;
590 
591     ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
592           window, data, data->key_file, data->window_num, data->page_offset,
593           data->page_num);
594 
595     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
596     page_group = g_strdup_printf(PAGE_STRING,
597                                  data->page_offset + data->page_num);
598     page_type = g_key_file_get_string(data->key_file, page_group,
599                                       PAGE_TYPE, &error);
600     if (error)
601     {
602         g_warning("error reading group %s key %s: %s",
603                   page_group, PAGE_TYPE, error->message);
604         goto cleanup;
605     }
606 
607     /* See if the page already exists. */
608     page = g_list_nth_data(priv->installed_pages, data->page_num);
609     if (page)
610     {
611         class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
612         if (strcmp(page_type, class_type) != 0)
613         {
614             g_warning("error: page types don't match: state %s, existing page %s",
615                       page_type, class_type);
616             goto cleanup;
617         }
618     }
619     else
620     {
621         /* create and install the page */
622         page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
623                                              data->key_file, page_group);
624         if (page)
625         {
626             /* Does the page still need to be installed into the window? */
627             if (page->window == NULL)
628             {
629                 gnc_plugin_page_set_use_new_window(page, FALSE);
630                 gnc_main_window_open_page(window, page);
631             }
632 
633             /* Restore the page name */
634             name = g_key_file_get_string(data->key_file, page_group,
635                                          PAGE_NAME, &error);
636             if (error)
637             {
638                 g_warning("error reading group %s key %s: %s",
639                           page_group, PAGE_NAME, error->message);
640                 /* Fall through and still show the page. */
641             }
642             else
643             {
644                 DEBUG("updating page name for %p to %s.", page, name);
645                 main_window_update_page_name(page, name);
646                 g_free(name);
647             }
648         }
649     }
650 
651     LEAVE("ok");
652 cleanup:
653     if (error)
654         g_error_free(error);
655     if (page_type)
656         g_free(page_type);
657     g_free(page_group);
658 }
659 
660 
661 /** Restore all the pages in a given window.  This function restores
662  *  all the window specific attributes, then calls a helper function
663  *  to restore all the pages that are contained in the window.
664  *
665  *  @param window The GncMainWindow whose pages should be restored.
666  *
667  *  @param data A data structure containing state about the
668  *  window/page restoration process. */
669 static void
gnc_main_window_restore_window(GncMainWindow * window,GncMainWindowSaveData * data)670 gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
671 {
672     GncMainWindowPrivate *priv;
673     GtkAction *action;
674     gint *pos, *geom, *order;
675     gsize length;
676     gboolean max, visible, desired_visibility;
677     gchar *window_group;
678     gint page_start, page_count, i;
679     GError *error = NULL;
680 
681     /* Setup */
682     ENTER("window %p, data %p (key file %p, window %d)",
683           window, data, data->key_file, data->window_num);
684     window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);
685 
686     /* Deal with the uncommon case that the state file defines a window
687      * but no pages. An example to get in such a situation can be found
688      * here: https://bugs.gnucash.org/show_bug.cgi?id=436479#c3
689      * If this happens on the first window, we will open an account hierarchy
690      * to avoid confusing the user by presenting a completely empty window.
691      * If it happens on a later window, we'll just skip restoring that window.
692      */
693     if (!g_key_file_has_group (data->key_file, window_group) ||
694         !g_key_file_has_key (data->key_file, window_group, WINDOW_PAGECOUNT, &error))
695     {
696         if (window)
697         {
698             gnc_main_window_restore_default_state (window);
699             PINFO ("saved state had an empty first main window\n"
700                    "an account hierarchy page was added automatically to avoid confusion");
701         }
702         else
703             PINFO ("saved state had an empty main window, skipping restore");
704 
705         goto cleanup;
706     }
707 
708 
709     /* Get this window's notebook info */
710     page_count = g_key_file_get_integer(data->key_file,
711                                         window_group, WINDOW_PAGECOUNT, &error);
712     if (error)
713     {
714         g_warning("error reading group %s key %s: %s",
715                   window_group, WINDOW_PAGECOUNT, error->message);
716         goto cleanup;
717     }
718     if (page_count == 0)
719     {
720         /* Should never happen, but has during alpha testing. Having this
721          * check doesn't hurt anything. */
722         goto cleanup;
723     }
724     page_start = g_key_file_get_integer(data->key_file,
725                                         window_group, WINDOW_FIRSTPAGE, &error);
726     if (error)
727     {
728         g_warning("error reading group %s key %s: %s",
729                   window_group, WINDOW_FIRSTPAGE, error->message);
730         goto cleanup;
731     }
732 
733     /* Build a window if we don't already have one */
734     if (window == NULL)
735     {
736         DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
737         DEBUG("active_windows %p.", active_windows);
738         if (active_windows)
739             DEBUG("first window %p.", active_windows->data);
740         window = gnc_main_window_new();
741     }
742 
743     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
744 
745     /* Get the window coordinates, etc. */
746     geom = g_key_file_get_integer_list(data->key_file, window_group,
747                                        WINDOW_GEOMETRY, &length, &error);
748     if (error)
749     {
750         g_warning("error reading group %s key %s: %s",
751                   window_group, WINDOW_GEOMETRY, error->message);
752         g_error_free(error);
753         error = NULL;
754     }
755     else if (length != 2)
756     {
757         g_warning("invalid number of values for group %s key %s",
758                   window_group, WINDOW_GEOMETRY);
759     }
760     else
761     {
762         gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
763         DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
764     }
765     /* keep the geometry for a test whether the windows position
766        is offscreen */
767 
768     pos = g_key_file_get_integer_list(data->key_file, window_group,
769                                       WINDOW_POSITION, &length, &error);
770     if (error)
771     {
772         g_warning("error reading group %s key %s: %s",
773                   window_group, WINDOW_POSITION, error->message);
774         g_error_free(error);
775         error = NULL;
776     }
777     else if (length != 2)
778     {
779         g_warning("invalid number of values for group %s key %s",
780                   window_group, WINDOW_POSITION);
781     }
782     /* Prevent restoring coordinates if this would move the window off-screen */
783     else if ((pos[0] + (geom ? geom[0] : 0) < 0) ||
784              (pos[0] > gdk_screen_width()) ||
785              (pos[1] + (geom ? geom[1] : 0) < 0) ||
786              (pos[1] > gdk_screen_height()))
787     {
788         DEBUG("position %dx%d, size%dx%d is offscreen; will not move",
789                 pos[0], pos[1], geom ? geom[0] : 0, geom ? geom[1] : 0);
790     }
791     else
792     {
793         gtk_window_move(GTK_WINDOW(window), pos[0], pos[1]);
794         priv->pos[0] = pos[0];
795         priv->pos[1] = pos[1];
796         DEBUG("window (%p) position %dx%d", window, pos[0], pos[1]);
797     }
798     if (geom)
799     {
800         g_free(geom);
801     }
802     if (pos)
803     {
804         g_free(pos);
805     }
806 
807     max = g_key_file_get_boolean(data->key_file, window_group,
808                                  WINDOW_MAXIMIZED, &error);
809     if (error)
810     {
811         g_warning("error reading group %s key %s: %s",
812                   window_group, WINDOW_MAXIMIZED, error->message);
813         g_error_free(error);
814         error = NULL;
815     }
816     else if (max)
817     {
818         gtk_window_maximize(GTK_WINDOW(window));
819     }
820 
821     /* Common view menu items */
822     action = gnc_main_window_find_action(window, "ViewToolbarAction");
823     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
824     desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
825                          TOOLBAR_VISIBLE, &error);
826     if (error)
827     {
828         g_warning("error reading group %s key %s: %s",
829                   window_group, TOOLBAR_VISIBLE, error->message);
830         g_error_free(error);
831         error = NULL;
832     }
833     else if (visible != desired_visibility)
834     {
835         gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
836     }
837 
838     action = gnc_main_window_find_action(window, "ViewSummaryAction");
839     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
840     desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
841                          SUMMARYBAR_VISIBLE, &error);
842     if (error)
843     {
844         g_warning("error reading group %s key %s: %s",
845                   window_group, TOOLBAR_VISIBLE, error->message);
846         g_error_free(error);
847         error = NULL;
848     }
849     else if (visible != desired_visibility)
850     {
851         gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
852     }
853 
854     action = gnc_main_window_find_action(window, "ViewStatusbarAction");
855     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
856     desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
857                          STATUSBAR_VISIBLE, &error);
858     if (error)
859     {
860         g_warning("error reading group %s key %s: %s",
861                   window_group, TOOLBAR_VISIBLE, error->message);
862         g_error_free(error);
863         error = NULL;
864     }
865     else if (visible != desired_visibility)
866     {
867         gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
868     }
869     priv->restoring_pages = TRUE;
870     /* Now populate the window with pages. */
871     for (i = 0; i < page_count; i++)
872     {
873         data->page_offset = page_start;
874         data->page_num = i;
875         gnc_main_window_restore_page(window, data);
876 
877         /* give the page a chance to display */
878         while (gtk_events_pending ())
879             gtk_main_iteration ();
880     }
881     priv->restoring_pages = FALSE;
882     /* Restore page ordering within the notebook. Use +1 notation so the
883      * numbers in the page order match the page sections, at least for
884      * the one window case. */
885     order = g_key_file_get_integer_list(data->key_file, window_group,
886                                         WINDOW_PAGEORDER, &length, &error);
887     if (error)
888     {
889         g_warning("error reading group %s key %s: %s",
890                   window_group, WINDOW_PAGEORDER, error->message);
891         g_error_free(error);
892         error = NULL;
893     }
894     else if (length != page_count)
895     {
896         g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %d",
897                   window_group, WINDOW_PAGEORDER, length, page_count);
898     }
899     else
900     {
901         /* Dump any list that might exist */
902         g_list_free(priv->usage_order);
903         priv->usage_order = NULL;
904         /* Now rebuild the list from the key file. */
905         for (i = 0; i < length; i++)
906         {
907             gpointer page = g_list_nth_data(priv->installed_pages, order[i] - 1);
908             if (page)
909             {
910                 priv->usage_order = g_list_append(priv->usage_order, page);
911             }
912         }
913         gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
914                                        order[0] - 1);
915 
916         g_signal_emit_by_name (window, "page_changed",
917                                g_list_nth_data (priv->usage_order, 0));
918     }
919     if (order)
920     {
921         g_free(order);
922     }
923 
924     LEAVE("window %p", window);
925 cleanup:
926     if (error)
927         g_error_free(error);
928     g_free(window_group);
929     if (window)
930         gtk_widget_show (GTK_WIDGET(window));
931 }
932 
933 void
gnc_main_window_restore_all_windows(const GKeyFile * keyfile)934 gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
935 {
936     gint i, window_count;
937     GError *error = NULL;
938     GncMainWindowSaveData data;
939     GncMainWindow *window;
940 
941     /* We use the same struct for reading and for writing, so we cast
942        away the const. */
943     data.key_file = (GKeyFile *) keyfile;
944     window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
945                                           WINDOW_COUNT, &error);
946     if (error)
947     {
948         g_warning("error reading group %s key %s: %s",
949                   STATE_FILE_TOP, WINDOW_COUNT, error->message);
950         g_error_free(error);
951         LEAVE("can't read count");
952         return;
953     }
954 
955     /* Restore all state information on the open windows.  Window
956        numbers in state file are 1-based. GList indices are 0-based. */
957     gnc_set_busy_cursor (NULL, TRUE);
958     for (i = 0; i < window_count; i++)
959     {
960         data.window_num = i;
961         window = g_list_nth_data(active_windows, i);
962         gnc_main_window_restore_window(window, &data);
963     }
964     gnc_unset_busy_cursor (NULL);
965 
966     statusbar_notification_lastmodified();
967 }
968 
969 void
gnc_main_window_restore_default_state(GncMainWindow * window)970 gnc_main_window_restore_default_state(GncMainWindow *window)
971 {
972     GtkAction *action;
973 
974     /* The default state should be to have an Account Tree page open
975      * in the window. */
976     DEBUG("no saved state file");
977     if (!window)
978         window = g_list_nth_data(active_windows, 0);
979     gtk_widget_show (GTK_WIDGET(window));
980     action = gnc_main_window_find_action(window, "ViewAccountTreeAction");
981     gtk_action_activate(action);
982 }
983 
984 /** Save the state of a single page to a disk.  This function handles
985  *  all the common tasks such as saving the page type and name, and
986  *  anything else that might be common to all pages.  It then calls a
987  *  page specific function to save the actual page.
988  *
989  *  @param page The GncPluginPage whose state should be saved.
990  *
991  *  @param data A data structure containing state about the
992  *  window/page saving process. */
993 static void
gnc_main_window_save_page(GncPluginPage * page,GncMainWindowSaveData * data)994 gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
995 {
996     gchar *page_group;
997     const gchar *plugin_name, *page_name;
998 
999     ENTER("page %p, data %p (key file %p, window %d, page %d)",
1000           page, data, data->key_file, data->window_num, data->page_num);
1001     plugin_name = gnc_plugin_page_get_plugin_name(page);
1002     page_name = gnc_plugin_page_get_page_name(page);
1003     if (!plugin_name || !page_name)
1004     {
1005         LEAVE("not saving invalid page");
1006         return;
1007     }
1008     page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
1009     g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
1010     g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
1011 
1012     gnc_plugin_page_save_page(page, data->key_file, page_group);
1013     g_free(page_group);
1014     LEAVE(" ");
1015 }
1016 
1017 
1018 /** Saves all the pages in a single window to a disk.  This function
1019  *  saves all the window specific attributes, then calls a helper
1020  *  function to save all the pages that are contained in the window.
1021  *
1022  *  @param window The GncMainWindow whose pages should be saved.
1023  *
1024  *  @param data A data structure containing state about the
1025  *  window/page saving process. */
1026 static void
gnc_main_window_save_window(GncMainWindow * window,GncMainWindowSaveData * data)1027 gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
1028 {
1029     GncMainWindowPrivate *priv;
1030     GtkAction *action;
1031     gint i, num_pages, coords[4], *order;
1032     gboolean maximized, minimized, visible;
1033     gchar *window_group;
1034 
1035     /* Setup */
1036     ENTER("window %p, data %p (key file %p, window %d)",
1037           window, data, data->key_file, data->window_num);
1038     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1039 
1040     /* Check for bogus window structures. */
1041     num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
1042     if (0 == num_pages)
1043     {
1044         LEAVE("empty window %p", window);
1045         return;
1046     }
1047 
1048     /* Save this window's notebook info */
1049     window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
1050     g_key_file_set_integer(data->key_file, window_group,
1051                            WINDOW_PAGECOUNT, num_pages);
1052     g_key_file_set_integer(data->key_file, window_group,
1053                            WINDOW_FIRSTPAGE, data->page_num);
1054 
1055     /* Save page ordering within the notebook. Use +1 notation so the
1056      * numbers in the page order match the page sections, at least for
1057      * the one window case. */
1058     order = g_malloc(sizeof(gint) * num_pages);
1059     for (i = 0; i < num_pages; i++)
1060     {
1061         gpointer page = g_list_nth_data(priv->usage_order, i);
1062         order[i] = g_list_index(priv->installed_pages, page) + 1;
1063     }
1064     g_key_file_set_integer_list(data->key_file, window_group,
1065                                 WINDOW_PAGEORDER, order, num_pages);
1066     g_free(order);
1067 
1068     /* Save the window coordinates, etc. */
1069     gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
1070     gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
1071     maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1072                  & GDK_WINDOW_STATE_MAXIMIZED) != 0;
1073     minimized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1074                  & GDK_WINDOW_STATE_ICONIFIED) != 0;
1075 
1076     if (minimized)
1077     {
1078         gint *pos = priv->pos;
1079         g_key_file_set_integer_list(data->key_file, window_group,
1080                                     WINDOW_POSITION, &pos[0], 2);
1081         DEBUG("window minimized (%p) position %dx%d", window, pos[0], pos[1]);
1082     }
1083     else
1084         g_key_file_set_integer_list(data->key_file, window_group,
1085                                     WINDOW_POSITION, &coords[0], 2);
1086     g_key_file_set_integer_list(data->key_file, window_group,
1087                                 WINDOW_GEOMETRY, &coords[2], 2);
1088     g_key_file_set_boolean(data->key_file, window_group,
1089                            WINDOW_MAXIMIZED, maximized);
1090     DEBUG("window (%p) position %dx%d, size %dx%d, %s", window,  coords[0], coords[1],
1091           coords[2], coords[3],
1092           maximized ? "maximized" : "not maximized");
1093 
1094     /* Common view menu items */
1095     action = gnc_main_window_find_action(window, "ViewToolbarAction");
1096     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1097     g_key_file_set_boolean(data->key_file, window_group,
1098                            TOOLBAR_VISIBLE, visible);
1099     action = gnc_main_window_find_action(window, "ViewSummaryAction");
1100     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1101     g_key_file_set_boolean(data->key_file, window_group,
1102                            SUMMARYBAR_VISIBLE, visible);
1103     action = gnc_main_window_find_action(window, "ViewStatusbarAction");
1104     visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1105     g_key_file_set_boolean(data->key_file, window_group,
1106                            STATUSBAR_VISIBLE, visible);
1107 
1108     /* Save individual pages in this window */
1109     g_list_foreach(priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
1110 
1111     g_free(window_group);
1112     LEAVE("window %p", window);
1113 }
1114 
1115 void
gnc_main_window_save_all_windows(GKeyFile * keyfile)1116 gnc_main_window_save_all_windows(GKeyFile *keyfile)
1117 {
1118     GncMainWindowSaveData data;
1119 
1120     /* Set up the iterator data structures */
1121     data.key_file = keyfile;
1122     data.window_num = 1;
1123     data.page_num = 1;
1124 
1125     g_key_file_set_integer(data.key_file,
1126                            STATE_FILE_TOP, WINDOW_COUNT,
1127                            g_list_length(active_windows));
1128     /* Dump all state information on the open windows */
1129     g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
1130 }
1131 
1132 
1133 gboolean
gnc_main_window_finish_pending(GncMainWindow * window)1134 gnc_main_window_finish_pending (GncMainWindow *window)
1135 {
1136     GncMainWindowPrivate *priv;
1137     GList *item;
1138 
1139     g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
1140 
1141     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1142     for (item = priv->installed_pages; item; item = g_list_next(item))
1143     {
1144         if (!gnc_plugin_page_finish_pending(item->data))
1145         {
1146             return FALSE;
1147         }
1148     }
1149     return TRUE;
1150 }
1151 
1152 
1153 gboolean
gnc_main_window_all_finish_pending(void)1154 gnc_main_window_all_finish_pending (void)
1155 {
1156     const GList *windows, *item;
1157 
1158     windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
1159     for (item = windows; item; item = g_list_next(item))
1160     {
1161         if (!gnc_main_window_finish_pending(item->data))
1162         {
1163             return FALSE;
1164         }
1165     }
1166     if (gnc_gui_refresh_suspended ())
1167     {
1168         gnc_warning_dialog (NULL, "%s", "An operation is still running, wait for it to complete before quitting.");
1169         return FALSE;
1170     }
1171     return TRUE;
1172 }
1173 
1174 
1175 /** See if the page already exists.  For each open window, look
1176  *  through the list of pages installed in that window and see if the
1177  *  specified page is there.
1178  *
1179  *  @internal
1180  *
1181  *  @param page The page to search for.
1182  *
1183  *  @return TRUE if the page is present in the window, FALSE otherwise.
1184  */
1185 static gboolean
gnc_main_window_page_exists(GncPluginPage * page)1186 gnc_main_window_page_exists (GncPluginPage *page)
1187 {
1188     GncMainWindow *window;
1189     GncMainWindowPrivate *priv;
1190     GList *walker;
1191 
1192     for (walker = active_windows; walker; walker = g_list_next(walker))
1193     {
1194         window = walker->data;
1195         priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1196         if (g_list_find(priv->installed_pages, page))
1197         {
1198             return TRUE;
1199         }
1200     }
1201     return FALSE;
1202 }
1203 
auto_save_countdown(GtkWidget * dialog)1204 static gboolean auto_save_countdown (GtkWidget *dialog)
1205 {
1206     GtkWidget *label;
1207     gchar *timeoutstr = NULL;
1208 
1209     /* Stop count down if user closed the dialog since the last time we were called */
1210     if (!GTK_IS_DIALOG (dialog))
1211         return FALSE; /* remove timer */
1212 
1213     /* Stop count down if count down text can't be updated */
1214     label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
1215     if (!GTK_IS_LABEL (label))
1216         return FALSE; /* remove timer */
1217 
1218     /* Protect against rolling over to MAXUINT */
1219     if (secs_to_save)
1220         --secs_to_save;
1221     DEBUG ("Counting down: %d seconds", secs_to_save);
1222 
1223     timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1224     gtk_label_set_text (GTK_LABEL (label), timeoutstr);
1225     g_free (timeoutstr);
1226 
1227     /* Count down reached 0. Save and close dialog */
1228     if (!secs_to_save)
1229     {
1230         gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1231         return FALSE; /* remove timer */
1232     }
1233 
1234     /* Run another cycle */
1235     return TRUE;
1236 }
1237 
1238 
1239 /** This function prompts the user to save the file with a dialog that
1240  *  follows the HIG guidelines.
1241  *
1242  *  @internal
1243  *
1244  *  @returns This function returns TRUE if the user clicked the Cancel
1245  *  button.  It returns FALSE if the closing of the window should
1246  *  continue.
1247  */
1248 static gboolean
gnc_main_window_prompt_for_save(GtkWidget * window)1249 gnc_main_window_prompt_for_save (GtkWidget *window)
1250 {
1251     QofSession *session;
1252     QofBook *book;
1253     GtkWidget *dialog, *msg_area, *label;
1254     gint response;
1255     const gchar *filename, *tmp;
1256     const gchar *title = _("Save changes to file %s before closing?");
1257     /* This should be the same message as in gnc-file.c */
1258     const gchar *message_hours =
1259         _("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
1260     const gchar *message_days =
1261         _("If you don't save, changes from the past %d days and %d hours will be discarded.");
1262     time64 oldest_change;
1263     gint minutes, hours, days;
1264     if (!gnc_current_session_exist())
1265         return FALSE;
1266     session = gnc_get_current_session();
1267     book = qof_session_get_book(session);
1268     if (!qof_book_session_not_saved(book))
1269         return FALSE;
1270     filename = qof_session_get_url(session);
1271     if (!strlen (filename))
1272         filename = _("<unknown>");
1273     if ((tmp = strrchr(filename, '/')) != NULL)
1274         filename = tmp + 1;
1275 
1276     /* Remove any pending auto-save timeouts */
1277     gnc_autosave_remove_timer(book);
1278 
1279     dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1280                                     GTK_DIALOG_MODAL,
1281                                     GTK_MESSAGE_WARNING,
1282                                     GTK_BUTTONS_NONE,
1283                                     title,
1284                                     filename);
1285     oldest_change = qof_book_get_session_dirty_time(book);
1286     minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
1287     hours = minutes / 60;
1288     minutes = minutes % 60;
1289     days = hours / 24;
1290     hours = hours % 24;
1291     if (days > 0)
1292     {
1293         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1294                 message_days, days, hours);
1295     }
1296     else if (hours > 0)
1297     {
1298         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1299                 message_hours, hours, minutes);
1300     }
1301     else
1302     {
1303         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1304                 ngettext("If you don't save, changes from the past %d minute will be discarded.",
1305                          "If you don't save, changes from the past %d minutes will be discarded.",
1306                          minutes), minutes);
1307     }
1308     gtk_dialog_add_buttons(GTK_DIALOG(dialog),
1309                            _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
1310                            _("_Cancel"), GTK_RESPONSE_CANCEL,
1311                            _("_Save"), GTK_RESPONSE_APPLY,
1312                            NULL);
1313     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1314 
1315     /* If requested by the user, add a timeout to the question to save automatically
1316      * if the user doesn't answer after a chosen number of seconds.
1317      */
1318     if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
1319     {
1320         gchar *timeoutstr = NULL;
1321 
1322         secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
1323         timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1324         label = GTK_WIDGET(gtk_label_new (timeoutstr));
1325         g_free (timeoutstr);
1326         gtk_widget_show (label);
1327 
1328         msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
1329         gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
1330         g_object_set (G_OBJECT (label), "xalign", 0.0, NULL);
1331 
1332         g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
1333         g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
1334     }
1335 
1336     response = gtk_dialog_run (GTK_DIALOG (dialog));
1337     gtk_widget_destroy(dialog);
1338 
1339     switch (response)
1340     {
1341     case GTK_RESPONSE_APPLY:
1342         gnc_file_save (GTK_WINDOW (window));
1343         return FALSE;
1344 
1345     case GTK_RESPONSE_CLOSE:
1346         qof_book_mark_session_saved(book);
1347         return FALSE;
1348 
1349     default:
1350         return TRUE;
1351     }
1352 }
1353 
1354 
1355 static void
gnc_main_window_add_plugin(gpointer plugin,gpointer window)1356 gnc_main_window_add_plugin (gpointer plugin,
1357                             gpointer window)
1358 {
1359     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1360     g_return_if_fail (GNC_IS_PLUGIN (plugin));
1361 
1362     ENTER(" ");
1363     gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
1364                               GNC_MAIN_WINDOW (window),
1365                               window_type);
1366     LEAVE(" ");
1367 }
1368 
1369 static void
gnc_main_window_remove_plugin(gpointer plugin,gpointer window)1370 gnc_main_window_remove_plugin (gpointer plugin,
1371                                gpointer window)
1372 {
1373     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1374     g_return_if_fail (GNC_IS_PLUGIN (plugin));
1375 
1376     ENTER(" ");
1377     gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
1378                                    GNC_MAIN_WINDOW (window),
1379                                    window_type);
1380     LEAVE(" ");
1381 }
1382 
1383 
1384 static gboolean
gnc_main_window_timed_quit(gpointer dummy)1385 gnc_main_window_timed_quit (gpointer dummy)
1386 {
1387     if (gnc_file_save_in_progress())
1388         return TRUE;
1389 
1390     gnc_shutdown (0);
1391     return FALSE;
1392 }
1393 
1394 static gboolean
gnc_main_window_quit(GncMainWindow * window)1395 gnc_main_window_quit(GncMainWindow *window)
1396 {
1397     QofSession *session;
1398     gboolean needs_save, do_shutdown = TRUE;
1399     if (gnc_current_session_exist())
1400     {
1401         session = gnc_get_current_session();
1402         needs_save =
1403             qof_book_session_not_saved(qof_session_get_book(session)) &&
1404             !gnc_file_save_in_progress();
1405         do_shutdown = !needs_save ||
1406             (needs_save &&
1407              !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
1408     }
1409     if (do_shutdown)
1410     {
1411         GList *w, *next;
1412 
1413         /* This is not a typical list iteration. There is a possability
1414          * that the window maybe removed from the active_windows list so
1415          * we have to cache the 'next' pointer before executing any code
1416          * in the loop. */
1417         for (w = active_windows; w; w = next)
1418         {
1419             GncMainWindowPrivate *priv;
1420             GncMainWindow *wind = w->data;
1421 
1422             next = g_list_next (w);
1423 
1424             wind->window_quitting = TRUE; // set window_quitting on all windows
1425 
1426             priv = GNC_MAIN_WINDOW_GET_PRIVATE(wind);
1427 
1428             // if there are no pages destroy window
1429             if (priv->installed_pages == NULL)
1430                 gtk_widget_destroy (GTK_WIDGET(wind));
1431         }
1432         /* remove the preference callbacks from the main window */
1433         gnc_main_window_remove_prefs (window);
1434         g_timeout_add(250, gnc_main_window_timed_quit, NULL);
1435         return TRUE;
1436     }
1437     return FALSE;
1438 }
1439 
1440 static gboolean
gnc_main_window_delete_event(GtkWidget * window,GdkEvent * event,gpointer user_data)1441 gnc_main_window_delete_event (GtkWidget *window,
1442                               GdkEvent *event,
1443                               gpointer user_data)
1444 {
1445     static gboolean already_dead = FALSE;
1446 
1447     if (already_dead)
1448         return TRUE;
1449 
1450     if (gnc_list_length_cmp (active_windows, 1) > 0)
1451     {
1452         gint response;
1453         GtkWidget *dialog;
1454         gchar *message = _("This window is closing and will not be restored.");
1455 
1456         dialog = gtk_message_dialog_new (GTK_WINDOW (window),
1457                                          GTK_DIALOG_DESTROY_WITH_PARENT,
1458                                          GTK_MESSAGE_QUESTION,
1459                                          GTK_BUTTONS_NONE,
1460                                          "%s", _("Close Window?"));
1461         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
1462                                                   "%s", message);
1463 
1464         gtk_dialog_add_buttons (GTK_DIALOG(dialog),
1465                               _("_Cancel"), GTK_RESPONSE_CANCEL,
1466                               _("_OK"), GTK_RESPONSE_YES,
1467                                (gchar *)NULL);
1468         gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1469         response = gnc_dialog_run (GTK_DIALOG(dialog), GNC_PREF_WARN_CLOSING_WINDOW_QUESTION);
1470         gtk_widget_destroy (dialog);
1471 
1472         if (response == GTK_RESPONSE_CANCEL)
1473             return TRUE;
1474     }
1475 
1476     if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
1477     {
1478         /* Don't close the window. */
1479         return TRUE;
1480     }
1481 
1482     if (gnc_list_length_cmp (active_windows, 1) > 0)
1483         return FALSE;
1484 
1485     already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
1486     return TRUE;
1487 }
1488 
1489 
1490 /** This function handles any event notifications from the engine.
1491  *  The only event it currently cares about is the deletion of a book.
1492  *  When a book is deleted, it runs through all installed pages
1493  *  looking for pages that reference the just (about to be?) deleted
1494  *  book.  It closes any page it finds so there are no dangling
1495  *  references to the book.
1496  *
1497  *  @internal
1498  *
1499  *  @param entity     The guid the item being added, deleted, etc.
1500  *
1501  *  @param type       The type of the item being added, deleted, etc. This
1502  *                    function only cares about a type of GNC_ID_BOOK.
1503  *
1504  *  @param event_type The type of the event.  This function only cares
1505  *                    about an event type of QOF_EVENT_DESTROY.
1506  *
1507  *  @param user_data  A pointer to the window data structure.
1508  */
1509 static void
gnc_main_window_event_handler(QofInstance * entity,QofEventId event_type,gpointer user_data,gpointer event_data)1510 gnc_main_window_event_handler (QofInstance *entity,  QofEventId event_type,
1511                                gpointer user_data, gpointer event_data)
1512 {
1513     GncMainWindow *window;
1514     GncMainWindowPrivate *priv;
1515     GncPluginPage *page;
1516     GList *item, *next;
1517 
1518     /* hard failures */
1519     g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
1520 
1521     /* soft failures */
1522     if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
1523         return;
1524     if (event_type !=  QOF_EVENT_DESTROY)
1525         return;
1526 
1527     ENTER("entity %p, event %d, window %p, event data %p",
1528           entity, event_type, user_data, event_data);
1529     window = GNC_MAIN_WINDOW(user_data);
1530     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1531 
1532     /* This is not a typical list iteration.  We're removing while
1533      * we iterate, so we have to cache the 'next' pointer before
1534      * executing any code in the loop. */
1535     for (item = priv->installed_pages; item; item = next)
1536     {
1537         next = g_list_next(item);
1538         page = GNC_PLUGIN_PAGE(item->data);
1539         if (gnc_plugin_page_has_book (page, (QofBook *)entity))
1540             gnc_main_window_close_page (page);
1541     }
1542 
1543     if (GTK_IS_WIDGET(window) && window->window_quitting)
1544         gtk_widget_destroy (GTK_WIDGET(window));
1545 
1546     LEAVE(" ");
1547 }
1548 
1549 
1550 /** Generate a title for this window based upon the Gnome Human
1551  *  Interface Guidelines, v2.0.  This title will be used as both the
1552  *  window title and the title of the "Window" menu item associated
1553  *  with the window.
1554  *
1555  *  As a side-effect, the save action is set sensitive iff the book
1556  *  is dirty, and the immutable_page_actions are set sensitive iff the page is
1557  *  mutable.
1558  *
1559  *  @param window The window whose title should be generated.
1560  *
1561  *  @return The title for the window.  It is the callers
1562  *  responsibility to free this string.
1563  *
1564  *  @internal
1565  */
1566 static gchar *
gnc_main_window_generate_title(GncMainWindow * window)1567 gnc_main_window_generate_title (GncMainWindow *window)
1568 {
1569     GncMainWindowPrivate *priv;
1570     GncPluginPage *page;
1571     QofBook *book;
1572     gboolean immutable;
1573     gchar *filename = NULL;
1574     const gchar *uri = NULL;
1575     const gchar *dirty = "";
1576     const gchar *readonly_text = NULL;
1577     gchar *readonly;
1578     gchar *title;
1579 
1580     if (gnc_current_session_exist())
1581     {
1582         uri = qof_session_get_url (gnc_get_current_session ());
1583         book = gnc_get_current_book();
1584         if (qof_book_session_not_saved (book))
1585             dirty = "*";
1586         if (qof_book_is_readonly(book))
1587         {
1588             /* Translators: This string is shown in the window title if this
1589             document is, well, read-only. */
1590             readonly_text = _("(read-only)");
1591         }
1592     }
1593     readonly = (readonly_text != NULL)
1594                ? g_strdup_printf(" %s", readonly_text)
1595                : g_strdup("");
1596 
1597     if (!uri || g_strcmp0 (uri, "") == 0)
1598         filename = g_strdup(_("Unsaved Book"));
1599     else
1600     {
1601         if (gnc_uri_targets_local_fs (uri))
1602         {
1603             /* The filename is a true file.
1604                The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
1605             gchar *path = gnc_uri_get_path ( uri );
1606             filename = g_path_get_basename ( path );
1607             g_free ( path );
1608         }
1609         else
1610         {
1611             /* The filename is composed of database connection parameters.
1612                For this we will show access_method://username@database[:port] */
1613             filename = gnc_uri_normalize_uri (uri, FALSE);
1614         }
1615     }
1616 
1617     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1618     page = priv->current_page;
1619     if (page)
1620     {
1621         /* The Gnome HIG 2.0 recommends the application name not be used. (p16)
1622            but several developers prefer to use it anyway. */
1623         title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
1624                                 gnc_plugin_page_get_page_name(page));
1625     }
1626     else
1627     {
1628         title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
1629     }
1630     /* Update the menus based upon whether this is an "immutable" page. */
1631     immutable = page &&
1632                 g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
1633     gnc_plugin_update_actions(priv->action_group,
1634                               immutable_page_actions,
1635                               "sensitive", !immutable);
1636     /* Trigger sensitivity updtates of other actions such as Save/Revert */
1637     g_signal_emit_by_name (window, "page_changed", page);
1638     g_free( filename );
1639     g_free(readonly);
1640 
1641     return title;
1642 }
1643 
1644 
1645 /** Update the title bar on the specified window.  This routine uses
1646  *  the gnc_main_window_generate_title() function to create the title.
1647  *  It is called whenever the user switched pages in a window, as the
1648  *  title includes the name of the current page.
1649  *
1650  *  @param window The window whose title should be updated.
1651  *
1652  *  @internal
1653  */
1654 static void
gnc_main_window_update_title(GncMainWindow * window)1655 gnc_main_window_update_title (GncMainWindow *window)
1656 {
1657     gchar *title;
1658 
1659     title = gnc_main_window_generate_title(window);
1660     gtk_window_set_title(GTK_WINDOW(window), title);
1661     g_free(title);
1662 }
1663 
1664 static void
gnc_main_window_update_all_titles(void)1665 gnc_main_window_update_all_titles (void)
1666 {
1667     g_list_foreach(active_windows,
1668                    (GFunc)gnc_main_window_update_title,
1669                    NULL);
1670 }
1671 
1672 static void
gnc_main_window_book_dirty_cb(QofBook * book,gboolean dirty,gpointer user_data)1673 gnc_main_window_book_dirty_cb (QofBook *book,
1674                                gboolean dirty,
1675                                gpointer user_data)
1676 {
1677     gnc_main_window_update_all_titles();
1678 
1679     /* Auto-save feature */
1680     gnc_autosave_dirty_handler(book, dirty);
1681 }
1682 
1683 static void
gnc_main_window_attach_to_book(QofSession * session)1684 gnc_main_window_attach_to_book (QofSession *session)
1685 {
1686     QofBook *book;
1687 
1688     g_return_if_fail(session);
1689 
1690     book = qof_session_get_book(session);
1691     qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, NULL);
1692     gnc_main_window_update_all_titles();
1693 #ifndef MAC_INTEGRATION
1694     gnc_main_window_update_all_menu_items();
1695 #endif
1696 }
1697 
1698 static guint gnc_statusbar_notification_messageid = 0;
1699 //#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
1700 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1701 /* Removes the statusbar notification again that has been pushed to the
1702  * statusbar by generate_statusbar_lastmodified_message. */
statusbar_notification_off(gpointer user_data_unused)1703 static gboolean statusbar_notification_off(gpointer user_data_unused)
1704 {
1705     GncMainWindow *mainwindow = GNC_MAIN_WINDOW (gnc_ui_get_main_window (NULL));
1706     //g_warning("statusbar_notification_off\n");
1707     if (gnc_statusbar_notification_messageid == 0)
1708         return FALSE;
1709 
1710     if (mainwindow)
1711     {
1712         GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1713         gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
1714         gnc_statusbar_notification_messageid = 0;
1715     }
1716     else
1717     {
1718         g_warning("oops, no GncMainWindow obtained\n");
1719     }
1720     return FALSE; // should not be called again
1721 }
1722 #endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
1723 
1724 /* Creates a statusbar message stating the last modification time of the opened
1725  * data file. */
generate_statusbar_lastmodified_message()1726 static gchar *generate_statusbar_lastmodified_message()
1727 {
1728     gchar *message = NULL;
1729     const gchar *uri = NULL;
1730 
1731     if (gnc_current_session_exist())
1732     {
1733         uri = qof_session_get_url (gnc_get_current_session ());
1734     }
1735 
1736     if (!(uri && strlen (uri)))
1737         return NULL;
1738     else
1739     {
1740         if (gnc_uri_targets_local_fs (uri))
1741         {
1742             /* The filename is a true file. */
1743             gchar *filepath = gnc_uri_get_path ( uri );
1744             gchar *filename = g_path_get_basename ( filepath );
1745             GFile *file = g_file_new_for_uri (uri);
1746             GFileInfo *info = g_file_query_info (file,
1747                                                  G_FILE_ATTRIBUTE_TIME_MODIFIED,
1748                                                  G_FILE_QUERY_INFO_NONE,
1749                                                  NULL, NULL);
1750 
1751             if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
1752             {
1753                 guint64 modtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1754 
1755                 /* Translators: This is the date and time that is shown in
1756                 the status bar after opening a file: The date and time of
1757                 last modification. The string is a format string using
1758                 boost::date_time's format flags, see the boost docs for an
1759                 explanation of the modifiers. */
1760                 char *time_string = gnc_print_time64 (modtime,
1761                  _("Last modified on %a, %b %d, %Y at %I:%M %p"));
1762                 //g_warning("got time %ld, str=%s\n", mtime, time_string);
1763                 /* Translators: This message appears in the status bar after opening the file. */
1764                 message = g_strdup_printf(_("File %s opened. %s"),
1765                                           filename, time_string);
1766                 free(time_string);
1767             }
1768             else
1769             {
1770                 g_warning("Unable to read mtime for file %s\n", filepath);
1771                 // message is still NULL
1772             }
1773             g_free(filename);
1774             g_free(filepath);
1775             g_object_unref (info);
1776             g_object_unref (file);
1777         }
1778         // If the URI is not a file but a database, we can maybe also show
1779         // something useful, but I have no idea how to obtain this information.
1780     }
1781     return message;
1782 }
1783 
1784 static void
statusbar_notification_lastmodified()1785 statusbar_notification_lastmodified()
1786 {
1787     // First look up the first GncMainWindow to set the statusbar there
1788     GList *iter;
1789     GtkWidget *widget = NULL;
1790     for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
1791             iter = g_list_next(iter))
1792     {
1793         widget = iter->data;
1794     }
1795     if (widget && GNC_IS_MAIN_WINDOW(widget))
1796     {
1797         // Ok, we found a mainwindow where we can set a statusbar message
1798         GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
1799         GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1800 
1801         gchar *msg = generate_statusbar_lastmodified_message();
1802         if (msg)
1803         {
1804             gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
1805         }
1806         g_free(msg);
1807 
1808 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1809         // Also register a timeout callback to remove that statusbar
1810         // notification again after 10 seconds
1811         g_timeout_add(10 * 1000, statusbar_notification_off, NULL); // maybe not needed anyway?
1812 #endif
1813     }
1814     else
1815     {
1816         g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
1817     }
1818 }
1819 
1820 
1821 /** This data structure is used to describe the requested state of a
1822  *  GtkRadioAction, and us used to pass data among several
1823  *  functions. */
1824 struct menu_update
1825 {
1826     /** The name of the GtkRadioAction to be updated. */
1827     gchar    *action_name;
1828 
1829     /** The new label for this GtkRadioAction. */
1830     gchar    *label;
1831 
1832     /** Whether or not the GtkRadioAction should be visible. */
1833     gboolean  visible;
1834 };
1835 
1836 #ifndef MAC_INTEGRATION
1837 /** Update the label on the specified GtkRadioAction in the specified
1838  *  window.  This action is displayed as a menu item in the "Windows"
1839  *  menu.  This function will end up being called whenever the front
1840  *  page is changed in any window, or whenever a window is added or
1841  *  deleted.
1842  *
1843  *  @param window The window whose menu item should be updated.
1844  *
1845  *  @param data A data structure containing the name of the
1846  *  GtkRadioAction, and describing the new state for this action.
1847  *
1848  *  @internal
1849  */
1850 static void
gnc_main_window_update_one_menu_action(GncMainWindow * window,struct menu_update * data)1851 gnc_main_window_update_one_menu_action (GncMainWindow *window,
1852                                         struct menu_update *data)
1853 {
1854     GncMainWindowPrivate *priv;
1855     GtkAction* action;
1856 
1857     ENTER("window %p, action %s, label %s, visible %d", window,
1858           data->action_name, data->label, data->visible);
1859     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1860     action = gtk_action_group_get_action(priv->action_group, data->action_name);
1861     if (action)
1862         g_object_set(G_OBJECT(action),
1863                      "label", data->label,
1864                      "visible", data->visible,
1865                      (char *)NULL);
1866     LEAVE(" ");
1867 }
1868 
1869 /** Update the window selection GtkRadioAction for a specific window.
1870  *  This is fairly simple since the windows are listed in the same
1871  *  order that they appear in the active_windows list, so the index
1872  *  from the window list is used to generate the name of the action.
1873  *  If the code is ever changed to allow more than ten open windows in
1874  *  the menu, then the actions in the menu will need to be dynamically
1875  *  generated/deleted and it gets harder.
1876  *
1877  *  @param window The window whose menu item should be updated.
1878  *
1879  *  @internal
1880  */
1881 static void
gnc_main_window_update_radio_button(GncMainWindow * window)1882 gnc_main_window_update_radio_button (GncMainWindow *window)
1883 {
1884     GncMainWindowPrivate *priv;
1885     GtkAction *action, *first_action;
1886     GSList *action_list;
1887     gchar *action_name;
1888     gint index;
1889 
1890     ENTER("window %p", window);
1891 
1892     /* Show the new entry in all windows. */
1893     index = g_list_index(active_windows, window);
1894     if (index >= n_radio_entries)
1895     {
1896         LEAVE("window %d, only %d actions", index, n_radio_entries);
1897         return;
1898     }
1899 
1900     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1901     action_name = g_strdup_printf("Window%dAction", index);
1902     action = gtk_action_group_get_action(priv->action_group, action_name);
1903 
1904     /* Block the signal so as not to affect window ordering (top to
1905      * bottom) on the screen */
1906     action_list = gtk_radio_action_get_group(GTK_RADIO_ACTION(action));
1907     if (action_list)
1908     {
1909         first_action = g_slist_last(action_list)->data;
1910         g_signal_handlers_block_by_func(G_OBJECT(first_action),
1911                                         G_CALLBACK(gnc_main_window_cmd_window_raise),
1912                                         window);
1913         DEBUG("blocked signal on %p, set %p active, window %p", first_action,
1914               action, window);
1915         gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
1916         g_signal_handlers_unblock_by_func(G_OBJECT(first_action),
1917                                           G_CALLBACK(gnc_main_window_cmd_window_raise),
1918                                           window);
1919     }
1920     g_free(action_name);
1921     LEAVE(" ");
1922 }
1923 
1924 /** In every window that the user has open, update the "Window" menu
1925  *  item that points to the specified window.  This keeps the "Window"
1926  *  menu items consistent across all open windows.  (These items
1927  *  cannot be shared because of the way the GtkUIManager code works.)
1928  *
1929  *  This function is called whenever the user switches pages in a
1930  *  window, or whenever a window is added or deleted.
1931  *
1932  *  @param window The window whose menu item should be updated in all
1933  *  open windows.
1934  *
1935  *  @internal
1936  */
1937 static void
gnc_main_window_update_menu_item(GncMainWindow * window)1938 gnc_main_window_update_menu_item (GncMainWindow *window)
1939 {
1940     struct menu_update data;
1941     gchar **strings, *title, *expanded;
1942     gint index;
1943 
1944     ENTER("window %p", window);
1945     index = g_list_index(active_windows, window);
1946     if (index > n_radio_entries)
1947     {
1948         LEAVE("skip window %d (only %d entries)", index, n_radio_entries);
1949         return;
1950     }
1951 
1952     /* Figure out the label name. Add the accelerator if possible. */
1953     title = gnc_main_window_generate_title(window);
1954     strings = g_strsplit(title, "_", 0);
1955     g_free(title);
1956     expanded = g_strjoinv("__", strings);
1957     if (index < 10)
1958     {
1959         data.label = g_strdup_printf("_%d %s", (index + 1) % 10, expanded);
1960         g_free(expanded);
1961     }
1962     else
1963     {
1964         data.label = expanded;
1965     }
1966     g_strfreev(strings);
1967 
1968     data.visible = TRUE;
1969     data.action_name = g_strdup_printf("Window%dAction", index);
1970     g_list_foreach(active_windows,
1971                    (GFunc)gnc_main_window_update_one_menu_action,
1972                    &data);
1973     g_free(data.action_name);
1974     g_free(data.label);
1975 
1976     LEAVE(" ");
1977 }
1978 #endif /* !MAC_INTEGRATION */
1979 
1980 /** Update all menu entries for all window menu items in all windows.
1981  *  This function is called whenever a window is added or deleted.
1982  *  The worst case scenario is where the user has deleted the first
1983  *  window, so every single visible item needs to be updated.
1984  *
1985  *  @internal
1986  */
1987 
1988 #ifndef MAC_INTEGRATION
1989 static void
gnc_main_window_update_all_menu_items(void)1990 gnc_main_window_update_all_menu_items (void)
1991 {
1992     struct menu_update data;
1993     gchar *label;
1994     gint i;
1995 
1996     ENTER("");
1997     /* First update the entries for all existing windows */
1998     g_list_foreach(active_windows,
1999                    (GFunc)gnc_main_window_update_menu_item,
2000                    NULL);
2001     g_list_foreach(active_windows,
2002                    (GFunc)gnc_main_window_update_radio_button,
2003                    NULL);
2004 
2005     /* Now hide any entries that aren't being used. */
2006     data.visible = FALSE;
2007     for (i = g_list_length(active_windows); i < n_radio_entries; i++)
2008     {
2009         data.action_name = g_strdup_printf("Window%dAction", i);
2010         label = g_strdup_printf("Window _%d", (i - 1) % 10);
2011         data.label = gettext(label);
2012 
2013         g_list_foreach(active_windows,
2014                        (GFunc)gnc_main_window_update_one_menu_action,
2015                        &data);
2016 
2017         g_free(data.action_name);
2018         g_free(label);
2019     }
2020     LEAVE(" ");
2021 }
2022 #endif /* !MAC_INTEGRATION */
2023 
2024 /** Show/hide the close box on the tab of a notebook page.  This
2025  *  function first checks to see if the specified page has a close
2026  *  box, and if so, sets its visibility to the requested state.
2027  *
2028  *  @internal
2029  *
2030  *  @param page The GncPluginPage whose notebook tab should be updated.
2031  *
2032  *  @param new_value A pointer to the boolean that indicates whether
2033  *  or not the close button should be visible.
2034  */
2035 static void
gnc_main_window_update_tab_close_one_page(GncPluginPage * page,gpointer user_data)2036 gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
2037         gpointer user_data)
2038 {
2039     gboolean *new_value = user_data;
2040     GtkWidget * close_button;
2041 
2042     ENTER("page %p, visible %d", page, *new_value);
2043     close_button = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON);
2044     if (!close_button)
2045     {
2046         LEAVE("no close button");
2047         return;
2048     }
2049 
2050     if (*new_value)
2051         gtk_widget_show (close_button);
2052     else
2053         gtk_widget_hide (close_button);
2054     LEAVE(" ");
2055 }
2056 
2057 
2058 /** Show/hide the close box on all pages in all windows.  This function
2059  *  calls gnc_main_window_update_tab_close() for each plugin page in the
2060  *  application.
2061  *
2062  *  @internal
2063  *
2064  *  @param prefs Unused.
2065  *
2066  *  @param pref Unused.
2067  *
2068  *  @param user_data Unused.
2069  */
2070 static void
gnc_main_window_update_tab_close(gpointer prefs,gchar * pref,gpointer user_data)2071 gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
2072 {
2073     gboolean new_value;
2074 
2075     ENTER(" ");
2076     new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
2077     gnc_main_window_foreach_page(
2078         gnc_main_window_update_tab_close_one_page,
2079         &new_value);
2080     LEAVE(" ");
2081 }
2082 
2083 
2084 /** Show/hide the account color on the tab of a notebook page.
2085  *
2086  *  @internal
2087  *
2088  *  @param page The GncPluginPage whose notebook tab should be updated.
2089  *
2090  *  @param user_data GncMainWindow.
2091  */
2092 static void
gnc_main_window_update_tab_color_one_page(GncPluginPage * page,gpointer user_data)2093 gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
2094         gpointer user_data)
2095 {
2096     const gchar          *color_string;
2097 
2098     ENTER("page %p", page);
2099     color_string = gnc_plugin_page_get_page_color(page);
2100     main_window_update_page_color (page, color_string);
2101     LEAVE(" ");
2102 }
2103 
2104 
2105 /** Show/hide the account color on tabs.
2106  *
2107  *  @internal
2108  *
2109  *  @param prefs Unused.
2110  *
2111  *  @param pref Name of the preference that was changed.
2112  *
2113  *  @param user_data GncMainWindow.
2114  */
2115 static void
gnc_main_window_update_tab_color(gpointer gsettings,gchar * pref,gpointer user_data)2116 gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
2117 {
2118     GncMainWindowPrivate *priv;
2119     GncMainWindow        *window;
2120 
2121     ENTER(" ");
2122     g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
2123     window = user_data;
2124     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2125     if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
2126         priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2127     gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
2128     LEAVE(" ");
2129 }
2130 
2131 
2132 /** Set the tab label ellipsize value. The special check for a zero
2133  *  value handles the case where a user hasn't set a tab width and
2134  *  the preference default isn't detected.
2135  *
2136  *  @internal
2137  *
2138  *  @param label GtkLabel for the tab.
2139  *
2140  *  @param tab_width Tab width the user has set in preferences.
2141  *
2142  */
2143 static void
gnc_main_window_set_tab_ellipsize(GtkWidget * label,gint tab_width)2144 gnc_main_window_set_tab_ellipsize (GtkWidget *label, gint tab_width)
2145 {
2146     const gchar *lab_text = gtk_label_get_text (GTK_LABEL(label));
2147 
2148     if (tab_width != 0)
2149     {
2150         gint text_length = g_utf8_strlen (lab_text, -1);
2151         if (text_length < tab_width)
2152         {
2153             gtk_label_set_width_chars (GTK_LABEL(label), text_length);
2154             gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2155         }
2156         else
2157         {
2158             gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2159             gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
2160         }
2161     }
2162     else
2163     {
2164         gtk_label_set_width_chars (GTK_LABEL(label), 15);
2165         gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2166     }
2167 }
2168 
2169 
2170 /** Update the width of the label in the tab of a notebook page.  This
2171  *  function adjusts both the width and the ellipsize mode so that the
2172  *  tab label looks correct.
2173  *
2174  *  @internal
2175  *
2176  *  @param page The GncPluginPage whose notebook tab should be updated.
2177  *
2178  *  @param new_value The new width of the label in the tab.
2179  */
2180 static void
gnc_main_window_update_tab_width_one_page(GncPluginPage * page,gpointer user_data)2181 gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
2182         gpointer user_data)
2183 {
2184     gint *new_value = user_data;
2185     GtkWidget *label;
2186 
2187     ENTER("page %p, visible %d", page, *new_value);
2188     label = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL);
2189     if (!label)
2190     {
2191         LEAVE("no label");
2192         return;
2193     }
2194     gnc_main_window_set_tab_ellipsize (label, *new_value);
2195     LEAVE(" ");
2196 }
2197 
2198 
2199 /** Update the tab label width in all pages in all windows.  This function
2200  *  calls gnc_main_window_update_tab_width() for each plugin page in the
2201  *  application.
2202  *
2203  *  @internal
2204  *
2205  *  @param prefs Unused.
2206  *
2207  *  @param pref Unused.
2208  *
2209  *  @param user_data Unused.
2210  */
2211 static void
gnc_main_window_update_tab_width(gpointer prefs,gchar * pref,gpointer user_data)2212 gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
2213 {
2214     gint new_value;
2215 
2216     ENTER(" ");
2217     new_value = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2218     gnc_main_window_foreach_page(
2219         gnc_main_window_update_tab_width_one_page,
2220         &new_value);
2221     LEAVE(" ");
2222 }
2223 
2224 
2225 /************************************************************
2226  *                 Tab Label Implementation                 *
2227  ************************************************************/
2228 static gboolean
main_window_find_tab_items(GncMainWindow * window,GncPluginPage * page,GtkWidget ** label_p,GtkWidget ** entry_p)2229 main_window_find_tab_items (GncMainWindow *window,
2230                             GncPluginPage *page,
2231                             GtkWidget **label_p,
2232                             GtkWidget **entry_p)
2233 {
2234     GncMainWindowPrivate *priv;
2235     GtkWidget *tab_hbox, *widget, *tab_widget;
2236     GList *children, *tmp;
2237 
2238     ENTER("window %p, page %p, label_p %p, entry_p %p",
2239           window, page, label_p, entry_p);
2240     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2241     *label_p = *entry_p = NULL;
2242 
2243     if (!page->notebook_page)
2244     {
2245         LEAVE("invalid notebook_page");
2246         return FALSE;
2247     }
2248 
2249     tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2250                                            page->notebook_page);
2251     if (GTK_IS_EVENT_BOX (tab_widget))
2252         tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2253     else if (GTK_IS_BOX (tab_widget))
2254         tab_hbox = tab_widget;
2255     else
2256     {
2257         PWARN ("Unknown widget for tab label %p", tab_widget);
2258         return FALSE;
2259     }
2260 
2261     children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
2262     for (tmp = children; tmp; tmp = g_list_next(tmp))
2263     {
2264         widget = tmp->data;
2265         if (GTK_IS_LABEL(widget))
2266         {
2267             *label_p = widget;
2268         }
2269         else if (GTK_IS_ENTRY(widget))
2270         {
2271             *entry_p = widget;
2272         }
2273     }
2274     g_list_free(children);
2275 
2276     LEAVE("label %p, entry %p", *label_p, *entry_p);
2277     return (*label_p && *entry_p);
2278 }
2279 
2280 static gboolean
main_window_find_tab_widget(GncMainWindow * window,GncPluginPage * page,GtkWidget ** widget_p)2281 main_window_find_tab_widget (GncMainWindow *window,
2282                              GncPluginPage *page,
2283                              GtkWidget **widget_p)
2284 {
2285     GncMainWindowPrivate *priv;
2286 
2287     ENTER("window %p, page %p, widget %p",
2288           window, page, widget_p);
2289     *widget_p = NULL;
2290 
2291     if (!page->notebook_page)
2292     {
2293         LEAVE("invalid notebook_page");
2294         return FALSE;
2295     }
2296 
2297     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2298     *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2299                                            page->notebook_page);
2300 
2301     LEAVE("widget %p", *widget_p);
2302     return TRUE;
2303 }
2304 
2305 void
main_window_update_page_name(GncPluginPage * page,const gchar * name_in)2306 main_window_update_page_name (GncPluginPage *page,
2307                               const gchar *name_in)
2308 {
2309     GncMainWindow *window;
2310     GncMainWindowPrivate *priv;
2311     GtkWidget *label, *entry;
2312     gchar *name, *old_page_name, *old_page_long_name;
2313     gint lab_width;
2314 
2315     ENTER(" ");
2316 
2317     if ((name_in == NULL) || (*name_in == '\0'))
2318     {
2319         LEAVE("no string");
2320         return;
2321     }
2322     name = g_strstrip(g_strdup(name_in));
2323 
2324     /* Optimization, if the name hasn't changed, don't update X. */
2325     if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
2326     {
2327         g_free(name);
2328         LEAVE("empty string or name unchanged");
2329         return;
2330     }
2331 
2332     old_page_name = g_strdup( gnc_plugin_page_get_page_name(page));
2333     old_page_long_name = g_strdup( gnc_plugin_page_get_page_long_name(page));
2334 
2335     /* Update the plugin */
2336     gnc_plugin_page_set_page_name(page, name);
2337 
2338     /* Update the notebook tab */
2339     window = GNC_MAIN_WINDOW(page->window);
2340     if (!window)
2341     {
2342         g_free(old_page_name);
2343         g_free(old_page_long_name);
2344         g_free(name);
2345         LEAVE("no window widget available");
2346         return;
2347     }
2348 
2349     if (main_window_find_tab_items(window, page, &label, &entry))
2350         gtk_label_set_text(GTK_LABEL(label), name);
2351 
2352     /* Adjust the label width for new text */
2353     lab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2354     gnc_main_window_update_tab_width_one_page (page, &lab_width);
2355 
2356     /* Update Tooltip on notebook Tab */
2357     if (old_page_long_name && old_page_name
2358             && g_strrstr(old_page_long_name, old_page_name) != NULL)
2359     {
2360         gchar *new_page_long_name;
2361         gint string_position;
2362         GtkWidget *tab_widget;
2363 
2364         string_position = strlen(old_page_long_name) - strlen(old_page_name);
2365         new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, NULL);
2366 
2367         gnc_plugin_page_set_page_long_name(page, new_page_long_name);
2368 
2369         if (main_window_find_tab_widget(window, page, &tab_widget))
2370             gtk_widget_set_tooltip_text(tab_widget, new_page_long_name);
2371 
2372         g_free(new_page_long_name);
2373     }
2374 
2375     /* Update the notebook menu */
2376     if (page->notebook_page)
2377     {
2378         priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2379         label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
2380                                              page->notebook_page);
2381         gtk_label_set_text(GTK_LABEL(label), name);
2382     }
2383 
2384     /* Force an update of the window title */
2385     gnc_main_window_update_title(window);
2386     g_free(old_page_long_name);
2387     g_free(old_page_name);
2388     g_free(name);
2389     LEAVE("done");
2390 }
2391 
2392 
2393 void
main_window_update_page_color(GncPluginPage * page,const gchar * color_in)2394 main_window_update_page_color (GncPluginPage *page,
2395                                const gchar *color_in)
2396 {
2397     GncMainWindow *window;
2398     GncMainWindowPrivate *priv;
2399     GtkWidget *tab_widget;
2400     GdkRGBA tab_color;
2401     gchar *color_string = NULL;
2402     gboolean want_color = FALSE;
2403 
2404     ENTER(" ");
2405     if (color_in)
2406         color_string = g_strstrip(g_strdup(color_in));
2407 
2408     if (color_string && *color_string != '\0')
2409         want_color = TRUE;
2410 
2411     /* Update the plugin */
2412     window = GNC_MAIN_WINDOW(page->window);
2413     if (want_color)
2414         gnc_plugin_page_set_page_color(page, color_string);
2415     else
2416         gnc_plugin_page_set_page_color(page, NULL);
2417 
2418     /* Update the notebook tab */
2419     main_window_find_tab_widget (window, page, &tab_widget);
2420     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2421 
2422     if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
2423     {
2424         GtkCssProvider *provider = gtk_css_provider_new();
2425         GtkStyleContext *stylectxt;
2426         gchar *col_str, *widget_css;
2427 
2428         if (!GTK_IS_EVENT_BOX (tab_widget))
2429         {
2430             GtkWidget *event_box = gtk_event_box_new ();
2431             g_object_ref (tab_widget);
2432             gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2433                                         page->notebook_page, event_box);
2434             gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
2435             g_object_unref (tab_widget);
2436             tab_widget = event_box;
2437         }
2438 
2439         stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
2440         col_str = gdk_rgba_to_string (&tab_color);
2441         widget_css = g_strconcat ("*{\n  background-color:", col_str, ";\n}\n", NULL);
2442 
2443         gtk_css_provider_load_from_data (provider, widget_css, -1, NULL);
2444         gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
2445                                         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2446         g_object_unref (provider);
2447         g_free (col_str);
2448         g_free (widget_css);
2449     }
2450     else
2451     {
2452         if (GTK_IS_EVENT_BOX (tab_widget))
2453         {
2454             GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2455             g_object_ref (tab_hbox);
2456             gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
2457             gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2458                                         page->notebook_page, tab_hbox);
2459             g_object_unref (tab_hbox);
2460         }
2461     }
2462     g_free(color_string);
2463     LEAVE("done");
2464 }
2465 
2466 
2467 void
main_window_update_page_set_read_only_icon(GncPluginPage * page,gboolean read_only)2468 main_window_update_page_set_read_only_icon (GncPluginPage *page,
2469                                             gboolean read_only)
2470 {
2471     GncMainWindow *window;
2472     GncMainWindowPrivate *priv;
2473     GtkWidget *tab_widget;
2474     GtkWidget *image = NULL;
2475     GList *children;
2476     gchar *image_name = NULL;
2477     const gchar *icon_name;
2478 
2479     ENTER(" ");
2480 
2481     g_return_if_fail(page && page->window && GNC_IS_MAIN_WINDOW(page->window));
2482     window = GNC_MAIN_WINDOW(page->window);
2483 
2484     /* Get the notebook tab widget */
2485     main_window_find_tab_widget (window, page, &tab_widget);
2486     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2487 
2488     if (!tab_widget)
2489     {
2490         LEAVE("no tab widget");
2491         return;
2492     }
2493 
2494     if (GTK_IS_EVENT_BOX(tab_widget))
2495         tab_widget = gtk_bin_get_child (GTK_BIN(tab_widget));
2496 
2497     children = gtk_container_get_children (GTK_CONTAINER(tab_widget));
2498     /* For each, walk the list of container children to get image widget */
2499     for (GList *child = children; child; child = g_list_next (child))
2500     {
2501         GtkWidget *widget = child->data;
2502         if (GTK_IS_IMAGE(widget))
2503             image = widget;
2504     }
2505     g_list_free (children);
2506 
2507     if (!image)
2508     {
2509         LEAVE("no image to replace");
2510         return;
2511     }
2512 
2513     g_object_get (image, "icon-name", &image_name, NULL);
2514 
2515     if (read_only)
2516         icon_name = "changes-prevent-symbolic";
2517     else
2518         icon_name = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
2519 
2520     if (g_strcmp0 (icon_name, image_name) == 0)
2521     {
2522         LEAVE("page icon the same, no need to replace");
2523         g_free (image_name);
2524         return;
2525     }
2526     gtk_container_remove (GTK_CONTAINER(tab_widget), image);
2527     image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
2528     gtk_widget_show (image);
2529 
2530     gtk_container_add (GTK_CONTAINER(tab_widget), image);
2531     gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
2532     gtk_box_reorder_child (GTK_BOX(tab_widget), image, 0);
2533 
2534     g_free (image_name);
2535     LEAVE("done");
2536 }
2537 
2538 
2539 static void
gnc_main_window_tab_entry_activate(GtkWidget * entry,GncPluginPage * page)2540 gnc_main_window_tab_entry_activate (GtkWidget *entry,
2541                                     GncPluginPage *page)
2542 {
2543     GtkWidget *label, *entry2;
2544 
2545     g_return_if_fail(GTK_IS_ENTRY(entry));
2546     g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
2547 
2548     ENTER("");
2549     if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2550                                     page, &label, &entry2))
2551     {
2552         LEAVE("can't find required widgets");
2553         return;
2554     }
2555 
2556     main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
2557 
2558     gtk_widget_hide(entry);
2559     gtk_widget_show(label);
2560     LEAVE("");
2561 }
2562 
2563 
2564 static gboolean
gnc_main_window_tab_entry_editing_done(GtkWidget * entry,GncPluginPage * page)2565 gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
2566                                         GncPluginPage *page)
2567 {
2568     ENTER("");
2569     gnc_main_window_tab_entry_activate(entry, page);
2570     LEAVE("");
2571     return FALSE;
2572 }
2573 
2574 static gboolean
gnc_main_window_tab_entry_focus_out_event(GtkWidget * entry,GdkEvent * event,GncPluginPage * page)2575 gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
2576         GdkEvent *event,
2577         GncPluginPage *page)
2578 {
2579     ENTER("");
2580     gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
2581     LEAVE("");
2582     return FALSE;
2583 }
2584 
2585 static gboolean
gnc_main_window_tab_entry_key_press_event(GtkWidget * entry,GdkEventKey * event,GncPluginPage * page)2586 gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
2587         GdkEventKey *event,
2588         GncPluginPage *page)
2589 {
2590     if (event->keyval == GDK_KEY_Escape)
2591     {
2592         GtkWidget *label, *entry2;
2593 
2594         g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
2595         g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
2596 
2597         ENTER("");
2598         if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2599                                         page, &label, &entry2))
2600         {
2601             LEAVE("can't find required widgets");
2602             return FALSE;
2603         }
2604 
2605         gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
2606         gtk_widget_hide(entry);
2607         gtk_widget_show(label);
2608         LEAVE("");
2609     }
2610     return FALSE;
2611 }
2612 
2613 /************************************************************
2614  *                   Widget Implementation                  *
2615  ************************************************************/
2616 
2617 
2618 
2619 /** Initialize the class for a new gnucash main window.  This will set
2620  *  up any function pointers that override functions in the parent
2621  *  class, and also initialize the signals that this class of widget
2622  *  can generate.
2623  *
2624  *  @param klass The new class structure created by the object system.
2625  */
2626 static void
gnc_main_window_class_init(GncMainWindowClass * klass)2627 gnc_main_window_class_init (GncMainWindowClass *klass)
2628 {
2629     GObjectClass *object_class = G_OBJECT_CLASS (klass);
2630     GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
2631 
2632     parent_class = g_type_class_peek_parent (klass);
2633 
2634     window_type = g_quark_from_static_string ("gnc-main-window");
2635 
2636     object_class->finalize = gnc_main_window_finalize;
2637 
2638     /* GtkWidget signals */
2639     gtkwidget_class->destroy = gnc_main_window_destroy;
2640 
2641     /**
2642      * GncMainWindow::page_added:
2643      * @param window: the #GncMainWindow
2644      * @param page: the #GncPluginPage
2645      *
2646      * The "page_added" signal is emitted when a new page is added
2647      * to the notebook of a GncMainWindow.  This can be used to
2648      * attach a signal from the page so that menu actions can be
2649      * adjusted based upon events that occur within the page
2650      * (e.g. an account is selected.)
2651      */
2652     main_window_signals[PAGE_ADDED] =
2653         g_signal_new ("page_added",
2654                       G_OBJECT_CLASS_TYPE (object_class),
2655                       G_SIGNAL_RUN_FIRST,
2656                       G_STRUCT_OFFSET (GncMainWindowClass, page_added),
2657                       NULL, NULL,
2658                       g_cclosure_marshal_VOID__OBJECT,
2659                       G_TYPE_NONE, 1,
2660                       G_TYPE_OBJECT);
2661 
2662     /**
2663      * GncMainWindow::page_changed:
2664      * @param window: the #GncMainWindow
2665      * @param page: the #GncPluginPage
2666      *
2667      * The "page_changed" signal is emitted when a new page is
2668      * selected in the notebook of a GncMainWindow.  This can be
2669      * used to adjust menu actions based upon which page is
2670      * currently displayed in a window.
2671      */
2672     main_window_signals[PAGE_CHANGED] =
2673         g_signal_new ("page_changed",
2674                       G_OBJECT_CLASS_TYPE (object_class),
2675                       G_SIGNAL_RUN_FIRST,
2676                       G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
2677                       NULL, NULL,
2678                       g_cclosure_marshal_VOID__OBJECT,
2679                       G_TYPE_NONE, 1,
2680                       G_TYPE_OBJECT);
2681 
2682     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2683                            GNC_PREF_SHOW_CLOSE_BUTTON,
2684                            gnc_main_window_update_tab_close,
2685                            NULL);
2686     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2687                            GNC_PREF_TAB_WIDTH,
2688                            gnc_main_window_update_tab_width,
2689                            NULL);
2690 
2691     gnc_hook_add_dangler(HOOK_BOOK_SAVED,
2692                          (GFunc)gnc_main_window_update_all_titles, NULL, NULL);
2693     gnc_hook_add_dangler(HOOK_BOOK_OPENED,
2694                          (GFunc)gnc_main_window_attach_to_book, NULL, NULL);
2695 
2696 }
2697 
2698 
2699 /** Initialize a new instance of a gnucash main window.  This function
2700  *  initializes the object private storage space.  It also adds the
2701  *  new object to a list (for memory tracking purposes).
2702  *
2703  *  @param window The new object instance created by the object system.
2704  *
2705  *  @param klass A pointer to the class data structure for this
2706  *  object. */
2707 static void
gnc_main_window_init(GncMainWindow * window,void * data)2708 gnc_main_window_init (GncMainWindow *window, void *data)
2709 {
2710     GncMainWindowPrivate *priv;
2711 
2712     GncMainWindowClass *klass = (GncMainWindowClass*)data;
2713 
2714     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2715     priv->merged_actions_table =
2716         g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2717 
2718     // Set the name for this dialog so it can be easily manipulated with css
2719     gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-main-window");
2720 
2721     priv->event_handler_id =
2722         qof_event_register_handler(gnc_main_window_event_handler, window);
2723 
2724     priv->restoring_pages = FALSE;
2725 
2726     /* Get the show_color_tabs value preference */
2727     priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2728 
2729     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2730                            GNC_PREF_TAB_COLOR,
2731                            gnc_main_window_update_tab_color,
2732                            window);
2733 
2734     gnc_main_window_setup_window (window);
2735     gnc_gobject_tracking_remember(G_OBJECT(window),
2736 		                  G_OBJECT_CLASS(klass));
2737 }
2738 
2739 
2740 /** Finalize the GncMainWindow object.  This function is called from
2741  *  the G_Object level to complete the destruction of the object.  It
2742  *  should release any memory not previously released by the destroy
2743  *  function (i.e. the private data structure), then chain up to the
2744  *  parent's destroy function.
2745  *
2746  *  @param object The object being destroyed.
2747  *
2748  *  @internal
2749  */
2750 static void
gnc_main_window_finalize(GObject * object)2751 gnc_main_window_finalize (GObject *object)
2752 {
2753     g_return_if_fail (object != NULL);
2754     g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
2755 
2756     if (active_windows == NULL)
2757     {
2758         /* Oops. User killed last window and we didn't catch it. */
2759         g_idle_add((GSourceFunc)gnc_shutdown, 0);
2760     }
2761 
2762     gnc_gobject_tracking_forget(object);
2763     G_OBJECT_CLASS (parent_class)->finalize (object);
2764 }
2765 
2766 
2767 static void
gnc_main_window_remove_prefs(GncMainWindow * window)2768 gnc_main_window_remove_prefs (GncMainWindow *window)
2769 {
2770     // remove the registered preference callbacks setup in this file.
2771     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2772                                  GNC_PREF_TAB_COLOR,
2773                                  gnc_main_window_update_tab_color,
2774                                  window);
2775 
2776     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2777                                  GNC_PREF_SHOW_CLOSE_BUTTON,
2778                                  gnc_main_window_update_tab_close,
2779                                  NULL);
2780 
2781     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2782                                  GNC_PREF_TAB_WIDTH,
2783                                  gnc_main_window_update_tab_width,
2784                                  NULL);
2785 
2786     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2787                                  GNC_PREF_TAB_POSITION_TOP,
2788                                  gnc_main_window_update_tab_position,
2789                                  window);
2790 
2791     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2792                                  GNC_PREF_TAB_POSITION_BOTTOM,
2793                                  gnc_main_window_update_tab_position,
2794                                  window);
2795 
2796     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2797                                  GNC_PREF_TAB_POSITION_LEFT,
2798                                  gnc_main_window_update_tab_position,
2799                                  window);
2800 
2801     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2802                                  GNC_PREF_TAB_POSITION_RIGHT,
2803                                  gnc_main_window_update_tab_position,
2804                                  window);
2805 
2806     // remove the registered negative color preference callback.
2807     if (gnc_prefs_get_reg_negative_color_pref_id() > 0 && window->window_quitting)
2808     {
2809         gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL,
2810                                    gnc_prefs_get_reg_negative_color_pref_id());
2811         gnc_prefs_set_reg_negative_color_pref_id (0);
2812     }
2813 
2814     // remove the registered auto_raise_lists preference callback.
2815     if (gnc_prefs_get_reg_auto_raise_lists_id() > 0 && window->window_quitting)
2816     {
2817         gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL_REGISTER,
2818                                    gnc_prefs_get_reg_auto_raise_lists_id());
2819         gnc_prefs_set_reg_auto_raise_lists_id (0);
2820     }
2821 }
2822 
2823 
2824 static void
gnc_main_window_destroy(GtkWidget * widget)2825 gnc_main_window_destroy (GtkWidget *widget)
2826 {
2827     GncMainWindow *window;
2828     GncMainWindowPrivate *priv;
2829     GncPluginManager *manager;
2830     GList *plugins;
2831 
2832     g_return_if_fail (widget != NULL);
2833     g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
2834 
2835     window = GNC_MAIN_WINDOW (widget);
2836 
2837     active_windows = g_list_remove (active_windows, window);
2838 
2839     /* Do these things once */
2840     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2841     if (priv->merged_actions_table)
2842     {
2843 
2844         /* Close any pages in this window */
2845         while (priv->current_page)
2846             gnc_main_window_close_page(priv->current_page);
2847 
2848         if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
2849             gnc_window_set_progressbar_window(NULL);
2850 #ifndef MAC_INTEGRATION
2851         /* Update the "Windows" menu in all other windows */
2852         gnc_main_window_update_all_menu_items();
2853 #endif
2854         /* remove the preference callbacks from the main window */
2855         gnc_main_window_remove_prefs (window);
2856 
2857         qof_event_unregister_handler(priv->event_handler_id);
2858         priv->event_handler_id = 0;
2859 
2860         g_hash_table_destroy (priv->merged_actions_table);
2861         priv->merged_actions_table = NULL;
2862 
2863         /* GncPluginManager stuff */
2864         manager = gnc_plugin_manager_get ();
2865         plugins = gnc_plugin_manager_get_plugins (manager);
2866         g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
2867         g_list_free (plugins);
2868     }
2869     GTK_WIDGET_CLASS (parent_class)->destroy (widget);
2870 }
2871 
2872 
2873 static gboolean
gnc_main_window_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2874 gnc_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2875 {
2876     GncMainWindowPrivate *priv;
2877     GdkModifierType modifiers;
2878 
2879     g_return_val_if_fail (GNC_IS_MAIN_WINDOW(widget), FALSE);
2880 
2881     priv = GNC_MAIN_WINDOW_GET_PRIVATE(widget);
2882 
2883     modifiers = gtk_accelerator_get_default_mod_mask ();
2884 
2885     if ((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) // Ctrl+Alt+
2886     {
2887         const gchar *account_key = C_ ("lower case key for short cut to 'Accounts'", "a");
2888         guint account_keyval = gdk_keyval_from_name (account_key);
2889 
2890         if ((account_keyval == event->keyval) || (account_keyval == gdk_keyval_to_lower (event->keyval)))
2891         {
2892             gint page = 0;
2893 
2894             for (GList *item = priv->installed_pages; item; item = g_list_next (item))
2895             {
2896                  const gchar *pname = gnc_plugin_page_get_plugin_name (GNC_PLUGIN_PAGE(item->data));
2897 
2898                  if (g_strcmp0 (pname, "GncPluginPageAccountTree") == 0)
2899                  {
2900                      gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook), page);
2901                      return TRUE;
2902                  }
2903                  page++;
2904             }
2905         }
2906         else if ((GDK_KEY_Menu == event->keyval) || (GDK_KEY_space == event->keyval))
2907         {
2908             GList *menu = gtk_menu_get_for_attach_widget (GTK_WIDGET(priv->notebook));
2909 
2910             if (menu)
2911             {
2912                 gtk_menu_popup_at_widget (GTK_MENU(menu->data),
2913                                           GTK_WIDGET(priv->notebook),
2914                                           GDK_GRAVITY_SOUTH,
2915                                           GDK_GRAVITY_SOUTH,
2916                                           NULL);
2917                 return TRUE;
2918             }
2919         }
2920     }
2921     return FALSE;
2922 }
2923 
2924 
2925 /*  Create a new gnc main window plugin.
2926  */
2927 GncMainWindow *
gnc_main_window_new(void)2928 gnc_main_window_new (void)
2929 {
2930     GncMainWindow *window;
2931     GtkWindow *old_window;
2932 
2933     window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL);
2934     gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
2935 
2936     old_window = gnc_ui_get_main_window (NULL);
2937     if (old_window)
2938     {
2939         gint width, height;
2940         gtk_window_get_size (old_window, &width, &height);
2941         gtk_window_resize (GTK_WINDOW (window), width, height);
2942         if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
2943                 & GDK_WINDOW_STATE_MAXIMIZED) != 0)
2944         {
2945             gtk_window_maximize (GTK_WINDOW (window));
2946         }
2947     }
2948     active_windows = g_list_append (active_windows, window);
2949     gnc_main_window_update_title(window);
2950     window->window_quitting = FALSE;
2951     window->just_plugin_prefs = FALSE;
2952 #ifdef MAC_INTEGRATION
2953     gnc_quartz_set_menu(window);
2954 #else
2955     gnc_main_window_update_all_menu_items();
2956 #endif
2957     gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
2958 
2959     // set up a callback for noteboook navigation
2960     g_signal_connect (G_OBJECT(window), "key-press-event",
2961                       G_CALLBACK(gnc_main_window_key_press_event),
2962                       NULL);
2963 
2964     return window;
2965 }
2966 
2967 /************************************************************
2968  *                     Utility Functions                    *
2969  ************************************************************/
2970 
2971 static void
gnc_main_window_engine_commit_error_callback(gpointer data,QofBackendError errcode)2972 gnc_main_window_engine_commit_error_callback( gpointer data,
2973         QofBackendError errcode )
2974 {
2975     GncMainWindow* window = GNC_MAIN_WINDOW(data);
2976     GtkWidget* dialog;
2977     const gchar *reason = _("Unable to save to database.");
2978     if ( errcode == ERR_BACKEND_READONLY )
2979         reason = _("Unable to save to database: Book is marked read-only.");
2980     dialog = gtk_message_dialog_new( GTK_WINDOW(window),
2981                                      GTK_DIALOG_DESTROY_WITH_PARENT,
2982                                      GTK_MESSAGE_ERROR,
2983                                      GTK_BUTTONS_CLOSE,
2984                                      "%s",
2985                                      reason );
2986     gtk_dialog_run(GTK_DIALOG (dialog));
2987     gtk_widget_destroy(dialog);
2988 
2989 }
2990 
2991 /** Connect a GncPluginPage to the window.  This function will insert
2992  *  the page in to the window's notebook and its list of active pages.
2993  *  It will also emit the "inserted" signal on the page, and the
2994  *  "add_page" signal on the window.
2995  *
2996  *  @param window The window where the new page should be added.
2997  *
2998  *  @param page The GncPluginPage that should be added to the window.
2999  *  The visible widget for this plugin must have already been created.
3000  *
3001  *  @param tab_hbox The widget that should be added into the notebook
3002  *  tab for this page.  Generally this is a GtkLabel, but could also
3003  *  be a GtkHBox containing an icon and a label.
3004  *
3005  *  @param menu_label The widget that should be added into the
3006  *  notebook popup menu for this page.  This should be a GtkLabel.
3007  */
3008 static void
gnc_main_window_connect(GncMainWindow * window,GncPluginPage * page,GtkWidget * tab_hbox,GtkWidget * menu_label)3009 gnc_main_window_connect (GncMainWindow *window,
3010                          GncPluginPage *page,
3011                          GtkWidget *tab_hbox,
3012                          GtkWidget *menu_label)
3013 {
3014     GncMainWindowPrivate *priv;
3015     GtkNotebook *notebook;
3016     gint current_position = -1;
3017 
3018     page->window = GTK_WIDGET(window);
3019     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3020     notebook = GTK_NOTEBOOK (priv->notebook);
3021 
3022     if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_OPEN_ADJACENT))
3023         current_position = g_list_index (priv->installed_pages, priv->current_page) + 1;
3024 
3025     priv->installed_pages = g_list_insert (priv->installed_pages, page, current_position);
3026     priv->usage_order = g_list_prepend (priv->usage_order, page);
3027     gtk_notebook_insert_page_menu (notebook, page->notebook_page,
3028                                    tab_hbox, menu_label, current_position);
3029     gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
3030     gnc_plugin_page_inserted (page);
3031     gtk_notebook_set_current_page (notebook, current_position);
3032 
3033     if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
3034         (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
3035     g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
3036 
3037     g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
3038                      G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3039     g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
3040                            G_CALLBACK(gnc_main_window_button_press_cb), page);
3041 }
3042 
3043 
3044 /** Disconnect a GncPluginPage page from the window.  If this page is
3045  *  currently foremost in the window's notebook, its user interface
3046  *  actions will be disconnected and the page's summarybar widget (if
3047  *  any) will be removed.  The page is then removed from the window's
3048  *  notebook and its list of active pages.
3049  *
3050  *  @param window The window the page should be removed from.
3051  *
3052  *  @param page The GncPluginPage that should be removed from the
3053  *  window.
3054  *
3055  *  @internal
3056  */
3057 static void
gnc_main_window_disconnect(GncMainWindow * window,GncPluginPage * page)3058 gnc_main_window_disconnect (GncMainWindow *window,
3059                             GncPluginPage *page)
3060 {
3061     GncMainWindowPrivate *priv;
3062     GtkNotebook *notebook;
3063     GncPluginPage *new_page;
3064     gint page_num;
3065 
3066     /* Disconnect the callbacks */
3067     g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3068                                          G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3069     g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3070                                          G_CALLBACK(gnc_main_window_button_press_cb), page);
3071 
3072     // Remove the page_changed signal callback
3073     gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
3074 
3075     /* Disconnect the page and summarybar from the window */
3076     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3077     if (priv->current_page == page)
3078     {
3079         gnc_plugin_page_unmerge_actions (page, window->ui_merge);
3080         gnc_plugin_page_unselected (page);
3081         priv->current_page = NULL;
3082     }
3083 
3084     /* Remove it from the list of pages in the window */
3085     priv->installed_pages = g_list_remove (priv->installed_pages, page);
3086     priv->usage_order = g_list_remove (priv->usage_order, page);
3087 
3088     /* Switch to the last recently used page */
3089     notebook = GTK_NOTEBOOK (priv->notebook);
3090     if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
3091     {
3092         new_page = g_list_nth_data (priv->usage_order, 0);
3093         if (new_page)
3094         {
3095             page_num =  gtk_notebook_page_num(notebook, new_page->notebook_page);
3096             gtk_notebook_set_current_page(notebook, page_num);
3097             /* This may have caused WebKit to schedule  a timer interrupt which it
3098                sometimes  forgets to cancel before deleting the object.  See
3099                <https://bugs.webkit.org/show_bug.cgi?id=119003>.   Get around this
3100                by flushing all events to get rid of the timer interrupt. */
3101             while (gtk_events_pending())
3102                 gtk_main_iteration();
3103         }
3104     }
3105 
3106     /* Remove the page from the notebook */
3107     page_num =  gtk_notebook_page_num(notebook, page->notebook_page);
3108     gtk_notebook_remove_page (notebook, page_num);
3109 
3110     if ( gtk_notebook_get_current_page(notebook) == -1)
3111     {
3112         /* Need to synthesize a page changed signal when the last
3113          * page is removed.  The notebook doesn't generate a signal
3114          * for this, therefore the switch_page code in this file
3115          * never gets called to generate this signal. */
3116         gnc_main_window_switch_page(notebook, NULL, -1, window);
3117         //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, NULL);
3118     }
3119 
3120     gnc_plugin_page_removed (page);
3121 
3122     gtk_ui_manager_ensure_update (window->ui_merge);
3123     gnc_window_set_status (GNC_WINDOW(window), page, NULL);
3124 }
3125 
3126 
3127 /************************************************************
3128  *                                                          *
3129  ************************************************************/
3130 
3131 
3132 void
gnc_main_window_display_page(GncPluginPage * page)3133 gnc_main_window_display_page (GncPluginPage *page)
3134 {
3135     GncMainWindow *window;
3136     GncMainWindowPrivate *priv;
3137     GtkNotebook *notebook;
3138     gint page_num;
3139 
3140     window = GNC_MAIN_WINDOW (page->window);
3141     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3142     notebook = GTK_NOTEBOOK (priv->notebook);
3143     page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3144     gtk_notebook_set_current_page (notebook, page_num);
3145     gtk_window_present(GTK_WINDOW(window));
3146 }
3147 
3148 
3149 /*  Display a data plugin page in a window.  If the page already
3150  *  exists in any window, then that window will be brought to the
3151  *  front and the notebook switch to display the specified page.  If
3152  *  the page is new then it will be added to the specified window.  If
3153  *  the window is NULL, the new page will be added to the first
3154  *  window.
3155  */
3156 void
gnc_main_window_open_page(GncMainWindow * window,GncPluginPage * page)3157 gnc_main_window_open_page (GncMainWindow *window,
3158                            GncPluginPage *page)
3159 {
3160     GncMainWindowPrivate *priv;
3161     GtkWidget *tab_hbox;
3162     GtkWidget *label, *entry;
3163     const gchar *icon, *text, *color_string, *lab_text;
3164     GtkWidget *image;
3165     GList *tmp;
3166     gint width;
3167 
3168     ENTER("window %p, page %p", window, page);
3169     if (window)
3170         g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3171     g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
3172     g_return_if_fail (gnc_plugin_page_has_books(page));
3173 
3174     if (gnc_main_window_page_exists(page))
3175     {
3176         gnc_main_window_display_page(page);
3177         return;
3178     }
3179 
3180     /* Does the page want to be in a new window? */
3181     if (gnc_plugin_page_get_use_new_window(page))
3182     {
3183         /* See if there's a blank window. If so, use that. */
3184         for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
3185         {
3186             window = GNC_MAIN_WINDOW(tmp->data);
3187             priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3188             if (priv->installed_pages == NULL)
3189             {
3190                 break;
3191             }
3192         }
3193         if (tmp == NULL)
3194             window = gnc_main_window_new ();
3195         gtk_widget_show(GTK_WIDGET(window));
3196     }
3197     else if ((window == NULL) && active_windows)
3198     {
3199         window = active_windows->data;
3200     }
3201 
3202     page->window = GTK_WIDGET(window);
3203     page->notebook_page = gnc_plugin_page_create_widget (page);
3204     g_object_set_data (G_OBJECT (page->notebook_page),
3205                        PLUGIN_PAGE_LABEL, page);
3206 
3207     /*
3208      * The page tab.
3209      */
3210     width = gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
3211     icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
3212     lab_text = gnc_plugin_page_get_page_name(page);
3213     label = gtk_label_new (lab_text);
3214     g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
3215 
3216     gnc_main_window_set_tab_ellipsize (label, width);
3217 
3218     gtk_widget_show (label);
3219 
3220     tab_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
3221 
3222     if (g_strcmp0 (gnc_plugin_page_get_plugin_name (page), "GncPluginPageAccountTree") == 0)
3223         gtk_widget_set_name (GTK_WIDGET(tab_hbox), "gnc-id-account-page-tab-box");
3224 
3225     gtk_box_set_homogeneous (GTK_BOX (tab_hbox), FALSE);
3226     gtk_widget_show (tab_hbox);
3227 
3228     if (icon != NULL)
3229     {
3230         image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
3231         gtk_widget_show (image);
3232         gtk_box_pack_start (GTK_BOX (tab_hbox), image, FALSE, FALSE, 0);
3233         gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
3234         gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3235     }
3236     else
3237         gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3238 
3239     text = gnc_plugin_page_get_page_long_name(page);
3240     if (text)
3241     {
3242         gtk_widget_set_tooltip_text(tab_hbox, text);
3243     }
3244 
3245     entry = gtk_entry_new();
3246     gtk_widget_hide (entry);
3247     gtk_box_pack_start (GTK_BOX (tab_hbox), entry, TRUE, TRUE, 0);
3248     g_signal_connect(G_OBJECT(entry), "activate",
3249                      G_CALLBACK(gnc_main_window_tab_entry_activate), page);
3250     g_signal_connect(G_OBJECT(entry), "focus-out-event",
3251                      G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
3252                      page);
3253     g_signal_connect(G_OBJECT(entry), "key-press-event",
3254                      G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
3255                      page);
3256     g_signal_connect(G_OBJECT(entry), "editing-done",
3257                      G_CALLBACK(gnc_main_window_tab_entry_editing_done),
3258                      page);
3259 
3260     /* Add close button - Not for immutable pages */
3261     if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
3262     {
3263         GtkWidget *close_image, *close_button;
3264         GtkRequisition requisition;
3265 
3266         close_button = gtk_button_new();
3267         gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
3268         close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
3269         gtk_widget_show(close_image);
3270         gtk_widget_get_preferred_size (close_image, &requisition, NULL);
3271         gtk_widget_set_size_request(close_button, requisition.width + 4,
3272                                     requisition.height + 2);
3273         gtk_container_add(GTK_CONTAINER(close_button), close_image);
3274         if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
3275             gtk_widget_show (close_button);
3276         else
3277             gtk_widget_hide (close_button);
3278 
3279         g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
3280                                   G_CALLBACK(gnc_main_window_close_page), page);
3281 
3282         gtk_box_pack_start (GTK_BOX (tab_hbox), close_button, FALSE, FALSE, 0);
3283         gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
3284         g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
3285     }
3286 
3287     /*
3288      * The popup menu
3289      */
3290     label = gtk_label_new (gnc_plugin_page_get_page_name(page));
3291 
3292     /*
3293      * Now install it all in the window.
3294      */
3295     gnc_main_window_connect(window, page, tab_hbox, label);
3296 
3297     color_string = gnc_plugin_page_get_page_color(page);
3298     main_window_update_page_color (page, color_string);
3299     LEAVE("");
3300 }
3301 
3302 
3303 /*  Remove a data plugin page from a window and display the previous
3304  *  page.  If the page removed was the last page in the window, and
3305  *  there is more than one window open, then the entire window will be
3306  *  destroyed.
3307  */
3308 void
gnc_main_window_close_page(GncPluginPage * page)3309 gnc_main_window_close_page (GncPluginPage *page)
3310 {
3311     GncMainWindow *window;
3312     GncMainWindowPrivate *priv;
3313 
3314     if (!page || !page->notebook_page)
3315         return;
3316 
3317     if (!gnc_plugin_page_finish_pending(page))
3318         return;
3319 
3320     if (!GNC_IS_MAIN_WINDOW (page->window))
3321         return;
3322 
3323     window = GNC_MAIN_WINDOW (page->window);
3324     if (!window)
3325     {
3326         g_warning("Page is not in a window.");
3327         return;
3328     }
3329 
3330     gnc_main_window_disconnect(window, page);
3331     gnc_plugin_page_destroy_widget (page);
3332     g_object_unref(page);
3333 
3334     /* If this isn't the last window, go ahead and destroy the window. */
3335     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3336     if (priv->installed_pages == NULL)
3337     {
3338         if (window->window_quitting)
3339         {
3340             GncPluginManager *manager = gnc_plugin_manager_get ();
3341             GList *plugins = gnc_plugin_manager_get_plugins (manager);
3342 
3343             /* remove only the preference callbacks from the window plugins */
3344             window->just_plugin_prefs = TRUE;
3345             g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
3346             window->just_plugin_prefs = FALSE;
3347             g_list_free (plugins);
3348 
3349             /* remove the preference callbacks from the main window */
3350             gnc_main_window_remove_prefs (window);
3351         }
3352         if (window && (gnc_list_length_cmp (active_windows, 1) > 0))
3353             gtk_widget_destroy (GTK_WIDGET(window));
3354     }
3355 }
3356 
3357 
3358 /*  Retrieve a pointer to the page that is currently at the front of
3359  *  the specified window.  Any plugin that needs to manipulate its
3360  *  menus based upon the currently selected menu page should connect
3361  *  to the "page_changed" signal on a window.  The callback function
3362  *  from that signal can then call this function to obtain a pointer
3363  *  to the current page.
3364  */
3365 GncPluginPage *
gnc_main_window_get_current_page(GncMainWindow * window)3366 gnc_main_window_get_current_page (GncMainWindow *window)
3367 {
3368     GncMainWindowPrivate *priv;
3369 
3370     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3371     return priv->current_page;
3372 }
3373 
3374 
3375 /*  Manually add a set of actions to the specified window.  Plugins
3376  *  whose user interface is not hard coded (e.g. the menu-additions
3377  *  plugin) must create their actions at run time, then use this
3378  *  function to install them into the window.
3379  */
3380 void
gnc_main_window_manual_merge_actions(GncMainWindow * window,const gchar * group_name,GtkActionGroup * group,guint merge_id)3381 gnc_main_window_manual_merge_actions (GncMainWindow *window,
3382                                       const gchar *group_name,
3383                                       GtkActionGroup *group,
3384                                       guint merge_id)
3385 {
3386     GncMainWindowPrivate *priv;
3387     MergedActionEntry *entry;
3388 
3389     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3390     g_return_if_fail (group_name != NULL);
3391     g_return_if_fail (GTK_IS_ACTION_GROUP(group));
3392     g_return_if_fail (merge_id > 0);
3393 
3394     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3395     entry = g_new0 (MergedActionEntry, 1);
3396     entry->action_group = group;
3397     entry->merge_id = merge_id;
3398     gtk_ui_manager_ensure_update (window->ui_merge);
3399     g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
3400 }
3401 
3402 
3403 /*  Add a set of actions to the specified window.  This function
3404  *  should not need to be called directly by plugin implementors.
3405  *  Correctly assigning values to the GncPluginClass fields during
3406  *  plugin initialization will cause this routine to be automatically
3407  *  called.
3408  */
3409 void
gnc_main_window_merge_actions(GncMainWindow * window,const gchar * group_name,GtkActionEntry * actions,guint n_actions,GtkToggleActionEntry * toggle_actions,guint n_toggle_actions,const gchar * filename,gpointer user_data)3410 gnc_main_window_merge_actions (GncMainWindow *window,
3411                                const gchar *group_name,
3412                                GtkActionEntry *actions,
3413                                guint n_actions,
3414                                GtkToggleActionEntry *toggle_actions,
3415                                guint n_toggle_actions,
3416                                const gchar *filename,
3417                                gpointer user_data)
3418 {
3419     GncMainWindowPrivate *priv;
3420     GncMainWindowActionData *data;
3421     MergedActionEntry *entry;
3422     GError *error = NULL;
3423     gchar *pathname;
3424 
3425     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3426     g_return_if_fail (group_name != NULL);
3427     g_return_if_fail (actions != NULL);
3428     g_return_if_fail (n_actions > 0);
3429     g_return_if_fail (filename != NULL);
3430 
3431     pathname = gnc_filepath_locate_ui_file (filename);
3432     if (pathname == NULL)
3433         return;
3434 
3435     data = g_new0 (GncMainWindowActionData, 1);
3436     data->window = window;
3437     data->data = user_data;
3438 
3439     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3440     entry = g_new0 (MergedActionEntry, 1);
3441     entry->action_group = gtk_action_group_new (group_name);
3442     gtk_action_group_set_translation_domain (entry->action_group, PROJECT_NAME);
3443     gtk_action_group_add_actions (entry->action_group, actions, n_actions, data);
3444     if (toggle_actions != NULL && n_toggle_actions > 0)
3445     {
3446         gtk_action_group_add_toggle_actions (entry->action_group,
3447                                              toggle_actions, n_toggle_actions,
3448                                              data);
3449     }
3450     gtk_ui_manager_insert_action_group (window->ui_merge, entry->action_group, 0);
3451     entry->merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge, pathname, &error);
3452     g_assert(entry->merge_id || error);
3453     if (entry->merge_id)
3454     {
3455         gtk_ui_manager_ensure_update (window->ui_merge);
3456         g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
3457     }
3458     else
3459     {
3460         g_critical("Failed to load ui file.\n  Filename %s\n  Error %s",
3461                    filename, error->message);
3462         g_error_free(error);
3463         g_free(entry);
3464     }
3465     g_free(pathname);
3466 }
3467 
3468 
3469 /*  Remove a set of actions from the specified window.  This function
3470  *  should not need to be called directly by plugin implementors.  It
3471  *  will automatically be called when a plugin is removed from a
3472  *  window.
3473  */
3474 void
gnc_main_window_unmerge_actions(GncMainWindow * window,const gchar * group_name)3475 gnc_main_window_unmerge_actions (GncMainWindow *window,
3476                                  const gchar *group_name)
3477 {
3478     GncMainWindowPrivate *priv;
3479     MergedActionEntry *entry;
3480 
3481     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3482     g_return_if_fail (group_name != NULL);
3483 
3484     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3485     if (priv->merged_actions_table == NULL)
3486         return;
3487     entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
3488 
3489     if (entry == NULL)
3490         return;
3491 
3492     gtk_ui_manager_remove_action_group (window->ui_merge, entry->action_group);
3493     gtk_ui_manager_remove_ui (window->ui_merge, entry->merge_id);
3494     gtk_ui_manager_ensure_update (window->ui_merge);
3495 
3496     g_hash_table_remove (priv->merged_actions_table, group_name);
3497 }
3498 
3499 
3500 /*  Force a full update of the user interface for the specified
3501  *  window.  This can be an expensive function, but is needed because
3502  *  the gtk ui manager doesn't always seem to update properly when
3503  *  actions are changed.
3504  */
3505 void
gnc_main_window_actions_updated(GncMainWindow * window)3506 gnc_main_window_actions_updated (GncMainWindow *window)
3507 {
3508     GtkActionGroup *force;
3509 
3510     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3511 
3512     /* Unfortunately gtk_ui_manager_ensure_update doesn't work
3513      * here.  Force a full update by adding and removing an empty
3514      * action group.
3515      */
3516     force = gtk_action_group_new("force_update");
3517     gtk_ui_manager_insert_action_group (window->ui_merge, force, 0);
3518     gtk_ui_manager_ensure_update (window->ui_merge);
3519     gtk_ui_manager_remove_action_group (window->ui_merge, force);
3520     g_object_unref(force);
3521 }
3522 
3523 
3524 GtkAction *
gnc_main_window_find_action(GncMainWindow * window,const gchar * name)3525 gnc_main_window_find_action (GncMainWindow *window, const gchar *name)
3526 {
3527     GtkAction *action = NULL;
3528     const GList *groups, *tmp;
3529 
3530     groups = gtk_ui_manager_get_action_groups(window->ui_merge);
3531     for (tmp = groups; tmp; tmp = g_list_next(tmp))
3532     {
3533         action = gtk_action_group_get_action(GTK_ACTION_GROUP(tmp->data), name);
3534         if (action)
3535             break;
3536     }
3537     return action;
3538 }
3539 
3540 
3541 /*  Retrieve a specific set of user interface actions from a window.
3542  *  This function can be used to get an group of action to be
3543  *  manipulated when the front page of a window has changed.
3544  */
3545 GtkActionGroup *
gnc_main_window_get_action_group(GncMainWindow * window,const gchar * group_name)3546 gnc_main_window_get_action_group (GncMainWindow *window,
3547                                   const gchar *group_name)
3548 {
3549     GncMainWindowPrivate *priv;
3550     MergedActionEntry *entry;
3551 
3552     g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
3553     g_return_val_if_fail (group_name != NULL, NULL);
3554 
3555     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3556     if (priv->merged_actions_table == NULL)
3557         return NULL;
3558     entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
3559 
3560     if (entry == NULL)
3561         return NULL;
3562 
3563     return entry->action_group;
3564 }
3565 
3566 static void
gnc_main_window_update_tab_position(gpointer prefs,gchar * pref,gpointer user_data)3567 gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
3568 {
3569     GncMainWindow *window;
3570     GtkPositionType position = GTK_POS_TOP;
3571     GncMainWindowPrivate *priv;
3572 
3573     g_return_if_fail (GNC_IS_MAIN_WINDOW(user_data));
3574 
3575     window = GNC_MAIN_WINDOW(user_data);
3576 
3577     ENTER ("window %p", window);
3578     if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
3579         position = GTK_POS_BOTTOM;
3580     else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
3581         position = GTK_POS_LEFT;
3582     else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
3583         position = GTK_POS_RIGHT;
3584 
3585     priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
3586     gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), position);
3587 
3588     LEAVE ("");
3589 }
3590 
3591 /*
3592  * Based on code from Epiphany (src/ephy-window.c)
3593  */
3594 static void
gnc_main_window_update_edit_actions_sensitivity(GncMainWindow * window,gboolean hide)3595 gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
3596 {
3597     GncMainWindowPrivate *priv;
3598     GncPluginPage *page;
3599     GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
3600     GtkAction *action;
3601     gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;
3602 
3603     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3604     page = priv->current_page;
3605     if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
3606     {
3607         (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
3608         return;
3609     }
3610 
3611     if (GTK_IS_EDITABLE (widget))
3612     {
3613         gboolean has_selection;
3614 
3615         has_selection = gtk_editable_get_selection_bounds
3616                         (GTK_EDITABLE (widget), NULL, NULL);
3617 
3618         can_copy = has_selection;
3619         can_cut = has_selection;
3620         can_paste = TRUE;
3621     }
3622     else if (GTK_IS_TEXT_VIEW (widget))
3623     {
3624         gboolean has_selection;
3625         GtkTextBuffer *text_buffer;
3626 
3627         text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
3628         has_selection = gtk_text_buffer_get_selection_bounds
3629                         (text_buffer, NULL, NULL);
3630 
3631         can_copy = has_selection;
3632         can_cut = has_selection;
3633         can_paste = TRUE;
3634     }
3635     else
3636     {
3637 #ifdef ORIGINAL_EPIPHANY_CODE
3638         /* For now we assume all actions are possible */
3639         can_copy = can_cut = can_paste = TRUE;
3640 #else
3641         /* If its not a GtkEditable, we don't know what to do
3642          * with it. */
3643         can_copy = can_cut = can_paste = FALSE;
3644 #endif
3645     }
3646 
3647     action = gnc_main_window_find_action (window, "EditCopyAction");
3648     gtk_action_set_sensitive (action, can_copy);
3649     gtk_action_set_visible (action, !hide || can_copy);
3650     action = gnc_main_window_find_action (window, "EditCutAction");
3651     gtk_action_set_sensitive (action, can_cut);
3652     gtk_action_set_visible (action, !hide || can_cut);
3653     action = gnc_main_window_find_action (window, "EditPasteAction");
3654     gtk_action_set_sensitive (action, can_paste);
3655     gtk_action_set_visible (action,  !hide || can_paste);
3656 }
3657 
3658 static void
gnc_main_window_enable_edit_actions_sensitivity(GncMainWindow * window)3659 gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
3660 {
3661     GtkAction *action;
3662 
3663     action = gnc_main_window_find_action (window, "EditCopyAction");
3664     gtk_action_set_sensitive (action, TRUE);
3665     gtk_action_set_visible (action, TRUE);
3666     action = gnc_main_window_find_action (window, "EditCutAction");
3667     gtk_action_set_sensitive (action, TRUE);
3668     gtk_action_set_visible (action, TRUE);
3669     action = gnc_main_window_find_action (window, "EditPasteAction");
3670     gtk_action_set_sensitive (action, TRUE);
3671     gtk_action_set_visible (action, TRUE);
3672 }
3673 
3674 static void
gnc_main_window_edit_menu_show_cb(GtkWidget * menu,GncMainWindow * window)3675 gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
3676                                    GncMainWindow *window)
3677 {
3678     gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
3679 }
3680 
3681 static void
gnc_main_window_edit_menu_hide_cb(GtkWidget * menu,GncMainWindow * window)3682 gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
3683                                    GncMainWindow *window)
3684 {
3685     gnc_main_window_enable_edit_actions_sensitivity (window);
3686 }
3687 
3688 static void
gnc_main_window_init_menu_updaters(GncMainWindow * window)3689 gnc_main_window_init_menu_updaters (GncMainWindow *window)
3690 {
3691     GtkWidget *edit_menu_item, *edit_menu;
3692 
3693     edit_menu_item = gtk_ui_manager_get_widget
3694                      (window->ui_merge, "/menubar/Edit");
3695     edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (edit_menu_item));
3696 
3697     g_signal_connect (edit_menu, "show",
3698                       G_CALLBACK (gnc_main_window_edit_menu_show_cb), window);
3699     g_signal_connect (edit_menu, "hide",
3700                       G_CALLBACK (gnc_main_window_edit_menu_hide_cb), window);
3701 }
3702 
3703 static void
gnc_main_window_window_menu(GncMainWindow * window)3704 gnc_main_window_window_menu (GncMainWindow *window)
3705 {
3706     guint merge_id;
3707 #ifdef MAC_INTEGRATION
3708     gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui-quartz.xml");
3709 #else
3710     gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui.xml");
3711     GncMainWindowPrivate *priv;
3712 #endif
3713     GError *error = NULL;
3714     g_assert(filename);
3715     merge_id = gtk_ui_manager_add_ui_from_file(window->ui_merge, filename,
3716                &error);
3717     g_free(filename);
3718     g_assert(merge_id);
3719 #ifndef MAC_INTEGRATION
3720     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3721     gtk_action_group_add_radio_actions (priv->action_group,
3722                                         radio_entries, n_radio_entries,
3723                                         0,
3724                                         G_CALLBACK(gnc_main_window_cmd_window_raise),
3725                                         window);
3726 #endif
3727 };
3728 
3729 /* This is used to prevent the tab having focus */
3730 static gboolean
gnc_main_window_page_focus_in(GtkWidget * widget,GdkEvent * event,gpointer user_data)3731 gnc_main_window_page_focus_in (GtkWidget *widget, GdkEvent  *event,
3732                                gpointer user_data)
3733 {
3734     GncMainWindow *window = user_data;
3735     GncPluginPage *page = gnc_main_window_get_current_page (window);
3736 
3737     g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
3738     return FALSE;
3739 }
3740 
3741 static void
gnc_main_window_setup_window(GncMainWindow * window)3742 gnc_main_window_setup_window (GncMainWindow *window)
3743 {
3744     GncMainWindowPrivate *priv;
3745     GtkWidget *main_vbox;
3746     guint merge_id;
3747     GncPluginManager *manager;
3748     GList *plugins;
3749     GError *error = NULL;
3750     gchar *filename;
3751 
3752     ENTER(" ");
3753 
3754     /* Catch window manager delete signal */
3755     g_signal_connect (G_OBJECT (window), "delete-event",
3756                       G_CALLBACK (gnc_main_window_delete_event), window);
3757 
3758     /* Create widgets and add them to the window */
3759     main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
3760     gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
3761     gtk_widget_show (main_vbox);
3762     gtk_container_add (GTK_CONTAINER (window), main_vbox);
3763 
3764     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3765     priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
3766     gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
3767     gtk_widget_show (priv->menu_dock);
3768     gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
3769                         FALSE, TRUE, 0);
3770 
3771     priv->notebook = gtk_notebook_new ();
3772     g_object_set(G_OBJECT(priv->notebook),
3773                  "scrollable", TRUE,
3774                  "enable-popup", TRUE,
3775                  (char *)NULL);
3776     gtk_widget_show (priv->notebook);
3777     g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
3778                       G_CALLBACK (gnc_main_window_switch_page), window);
3779     g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
3780                       G_CALLBACK (gnc_main_window_page_reordered), window);
3781     g_signal_connect (G_OBJECT (priv->notebook), "focus-in-event",
3782                       G_CALLBACK (gnc_main_window_page_focus_in), window);
3783     gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
3784                         TRUE, TRUE, 0);
3785 
3786     priv->statusbar = gtk_statusbar_new ();
3787     gtk_widget_show (priv->statusbar);
3788     gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
3789                         FALSE, TRUE, 0);
3790 
3791     priv->progressbar = gtk_progress_bar_new ();
3792     gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(priv->progressbar), TRUE);
3793     gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
3794     gtk_widget_show (priv->progressbar);
3795     gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
3796                         FALSE, TRUE, 0);
3797     gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
3798                                     0.01);
3799 
3800     window->ui_merge = gtk_ui_manager_new ();
3801 
3802     /* Create menu and toolbar information */
3803     priv->action_group = gtk_action_group_new ("MainWindowActions");
3804     gtk_action_group_set_translation_domain (priv->action_group, PROJECT_NAME);
3805     gtk_action_group_add_actions (priv->action_group, gnc_menu_actions,
3806                                   gnc_menu_n_actions, window);
3807     gtk_action_group_add_toggle_actions (priv->action_group,
3808                                          toggle_actions, n_toggle_actions,
3809                                          window);
3810     gnc_plugin_update_actions(priv->action_group,
3811                               initially_insensitive_actions,
3812                               "sensitive", FALSE);
3813     gnc_plugin_update_actions(priv->action_group,
3814                               always_insensitive_actions,
3815                               "sensitive", FALSE);
3816     gnc_plugin_update_actions(priv->action_group,
3817                               always_hidden_actions,
3818                               "visible", FALSE);
3819     gnc_plugin_set_important_actions (priv->action_group,
3820                                       gnc_menu_important_actions);
3821     gtk_ui_manager_insert_action_group (window->ui_merge, priv->action_group, 0);
3822 
3823     g_signal_connect (G_OBJECT (window->ui_merge), "add_widget",
3824                       G_CALLBACK (gnc_main_window_add_widget), window);
3825 
3826     /* Use the "connect-proxy" signal for tooltip display in the status bar */
3827     g_signal_connect (G_OBJECT (window->ui_merge), "connect-proxy",
3828                       G_CALLBACK (gnc_window_connect_proxy), priv->statusbar);
3829 
3830     filename = gnc_filepath_locate_ui_file("gnc-main-window-ui.xml");
3831 
3832     /* Can't do much without a ui. */
3833     g_assert (filename);
3834 
3835     merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge,
3836                filename, &error);
3837     g_assert(merge_id || error);
3838     if (merge_id)
3839     {
3840         gtk_window_add_accel_group (GTK_WINDOW (window),
3841                                     gtk_ui_manager_get_accel_group(window->ui_merge));
3842         gtk_ui_manager_ensure_update (window->ui_merge);
3843     }
3844     else
3845     {
3846         g_critical("Failed to load ui file.\n  Filename %s\n  Error %s",
3847                    filename, error->message);
3848         g_error_free(error);
3849         g_assert(merge_id != 0);
3850     }
3851     g_free(filename);
3852     gnc_main_window_window_menu(window);
3853     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3854                            GNC_PREF_TAB_POSITION_TOP,
3855                            gnc_main_window_update_tab_position,
3856                            window);
3857     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3858                            GNC_PREF_TAB_POSITION_BOTTOM,
3859                            gnc_main_window_update_tab_position,
3860                            window);
3861     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3862                            GNC_PREF_TAB_POSITION_LEFT,
3863                            gnc_main_window_update_tab_position,
3864                            window);
3865     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3866                            GNC_PREF_TAB_POSITION_RIGHT,
3867                            gnc_main_window_update_tab_position,
3868                            window);
3869     gnc_main_window_update_tab_position(NULL, NULL, window);
3870 
3871     gnc_main_window_init_menu_updaters(window);
3872 
3873     /* Testing */
3874     /* Now update the "eXtensions" menu */
3875     if (!gnc_prefs_is_extra_enabled())
3876     {
3877         GtkAction*  action;
3878 
3879         action = gtk_action_group_get_action(priv->action_group,
3880                                              "ExtensionsAction");
3881         gtk_action_set_visible(action, FALSE);
3882     }
3883 
3884     /* GncPluginManager stuff */
3885     manager = gnc_plugin_manager_get ();
3886     plugins = gnc_plugin_manager_get_plugins (manager);
3887     g_list_foreach (plugins, gnc_main_window_add_plugin, window);
3888     g_list_free (plugins);
3889 
3890     g_signal_connect (G_OBJECT (manager), "plugin-added",
3891                       G_CALLBACK (gnc_main_window_plugin_added), window);
3892     g_signal_connect (G_OBJECT (manager), "plugin-removed",
3893                       G_CALLBACK (gnc_main_window_plugin_removed), window);
3894 
3895     LEAVE(" ");
3896 }
3897 
3898 #ifdef MAC_INTEGRATION
3899 /* Event handlers for the shutdown process.  Gnc_quartz_shutdown is
3900  * connected to NSApplicationWillTerminate, the last chance to do
3901  * anything before quitting. The problem is that it's launched from a
3902  * CFRunLoop, not a g_main_loop, and if we call anything that would
3903  * affect the main_loop we get an assert that we're in a subidiary
3904  * loop.
3905  */
3906 static void
gnc_quartz_shutdown(GtkosxApplication * theApp,gpointer data)3907 gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
3908 {
3909     /* Do Nothing. It's too late. */
3910 }
3911 /* Should quit responds to NSApplicationBlockTermination; returning TRUE means
3912  * "don't terminate", FALSE means "do terminate". gnc_main_window_quit() queues
3913  * a timer that starts an orderly shutdown in 250ms and if we tell macOS it's OK
3914  * to quit GnuCash gets terminated instead of doing its orderly shutdown,
3915  * leaving the book locked.
3916  */
3917 static gboolean
gnc_quartz_should_quit(GtkosxApplication * theApp,GncMainWindow * window)3918 gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
3919 {
3920     if (gnc_main_window_all_finish_pending())
3921         gnc_main_window_quit (window);
3922     return TRUE;
3923 }
3924 
3925 static void
gnc_quartz_set_menu(GncMainWindow * window)3926 gnc_quartz_set_menu(GncMainWindow* window)
3927 {
3928     GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
3929     GtkWidget       *menu;
3930     GtkWidget       *item;
3931 
3932     menu = gtk_ui_manager_get_widget (window->ui_merge, "/menubar");
3933     if (GTK_IS_MENU_ITEM (menu))
3934         menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
3935     gtk_widget_hide(menu);
3936     gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL (menu));
3937 
3938     item = gtk_ui_manager_get_widget (window->ui_merge,
3939                                       "/menubar/File/FileQuit");
3940     if (GTK_IS_MENU_ITEM (item))
3941         gtk_widget_hide (GTK_WIDGET (item));
3942 
3943     item = gtk_ui_manager_get_widget (window->ui_merge,
3944                                       "/menubar/Help/HelpAbout");
3945     if (GTK_IS_MENU_ITEM (item))
3946     {
3947         gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 0);
3948     }
3949 
3950     item = gtk_ui_manager_get_widget (window->ui_merge,
3951                                       "/menubar/Edit/EditPreferences");
3952     if (GTK_IS_MENU_ITEM (item))
3953     {
3954         gtkosx_application_insert_app_menu_item (theApp,
3955                 gtk_separator_menu_item_new (), 1);
3956         gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 2);
3957     }
3958 
3959     item = gtk_ui_manager_get_widget (window->ui_merge,
3960                                       "/menubar/Help");
3961     gtkosx_application_set_help_menu(theApp, GTK_MENU_ITEM(item));
3962     item = gtk_ui_manager_get_widget (window->ui_merge,
3963                                       "/menubar/Windows");
3964     gtkosx_application_set_window_menu(theApp, GTK_MENU_ITEM(item));
3965     g_signal_connect(theApp, "NSApplicationBlockTermination",
3966                      G_CALLBACK(gnc_quartz_should_quit), window);
3967     gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
3968     g_object_unref (theApp);
3969 
3970 }
3971 #endif //MAC_INTEGRATION
3972 
3973 /* Callbacks */
3974 static void
gnc_main_window_add_widget(GtkUIManager * merge,GtkWidget * widget,GncMainWindow * window)3975 gnc_main_window_add_widget (GtkUIManager *merge,
3976                             GtkWidget *widget,
3977                             GncMainWindow *window)
3978 {
3979     GncMainWindowPrivate *priv;
3980 
3981     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3982     if (GTK_IS_TOOLBAR (widget))
3983     {
3984         priv->toolbar = widget;
3985         gtk_toolbar_set_style (GTK_TOOLBAR(priv->toolbar),
3986                                GTK_TOOLBAR_BOTH);
3987         gtk_toolbar_set_icon_size (GTK_TOOLBAR(priv->toolbar),
3988                                    GTK_ICON_SIZE_SMALL_TOOLBAR);
3989     }
3990 
3991     gtk_box_pack_start (GTK_BOX (priv->menu_dock), widget, FALSE, FALSE, 0);
3992     gtk_widget_show (widget);
3993 }
3994 
3995 /** Should a summary bar be visible in this window?  In order to
3996  *  prevent synchronization issues, the "ViewSummaryBar"
3997  *  GtkToggleAction is the sole source of information for whether or
3998  *  not any summary bar should be visible in a window.
3999  *
4000  *  @param window A pointer to the window in question.
4001  *
4002  *  @param action If known, a pointer to the "ViewSummaryBar"
4003  *  GtkToggleAction.  If NULL, the function will look up this action.
4004  *
4005  *  @return TRUE if the summarybar should be visible.
4006  */
4007 static gboolean
gnc_main_window_show_summarybar(GncMainWindow * window,GtkAction * action)4008 gnc_main_window_show_summarybar (GncMainWindow *window, GtkAction *action)
4009 {
4010     GncMainWindowPrivate *priv;
4011 
4012     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4013     if (action == NULL)
4014         action = gtk_action_group_get_action(priv->action_group,
4015                                              "ViewSummaryAction");
4016     if (action == NULL)
4017         return TRUE;
4018     return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
4019 }
4020 
4021 /** This function is invoked when the GtkNotebook switches pages.  It
4022  *  is responsible for updating the rest of the window contents
4023  *  outside of the notebook.  I.E. Updating the user interface, the
4024  *  summary bar, etc.  This function also emits the "page_changed"
4025  *  signal from the window so that any plugin can also learn about the
4026  *  fact that the page has changed.
4027  *
4028  *  @internal
4029  */
4030 static void
gnc_main_window_switch_page(GtkNotebook * notebook,gpointer * notebook_page,gint pos,GncMainWindow * window)4031 gnc_main_window_switch_page (GtkNotebook *notebook,
4032                              gpointer *notebook_page,
4033                              gint pos,
4034                              GncMainWindow *window)
4035 {
4036     GncMainWindowPrivate *priv;
4037     GtkWidget *child;
4038     GncPluginPage *page;
4039     gboolean visible;
4040 
4041     ENTER("Notebook %p, page, %p, index %d, window %p",
4042           notebook, notebook_page, pos, window);
4043     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4044 
4045     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4046     if (priv->current_page != NULL)
4047     {
4048         page = priv->current_page;
4049         gnc_plugin_page_unmerge_actions (page, window->ui_merge);
4050         gnc_plugin_page_unselected (page);
4051     }
4052 
4053     child = gtk_notebook_get_nth_page (notebook, pos);
4054     if (child)
4055     {
4056         page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
4057     }
4058     else
4059     {
4060         page = NULL;
4061     }
4062 
4063     priv->current_page = page;
4064 
4065     if (page != NULL)
4066     {
4067         /* Update the user interface (e.g. menus and toolbars */
4068         gnc_plugin_page_merge_actions (page, window->ui_merge);
4069         visible = gnc_main_window_show_summarybar(window, NULL);
4070         gnc_plugin_page_show_summarybar (page, visible);
4071 
4072         /* Allow page specific actions */
4073         gnc_plugin_page_selected (page);
4074         gnc_window_update_status (GNC_WINDOW(window), page);
4075 
4076         /* Update the page reference info */
4077         priv->usage_order = g_list_remove (priv->usage_order, page);
4078         priv->usage_order = g_list_prepend (priv->usage_order, page);
4079     }
4080 
4081     gnc_plugin_update_actions(priv->action_group,
4082                               multiple_page_actions,
4083                               "sensitive",
4084                               g_list_length(priv->installed_pages) > 1);
4085 
4086     gnc_main_window_update_title(window);
4087 #ifndef MAC_INTEGRATION
4088     gnc_main_window_update_menu_item(window);
4089 #endif
4090     g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4091     LEAVE(" ");
4092 }
4093 
4094 /** This function is invoked when a GtkNotebook tab gets reordered by
4095  *  drag and drop. It adjusts the list installed_pages to reflect the new
4096  *  ordering so that GnuCash saves and restores the tabs correctly.
4097  *
4098  *  @internal
4099  */
4100 static void
gnc_main_window_page_reordered(GtkNotebook * notebook,GtkWidget * child,guint pos,GncMainWindow * window)4101 gnc_main_window_page_reordered (GtkNotebook *notebook,
4102                                 GtkWidget *child,
4103                                 guint pos,
4104                                 GncMainWindow *window)
4105 {
4106     GncMainWindowPrivate *priv;
4107     GncPluginPage *page;
4108     GList *old_link;
4109 
4110     ENTER("Notebook %p, child %p, index %d, window %p",
4111           notebook, child, pos, window);
4112     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4113 
4114     if (!child) return;
4115 
4116     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4117 
4118     page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
4119     if (!page) return;
4120 
4121     old_link = g_list_find (priv->installed_pages, page);
4122     if (!old_link) return;
4123 
4124     priv->installed_pages = g_list_delete_link (priv->installed_pages,
4125                             old_link);
4126     priv->installed_pages = g_list_insert (priv->installed_pages,
4127                                            page, pos);
4128 
4129     LEAVE(" ");
4130 }
4131 
4132 static void
gnc_main_window_plugin_added(GncPlugin * manager,GncPlugin * plugin,GncMainWindow * window)4133 gnc_main_window_plugin_added (GncPlugin *manager,
4134                               GncPlugin *plugin,
4135                               GncMainWindow *window)
4136 {
4137     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4138     g_return_if_fail (GNC_IS_PLUGIN (plugin));
4139 
4140     gnc_plugin_add_to_window (plugin, window, window_type);
4141 }
4142 
4143 static void
gnc_main_window_plugin_removed(GncPlugin * manager,GncPlugin * plugin,GncMainWindow * window)4144 gnc_main_window_plugin_removed (GncPlugin *manager,
4145                                 GncPlugin *plugin,
4146                                 GncMainWindow *window)
4147 {
4148     g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4149     g_return_if_fail (GNC_IS_PLUGIN (plugin));
4150 
4151     gnc_plugin_remove_from_window (plugin, window, window_type);
4152 }
4153 
4154 
4155 /* Command callbacks */
4156 static void
gnc_main_window_cmd_page_setup(GtkAction * action,GncMainWindow * window)4157 gnc_main_window_cmd_page_setup (GtkAction *action,
4158                                 GncMainWindow *window)
4159 {
4160     GtkWindow *gtk_window;
4161 
4162     g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4163 
4164     gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
4165     gnc_ui_page_setup(gtk_window);
4166 }
4167 
4168 gboolean
gnc_book_options_dialog_apply_helper(GNCOptionDB * options)4169 gnc_book_options_dialog_apply_helper(GNCOptionDB * options)
4170 {
4171     QofBook *book = gnc_get_current_book ();
4172     gboolean use_split_action_for_num_before =
4173         qof_book_use_split_action_for_num_field (book);
4174     gboolean use_book_currency_before =
4175         gnc_book_use_book_currency (book);
4176     gint use_read_only_threshold_before =
4177         qof_book_get_num_days_autoreadonly (book);
4178     gboolean use_split_action_for_num_after;
4179     gboolean use_book_currency_after;
4180     gint use_read_only_threshold_after;
4181     gboolean return_val = FALSE;
4182     GList *results = NULL, *iter;
4183 
4184     if (!options) return return_val;
4185 
4186     results = gnc_option_db_commit (options);
4187     for (iter = results; iter; iter = iter->next)
4188     {
4189         GtkWidget *dialog = gtk_message_dialog_new(gnc_ui_get_main_window (NULL),
4190                                                    0,
4191                                                    GTK_MESSAGE_ERROR,
4192                                                    GTK_BUTTONS_OK,
4193                                                    "%s",
4194                                                    (char*)iter->data);
4195         gtk_dialog_run(GTK_DIALOG(dialog));
4196         gtk_widget_destroy(dialog);
4197         g_free (iter->data);
4198     }
4199     g_list_free (results);
4200     qof_book_begin_edit (book);
4201     qof_book_save_options (book, gnc_option_db_save, options, TRUE);
4202     use_split_action_for_num_after =
4203         qof_book_use_split_action_for_num_field (book);
4204     use_book_currency_after = gnc_book_use_book_currency (book);
4205 
4206     // mark cached value as invalid so we get new value
4207     book->cached_num_days_autoreadonly_isvalid = FALSE;
4208     use_read_only_threshold_after = qof_book_get_num_days_autoreadonly (book);
4209 
4210     if (use_split_action_for_num_before != use_split_action_for_num_after)
4211     {
4212         gnc_book_option_num_field_source_change_cb (
4213                                                 use_split_action_for_num_after);
4214         return_val = TRUE;
4215     }
4216     if (use_book_currency_before != use_book_currency_after)
4217     {
4218         gnc_book_option_book_currency_selected_cb (use_book_currency_after);
4219         return_val = TRUE;
4220     }
4221     if (use_read_only_threshold_before != use_read_only_threshold_after)
4222         return_val = TRUE;
4223 
4224     qof_book_commit_edit (book);
4225     return return_val;
4226 }
4227 
4228 static void
gnc_book_options_dialog_apply_cb(GNCOptionWin * optionwin,gpointer user_data)4229 gnc_book_options_dialog_apply_cb(GNCOptionWin * optionwin,
4230                                  gpointer user_data)
4231 {
4232     GNCOptionDB * options = user_data;
4233 
4234     if (!options) return;
4235 
4236     if (gnc_book_options_dialog_apply_helper (options))
4237         gnc_gui_refresh_all ();
4238 }
4239 
4240 static void
gnc_book_options_dialog_close_cb(GNCOptionWin * optionwin,gpointer user_data)4241 gnc_book_options_dialog_close_cb(GNCOptionWin * optionwin,
4242                                  gpointer user_data)
4243 {
4244     GNCOptionDB * options = user_data;
4245 
4246     gnc_options_dialog_destroy(optionwin);
4247     gnc_option_db_destroy(options);
4248 }
4249 
4250 /** Calls gnc_book_option_num_field_source_change to initiate registered
4251  * callbacks when num_field_source book option changes so that
4252  * registers/reports can update themselves; sets feature flag */
4253 void
gnc_book_option_num_field_source_change_cb(gboolean num_action)4254 gnc_book_option_num_field_source_change_cb (gboolean num_action)
4255 {
4256     gnc_suspend_gui_refresh ();
4257     if (num_action)
4258     {
4259         /* Set a feature flag in the book for use of the split action field as number.
4260          * This will prevent older GnuCash versions that don't support this feature
4261          * from opening this file. */
4262         gnc_features_set_used (gnc_get_current_book(),
4263                                GNC_FEATURE_NUM_FIELD_SOURCE);
4264     }
4265     gnc_book_option_num_field_source_change (num_action);
4266     gnc_resume_gui_refresh ();
4267 }
4268 
4269 /** Calls gnc_book_option_book_currency_selected to initiate registered
4270  * callbacks when currency accounting book option changes to book-currency so
4271  * that registers/reports can update themselves; sets feature flag */
4272 void
gnc_book_option_book_currency_selected_cb(gboolean use_book_currency)4273 gnc_book_option_book_currency_selected_cb (gboolean use_book_currency)
4274 {
4275     gnc_suspend_gui_refresh ();
4276     if (use_book_currency)
4277     {
4278         /* Set a feature flag in the book for use of book currency. This will
4279          * prevent older GnuCash versions that don't support this feature from
4280          * opening this file. */
4281         gnc_features_set_used (gnc_get_current_book(),
4282                                GNC_FEATURE_BOOK_CURRENCY);
4283     }
4284     gnc_book_option_book_currency_selected (use_book_currency);
4285     gnc_resume_gui_refresh ();
4286 }
4287 
4288 static gboolean
show_handler(const char * class_name,gint component_id,gpointer user_data,gpointer iter_data)4289 show_handler (const char *class_name, gint component_id,
4290               gpointer user_data, gpointer iter_data)
4291 {
4292     GNCOptionWin *optwin = user_data;
4293     GtkWidget *widget;
4294 
4295     if (!optwin)
4296         return(FALSE);
4297 
4298     widget = gnc_options_dialog_widget(optwin);
4299 
4300     gtk_window_present(GTK_WINDOW(widget));
4301     return(TRUE);
4302 }
4303 
4304 GtkWidget *
gnc_book_options_dialog_cb(gboolean modal,gchar * title,GtkWindow * parent)4305 gnc_book_options_dialog_cb (gboolean modal, gchar *title, GtkWindow* parent)
4306 {
4307     QofBook *book = gnc_get_current_book ();
4308     GNCOptionDB *options;
4309     GNCOptionWin *optionwin;
4310 
4311     options = gnc_option_db_new_for_type (QOF_ID_BOOK);
4312     qof_book_load_options (book, gnc_option_db_load, options);
4313     gnc_option_db_clean (options);
4314 
4315     /* Only allow one Book Options dialog if called from file->properties
4316        menu */
4317     if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
4318                                   show_handler, NULL))
4319     {
4320         return NULL;
4321     }
4322     optionwin = gnc_options_dialog_new_modal (
4323         modal,
4324         (title ? title : _( "Book Options")),
4325         DIALOG_BOOK_OPTIONS_CM_CLASS, parent);
4326     gnc_options_dialog_build_contents (optionwin, options);
4327 
4328     gnc_options_dialog_set_book_options_help_cb (optionwin);
4329 
4330     gnc_options_dialog_set_apply_cb (optionwin,
4331                                      gnc_book_options_dialog_apply_cb,
4332                                      (gpointer)options);
4333     gnc_options_dialog_set_close_cb (optionwin,
4334                                      gnc_book_options_dialog_close_cb,
4335                                      (gpointer)options);
4336     if (modal)
4337         gnc_options_dialog_set_new_book_option_values (options);
4338     return gnc_options_dialog_widget (optionwin);
4339 }
4340 
4341 static void
gnc_main_window_cmd_file_properties(GtkAction * action,GncMainWindow * window)4342 gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window)
4343 {
4344     gnc_book_options_dialog_cb (FALSE, NULL, GTK_WINDOW (window));
4345 }
4346 
4347 static void
gnc_main_window_cmd_file_close(GtkAction * action,GncMainWindow * window)4348 gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window)
4349 {
4350     GncMainWindowPrivate *priv;
4351     GncPluginPage *page;
4352 
4353     g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4354 
4355     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4356     page = priv->current_page;
4357     gnc_main_window_close_page(page);
4358 }
4359 
4360 static void
gnc_main_window_cmd_file_quit(GtkAction * action,GncMainWindow * window)4361 gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window)
4362 {
4363     if (!gnc_main_window_all_finish_pending())
4364         return;
4365 
4366     gnc_main_window_quit(window);
4367 }
4368 
4369 static void
gnc_main_window_cmd_edit_cut(GtkAction * action,GncMainWindow * window)4370 gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window)
4371 {
4372     GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4373 
4374     if (GTK_IS_EDITABLE(widget))
4375     {
4376         gtk_editable_cut_clipboard (GTK_EDITABLE(widget));
4377     }
4378     else if (GTK_IS_TEXT_VIEW(widget))
4379     {
4380         GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4381         GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4382                                                             GDK_SELECTION_CLIPBOARD);
4383         gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4384 
4385         if (clipboard)
4386             gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
4387     }
4388 }
4389 
4390 static void
gnc_main_window_cmd_edit_copy(GtkAction * action,GncMainWindow * window)4391 gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window)
4392 {
4393     GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4394 
4395     if (GTK_IS_EDITABLE(widget))
4396     {
4397         gtk_editable_copy_clipboard (GTK_EDITABLE(widget));
4398     }
4399     else if (GTK_IS_TEXT_VIEW(widget))
4400     {
4401         GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4402         GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4403                                                             GDK_SELECTION_CLIPBOARD);
4404         if (clipboard)
4405             gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
4406     }
4407 }
4408 
4409 static void
gnc_main_window_cmd_edit_paste(GtkAction * action,GncMainWindow * window)4410 gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window)
4411 {
4412     GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4413 
4414     if (GTK_IS_EDITABLE(widget))
4415     {
4416         gtk_editable_paste_clipboard (GTK_EDITABLE(widget));
4417     }
4418     else if (GTK_IS_TEXT_VIEW(widget))
4419     {
4420         GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4421         GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4422                                                             GDK_SELECTION_CLIPBOARD);
4423         gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4424 
4425         if (clipboard)
4426             gtk_text_buffer_paste_clipboard (text_buffer, clipboard, NULL, editable);
4427     }
4428 }
4429 
4430 static void
gnc_main_window_cmd_edit_preferences(GtkAction * action,GncMainWindow * window)4431 gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window)
4432 {
4433     gnc_preferences_dialog (GTK_WINDOW(window));
4434 }
4435 
4436 static void
gnc_main_window_cmd_view_refresh(GtkAction * action,GncMainWindow * window)4437 gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window)
4438 {
4439 }
4440 
4441 static void
gnc_main_window_cmd_actions_reset_warnings(GtkAction * action,GncMainWindow * window)4442 gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window)
4443 {
4444     gnc_reset_warnings_dialog(GTK_WINDOW(window));
4445 }
4446 
4447 static void
gnc_main_window_cmd_actions_rename_page(GtkAction * action,GncMainWindow * window)4448 gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window)
4449 {
4450     GncMainWindowPrivate *priv;
4451     GncPluginPage *page;
4452     GtkWidget *label, *entry;
4453 
4454     ENTER(" ");
4455     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4456     page = priv->current_page;
4457     if (!page)
4458     {
4459         LEAVE("No current page");
4460         return;
4461     }
4462 
4463     if (!main_window_find_tab_items(window, page, &label, &entry))
4464     {
4465         LEAVE("can't find required widgets");
4466         return;
4467     }
4468 
4469     gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
4470     gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
4471     gtk_widget_hide(label);
4472     gtk_widget_show(entry);
4473     gtk_widget_grab_focus(entry);
4474     LEAVE("opened for editing");
4475 }
4476 
4477 static void
gnc_main_window_cmd_view_toolbar(GtkAction * action,GncMainWindow * window)4478 gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window)
4479 {
4480     GncMainWindowPrivate *priv;
4481 
4482     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4483     if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
4484     {
4485         gtk_widget_show (priv->toolbar);
4486     }
4487     else
4488     {
4489         gtk_widget_hide (priv->toolbar);
4490     }
4491 }
4492 
4493 static void
gnc_main_window_cmd_view_summary(GtkAction * action,GncMainWindow * window)4494 gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window)
4495 {
4496     GncMainWindowPrivate *priv;
4497     GList *item;
4498     gboolean visible;
4499 
4500     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4501     visible = gnc_main_window_show_summarybar(window, action);
4502     for (item = priv->installed_pages; item; item = g_list_next(item))
4503     {
4504         gnc_plugin_page_show_summarybar(item->data, visible);
4505     }
4506 }
4507 
4508 static void
gnc_main_window_cmd_view_statusbar(GtkAction * action,GncMainWindow * window)4509 gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window)
4510 {
4511     GncMainWindowPrivate *priv;
4512 
4513     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4514     if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
4515     {
4516         gtk_widget_show (priv->statusbar);
4517     }
4518     else
4519     {
4520         gtk_widget_hide (priv->statusbar);
4521     }
4522 }
4523 
4524 static void
gnc_main_window_cmd_window_new(GtkAction * action,GncMainWindow * window)4525 gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window)
4526 {
4527     GncMainWindow *new_window;
4528 
4529     /* Create the new window */
4530     ENTER(" ");
4531     new_window = gnc_main_window_new ();
4532     gtk_widget_show(GTK_WIDGET(new_window));
4533     LEAVE(" ");
4534 }
4535 
4536 static void
gnc_main_window_cmd_window_move_page(GtkAction * action,GncMainWindow * window)4537 gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window)
4538 {
4539     GncMainWindowPrivate *priv;
4540     GncMainWindow *new_window;
4541     GncPluginPage *page;
4542     GtkNotebook *notebook;
4543     GtkWidget *tab_widget, *menu_widget;
4544 
4545     ENTER("action %p,window %p", action, window);
4546 
4547     /* Setup */
4548     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4549     page = priv->current_page;
4550     if (!page)
4551     {
4552         LEAVE("invalid page");
4553         return;
4554     }
4555     if (!page->notebook_page)
4556     {
4557         LEAVE("invalid notebook_page");
4558         return;
4559     }
4560 
4561     notebook = GTK_NOTEBOOK (priv->notebook);
4562     tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
4563     menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
4564 
4565     // Remove the page_changed signal callback
4566     gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
4567 
4568     /* Ref the page components, then remove it from its old window */
4569     g_object_ref(page);
4570     g_object_ref(tab_widget);
4571     g_object_ref(menu_widget);
4572     g_object_ref(page->notebook_page);
4573     gnc_main_window_disconnect(window, page);
4574 
4575     /* Create the new window */
4576     new_window = gnc_main_window_new ();
4577     gtk_widget_show(GTK_WIDGET(new_window));
4578 
4579     /* Now add the page to the new window */
4580     gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
4581 
4582     /* Unref the page components now that we're done */
4583     g_object_unref(page->notebook_page);
4584     g_object_unref(menu_widget);
4585     g_object_unref(tab_widget);
4586     g_object_unref(page);
4587 
4588     /* just a little debugging. :-) */
4589     DEBUG("Moved page %p from window %p to new window %p",
4590           page, window, new_window);
4591     DEBUG("Old window current is %p, new window current is %p",
4592           priv->current_page, priv->current_page);
4593 
4594     LEAVE("page moved");
4595 }
4596 
4597 #ifndef MAC_INTEGRATION
4598 static void
gnc_main_window_cmd_window_raise(GtkAction * action,GtkRadioAction * current,GncMainWindow * old_window)4599 gnc_main_window_cmd_window_raise (GtkAction *action,
4600                                   GtkRadioAction *current,
4601                                   GncMainWindow *old_window)
4602 {
4603     GncMainWindow *new_window;
4604     gint value;
4605 
4606     g_return_if_fail(GTK_IS_ACTION(action));
4607     g_return_if_fail(GTK_IS_RADIO_ACTION(current));
4608     g_return_if_fail(GNC_IS_MAIN_WINDOW(old_window));
4609 
4610     ENTER("action %p, current %p, window %p", action, current, old_window);
4611     value = gtk_radio_action_get_current_value(current);
4612     new_window = g_list_nth_data(active_windows, value);
4613     gtk_window_present(GTK_WINDOW(new_window));
4614     /* revert the change in the radio group
4615      * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
4616     g_idle_add((GSourceFunc)gnc_main_window_update_radio_button, old_window);
4617     LEAVE(" ");
4618 }
4619 #endif /* !MAC_INTEGRATION */
4620 
4621 static void
gnc_main_window_cmd_help_tutorial(GtkAction * action,GncMainWindow * window)4622 gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window)
4623 {
4624     gnc_gnome_help (GTK_WINDOW(window), HF_GUIDE, NULL);
4625 }
4626 
4627 static void
gnc_main_window_cmd_help_contents(GtkAction * action,GncMainWindow * window)4628 gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window)
4629 {
4630     gnc_gnome_help (GTK_WINDOW(window), HF_HELP, NULL);
4631 }
4632 
4633 /** This is a helper function to find a data file and suck it into
4634  *  memory.
4635  *
4636  *  @param partial The name of the file relative to the gnucash
4637  *  specific shared data directory.
4638  *
4639  *  @return The text of the file or NULL. The caller is responsible
4640  *  for freeing this string.
4641  */
4642 static gchar *
get_file(const gchar * partial)4643 get_file (const gchar *partial)
4644 {
4645     gchar *filename, *text = NULL;
4646     gsize length;
4647 
4648     filename = gnc_filepath_locate_doc_file(partial);
4649     if (filename && g_file_get_contents(filename, &text, &length, NULL))
4650     {
4651         if (length)
4652         {
4653             g_free(filename);
4654             return text;
4655         }
4656         g_free(text);
4657     }
4658     g_free (filename);
4659     return NULL;
4660 }
4661 
4662 
4663 /** This is a helper function to find a data file, suck it into
4664  *  memory, and split it into an array of strings.
4665  *
4666  *  @param partial The name of the file relative to the gnucash
4667  *  specific shared data directory.
4668  *
4669  *  @return The text of the file as an array of strings, or NULL. The
4670  *  caller is responsible for freeing all the strings and the array.
4671  */
4672 static gchar **
get_file_strsplit(const gchar * partial)4673 get_file_strsplit (const gchar *partial)
4674 {
4675     gchar *text, **lines;
4676 
4677     text = get_file(partial);
4678     if (!text)
4679         return NULL;
4680 
4681     lines = g_strsplit_set(text, "\r\n", -1);
4682     g_free(text);
4683     return lines;
4684 }
4685 /** URL activation callback.
4686  *  Use our own function to activate the URL in the users browser
4687  *  instead of gtk_show_uri(), which requires gvfs.
4688  *  Signature described in gtk docs at GtkAboutDialog activate-link signal.
4689  */
4690 
4691 static gboolean
url_signal_cb(GtkAboutDialog * dialog,gchar * uri,gpointer data)4692 url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
4693 {
4694     gnc_launch_doclink (GTK_WINDOW(dialog), uri);
4695     return TRUE;
4696 }
4697 
4698 /** Create and display the "about" dialog for gnucash.
4699  *
4700  *  @param action The GtkAction for the "about" menu item.
4701  *
4702  *  @param window The main window whose menu item was activated.
4703  */
4704 static void
gnc_main_window_cmd_help_about(GtkAction * action,GncMainWindow * window)4705 gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
4706 {
4707     /* Translators: %s will be replaced with the current year */
4708     gchar *copyright = g_strdup_printf(_("Copyright © 1997-%s The GnuCash contributors."),
4709                                        GNC_VCS_REV_YEAR);
4710     gchar **authors = get_file_strsplit("AUTHORS");
4711     gchar **documenters = get_file_strsplit("DOCUMENTERS");
4712     gchar *license = get_file("LICENSE");
4713     GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
4714     GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
4715                                                 GNC_ICON_APP,
4716                                                 128,
4717                                                 GTK_ICON_LOOKUP_USE_BUILTIN,
4718                                                 NULL);
4719     gchar *version = g_strdup_printf ("%s: %s\n%s: %s\nFinance::Quote: %s",
4720                                       _("Version"), gnc_version(),
4721                                       _("Build ID"), gnc_build_id(),
4722                                       gnc_quote_source_fq_version ()
4723                                       ? gnc_quote_source_fq_version ()
4724                                       : "-");
4725     GtkDialog *dialog = GTK_DIALOG (gtk_about_dialog_new ());
4726     g_object_set (G_OBJECT (dialog),
4727                   "authors", authors,
4728                   "documenters", documenters,
4729                   "comments", _("Accounting for personal and small business finance."),
4730                   "copyright", copyright,
4731                   "license", license,
4732                   "logo", logo,
4733                   "name", "GnuCash",
4734                   /* Translators: the following string will be shown in Help->About->Credits
4735                      Enter your name or that of your team and an email contact for feedback.
4736                      The string can have multiple rows, so you can also add a list of
4737                      contributors. */
4738                   "translator-credits", _("translator-credits"),
4739                   "version", version,
4740                   "website", PACKAGE_URL,
4741                   "website-label", _("Visit the GnuCash website."),
4742                   NULL);
4743 
4744     g_free(version);
4745     g_free(copyright);
4746     if (license)
4747         g_free(license);
4748     if (documenters)
4749         g_strfreev(documenters);
4750     if (authors)
4751         g_strfreev(authors);
4752     g_object_unref (logo);
4753     g_signal_connect (dialog, "activate-link",
4754                       G_CALLBACK (url_signal_cb), NULL);
4755     /* Set dialog to resize. */
4756     gtk_window_set_resizable(GTK_WINDOW (dialog), TRUE);
4757 
4758     gtk_window_set_transient_for (GTK_WINDOW (dialog),
4759                                   GTK_WINDOW (window));
4760     gtk_dialog_run (dialog);
4761     gtk_widget_destroy (GTK_WIDGET (dialog));
4762 }
4763 
4764 
4765 /************************************************************
4766  *                                                          *
4767  ************************************************************/
4768 
4769 void
gnc_main_window_show_all_windows(void)4770 gnc_main_window_show_all_windows(void)
4771 {
4772     GList *window_iter;
4773 #ifdef MAC_INTEGRATION
4774     GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
4775 #endif
4776     for (window_iter = active_windows; window_iter != NULL; window_iter = window_iter->next)
4777     {
4778         gtk_widget_show(GTK_WIDGET(window_iter->data));
4779     }
4780 #ifdef MAC_INTEGRATION
4781     g_signal_connect(theApp, "NSApplicationWillTerminate",
4782                      G_CALLBACK(gnc_quartz_shutdown), NULL);
4783     gtkosx_application_ready(theApp);
4784     g_object_unref (theApp);
4785 #endif
4786 }
4787 
4788 GtkWindow *
gnc_ui_get_gtk_window(GtkWidget * widget)4789 gnc_ui_get_gtk_window (GtkWidget *widget)
4790 {
4791     GtkWidget *toplevel;
4792 
4793     if (!widget)
4794         return NULL;
4795 
4796     toplevel = gtk_widget_get_toplevel (widget);
4797     if (toplevel && GTK_IS_WINDOW (toplevel))
4798         return GTK_WINDOW (toplevel);
4799     else
4800         return NULL;
4801 }
4802 
4803 GtkWindow *
gnc_ui_get_main_window(GtkWidget * widget)4804 gnc_ui_get_main_window (GtkWidget *widget)
4805 {
4806     GList *window;
4807 
4808     GtkWindow *toplevel = gnc_ui_get_gtk_window (widget);
4809     while (toplevel && !GNC_IS_MAIN_WINDOW (toplevel))
4810         toplevel = gtk_window_get_transient_for(toplevel);
4811 
4812     if (toplevel)
4813         return toplevel;
4814 
4815     for (window = active_windows; window; window = window->next)
4816         if (gtk_window_is_active (GTK_WINDOW (window->data)))
4817             return window->data;
4818 
4819     for (window = active_windows; window; window = window->next)
4820         if (gtk_widget_get_mapped (GTK_WIDGET(window->data)))
4821             return window->data;
4822 
4823     return NULL;
4824 }
4825 
4826 
4827 /** Retrieve the gtk window associated with a main window object.
4828  *  This function is called via a vector off a generic window
4829  *  interface.
4830  *
4831  *  @param window A pointer to a generic window. */
4832 static GtkWindow *
gnc_main_window_get_gtk_window(GncWindow * window)4833 gnc_main_window_get_gtk_window (GncWindow *window)
4834 {
4835     g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
4836     return GTK_WINDOW(window);
4837 }
4838 
4839 
4840 /** Retrieve the status bar associated with a main window object.
4841  *  This function is called via a vector off a generic window
4842  *  interface.
4843  *
4844  *  @param window_in A pointer to a generic window. */
4845 static GtkWidget *
gnc_main_window_get_statusbar(GncWindow * window_in)4846 gnc_main_window_get_statusbar (GncWindow *window_in)
4847 {
4848     GncMainWindowPrivate *priv;
4849     GncMainWindow *window;
4850 
4851     g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
4852 
4853     window = GNC_MAIN_WINDOW(window_in);
4854     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4855     return priv->statusbar;
4856 }
4857 
4858 
4859 /** Retrieve the progress bar associated with a main window object.
4860  *  This function is called via a vector off a generic window
4861  *  interface.
4862  *
4863  *  @param window_in A pointer to a generic window. */
4864 static GtkWidget *
gnc_main_window_get_progressbar(GncWindow * window_in)4865 gnc_main_window_get_progressbar (GncWindow *window_in)
4866 {
4867     GncMainWindowPrivate *priv;
4868     GncMainWindow *window;
4869 
4870     g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
4871 
4872     window = GNC_MAIN_WINDOW(window_in);
4873     priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4874     return priv->progressbar;
4875 }
4876 
4877 
4878 static void
gnc_main_window_all_ui_set_sensitive(GncWindow * unused,gboolean sensitive)4879 gnc_main_window_all_ui_set_sensitive (GncWindow *unused, gboolean sensitive)
4880 {
4881     GncMainWindow *window;
4882     GncMainWindowPrivate *priv;
4883     GList *groupp, *groups, *winp, *tmp;
4884     GtkWidget *close_button;
4885 
4886     for (winp = active_windows; winp; winp = g_list_next(winp))
4887     {
4888         window = winp->data;
4889         priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4890 
4891         groups = gtk_ui_manager_get_action_groups(window->ui_merge);
4892         for (groupp = groups; groupp; groupp = g_list_next(groupp))
4893         {
4894             gtk_action_group_set_sensitive(GTK_ACTION_GROUP(groupp->data), sensitive);
4895         }
4896 
4897         for (tmp = priv->installed_pages; tmp; tmp = g_list_next(tmp))
4898         {
4899             close_button = g_object_get_data(tmp->data, PLUGIN_PAGE_CLOSE_BUTTON);
4900             if (!close_button)
4901                 continue;
4902             gtk_widget_set_sensitive (close_button, sensitive);
4903         }
4904     }
4905 }
4906 
4907 
4908 /** Initialize the generic window interface for a main window.
4909  *
4910  *  @param iface A pointer to the interface data structure to
4911  *  populate. */
4912 static void
gnc_window_main_window_init(GncWindowIface * iface)4913 gnc_window_main_window_init (GncWindowIface *iface)
4914 {
4915     iface->get_gtk_window  = gnc_main_window_get_gtk_window;
4916     iface->get_statusbar   = gnc_main_window_get_statusbar;
4917     iface->get_progressbar = gnc_main_window_get_progressbar;
4918     iface->ui_set_sensitive = gnc_main_window_all_ui_set_sensitive;
4919 }
4920 
4921 
4922 /*  Set the window where all progressbar updates should occur.  This
4923  *  is a wrapper around the gnc_window_set_progressbar_window()
4924  *  function.
4925  */
4926 void
gnc_main_window_set_progressbar_window(GncMainWindow * window)4927 gnc_main_window_set_progressbar_window (GncMainWindow *window)
4928 {
4929     GncWindow *gncwin;
4930     gncwin = GNC_WINDOW(window);
4931     gnc_window_set_progressbar_window(gncwin);
4932 }
4933 
4934 
4935 /** Popup a contextual menu.  This function ends up being called when
4936  *  the user right-clicks in the context of a window, or uses the
4937  *  keyboard context-menu request key combination (Shift-F10 by
4938  *  default).
4939  *
4940  *  @param page This is the GncPluginPage corresponding to the visible
4941  *  page.
4942  *
4943  *  @param event The event parameter passed to the "button-press"
4944  *  callback.  May be null if there was no event (aka keyboard
4945  *  request).
4946  */
4947 static void
do_popup_menu(GncPluginPage * page,GdkEventButton * event)4948 do_popup_menu(GncPluginPage *page, GdkEventButton *event)
4949 {
4950     GtkUIManager *ui_merge;
4951     GtkWidget *menu;
4952 
4953     g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
4954 
4955     ENTER("page %p, event %p", page, event);
4956     ui_merge = gnc_plugin_page_get_ui_merge(page);
4957     if (ui_merge == NULL)
4958     {
4959         LEAVE("no ui merge");
4960         return;
4961     }
4962 
4963     menu = gtk_ui_manager_get_widget(ui_merge, "/MainPopup");
4964     if (!menu)
4965     {
4966         LEAVE("no menu");
4967         return;
4968     }
4969     gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
4970 
4971     LEAVE(" ");
4972 }
4973 
4974 
4975 /** Callback function invoked when the user requests that Gnucash
4976  *  popup the contextual menu via the keyboard context-menu request
4977  *  key combination (Shift-F10 by default).
4978  *
4979  *  @param page This is the GncPluginPage corresponding to the visible
4980  *  page.
4981  *
4982  *  @param widget Whatever widget had focus when the user issued the
4983  *  keyboard context-menu request.
4984  *
4985  *  @return Always returns TRUE to indicate that the menu request was
4986  *  handled.
4987  */
4988 gboolean
gnc_main_window_popup_menu_cb(GtkWidget * widget,GncPluginPage * page)4989 gnc_main_window_popup_menu_cb (GtkWidget *widget,
4990                                GncPluginPage *page)
4991 {
4992     ENTER("widget %p, page %p", widget, page);
4993     do_popup_menu(page, NULL);
4994     LEAVE(" ");
4995     return TRUE;
4996 }
4997 
4998 
4999 /*  Callback function invoked when the user clicks in the content of
5000  *  any Gnucash window.  If this was a "right-click" then Gnucash will
5001  *  popup the contextual menu.
5002  */
5003 gboolean
gnc_main_window_button_press_cb(GtkWidget * whatever,GdkEventButton * event,GncPluginPage * page)5004 gnc_main_window_button_press_cb (GtkWidget *whatever,
5005                                  GdkEventButton *event,
5006                                  GncPluginPage *page)
5007 {
5008     g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
5009 
5010     ENTER("widget %p, event %p, page %p", whatever, event, page);
5011     /* Ignore double-clicks and triple-clicks */
5012     if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
5013     {
5014         do_popup_menu(page, event);
5015         LEAVE("menu shown");
5016         return TRUE;
5017     }
5018 
5019     LEAVE("other click");
5020     return FALSE;
5021 }
5022 
5023 void
gnc_main_window_all_action_set_sensitive(const gchar * action_name,gboolean sensitive)5024 gnc_main_window_all_action_set_sensitive (const gchar *action_name,
5025         gboolean sensitive)
5026 {
5027     GList *tmp;
5028     GtkAction *action;
5029 
5030     for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
5031     {
5032         action = gnc_main_window_find_action (tmp->data, action_name);
5033         gtk_action_set_sensitive (action, sensitive);
5034     }
5035 }
5036 
gnc_main_window_get_uimanager(GncMainWindow * window)5037 GtkUIManager *gnc_main_window_get_uimanager (GncMainWindow *window)
5038 {
5039     g_assert(window);
5040     return window->ui_merge;
5041 }
5042 
5043 /** @} */
5044 /** @} */
5045