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