GnuCash  4.901-15-g732a005710
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 <glib/gi18n.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
39 #include "dialog-options.hpp"
40 #include <libguile.h>
41 
42 #include <config.h>
43 
44 
45 #include "gnc-plugin.h"
46 #include "gnc-plugin-manager.h"
47 #include "gnc-main-window.h"
48 
49 #include "dialog-preferences.h"
50 #include "dialog-reset-warnings.h"
51 #include "dialog-transfer.h"
52 #include "dialog-utils.h"
53 #include "engine-helpers.h"
54 #include "file-utils.h"
55 #include "gnc-component-manager.h"
56 #include "dialog-doclink-utils.h"
57 #include "gnc-engine.h"
58 #include "gnc-features.h"
59 #include "gnc-file.h"
60 #include "gnc-filepath-utils.h"
61 #include "gnc-gkeyfile-utils.h"
62 #include "gnc-gnome-utils.h"
63 #include "gnc-gobject-utils.h"
64 #include "gnc-gui-query.h"
65 #include "gnc-gtk-utils.h"
66 #include "gnc-hooks.h"
67 #include "gnc-icons.h"
68 #include "gnc-session.h"
69 #include "gnc-state.h"
70 #include "gnc-ui.h"
71 #include "gnc-ui-util.h"
72 #include <gnc-glib-utils.h>
73 #include "gnc-uri-utils.h"
74 #include "gnc-version.h"
75 #include "gnc-warnings.h"
76 #include "gnc-window.h"
77 #include "gnc-prefs.h"
78 #include "gnc-optiondb.h"
79 #include "gnc-autosave.h"
80 #include "print-session.h"
81 #ifdef MAC_INTEGRATION
82 #include <gtkmacintegration/gtkosxapplication.h>
83 #endif
84 #ifdef HAVE_SYS_STAT_H
85 # define __need_system_sys_stat_h //To block Guile-2.0's evil substitute
86 # include <sys/types.h>
87 # include <sys/stat.h> // for stat(2)
88 #endif
89 
91 enum
92 {
93  PAGE_ADDED,
94  PAGE_CHANGED,
95  MENU_CHANGED,
96  LAST_SIGNAL
97 };
98 
101 #define PLUGIN_PAGE_LABEL "plugin-page"
102 
103 #define PLUGIN_PAGE_CLOSE_BUTTON "close-button"
104 #define PLUGIN_PAGE_TAB_LABEL "label"
105 
106 #define GNC_PREF_SHOW_CLOSE_BUTTON "tab-close-buttons"
107 #define GNC_PREF_TAB_NEXT_RECENT "tab-next-recent"
108 #define GNC_PREF_TAB_POSITION_TOP "tab-position-top"
109 #define GNC_PREF_TAB_POSITION_BOTTOM "tab-position-bottom"
110 #define GNC_PREF_TAB_POSITION_LEFT "tab-position-left"
111 #define GNC_PREF_TAB_POSITION_RIGHT "tab-position-right"
112 #define GNC_PREF_TAB_WIDTH "tab-width"
113 #define GNC_PREF_TAB_COLOR "show-account-color-tabs"
114 #define GNC_PREF_SAVE_CLOSE_EXPIRES "save-on-close-expires"
115 #define GNC_PREF_SAVE_CLOSE_WAIT_TIME "save-on-close-wait-time"
116 #define GNC_PREF_TAB_OPEN_ADJACENT "tab-open-adjacent"
117 
118 #define GNC_MAIN_WINDOW_NAME "GncMainWindow"
119 
120 #define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
121 
133 extern gboolean gnc_book_options_dialog_apply_helper(GncOptionDB * options);
134 
136 constexpr auto gnc_main_window_max_number {10};
137 
138 /* Static Globals *******************************************************/
139 
141 static QofLogModule log_module = GNC_MOD_GUI;
143 static GObjectClass *parent_class = nullptr;
145 static GQuark window_type = 0;
148 static GList *active_windows = nullptr;
151 static guint secs_to_save = 0;
152 #define MSG_AUTO_SAVE _("Changes will be saved automatically in %u seconds")
153 
154 /* Declarations *********************************************************/
155 static void gnc_main_window_class_init (GncMainWindowClass *klass);
156 static void gnc_main_window_init (GncMainWindow *window,
157  void *data);
158 static void gnc_main_window_finalize (GObject *object);
159 static void gnc_main_window_destroy (GtkWidget *widget);
160 
161 static void gnc_main_window_setup_window (GncMainWindow *window);
162 static void gnc_window_main_window_init (GncWindowIface *iface);
163 #ifndef MAC_INTEGRATION
164 static void gnc_main_window_update_all_menu_items (void);
165 #endif
166 
167 /* Callbacks */
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_redirect (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
176 static void gnc_main_window_cmd_page_setup (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
177 static void gnc_main_window_cmd_file_properties (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
178 static void gnc_main_window_cmd_file_close (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
179 static void gnc_main_window_cmd_file_quit (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
180 static void gnc_main_window_cmd_edit_cut (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
181 static void gnc_main_window_cmd_edit_copy (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
182 static void gnc_main_window_cmd_edit_paste (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
183 static void gnc_main_window_cmd_edit_preferences (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
184 static void gnc_main_window_cmd_view_refresh (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
185 static void gnc_main_window_cmd_view_toolbar (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
186 static void gnc_main_window_cmd_view_summary (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
187 static void gnc_main_window_cmd_view_statusbar (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
188 
189 static void gnc_main_window_cmd_view_tab_position (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
190 
191 static void gnc_main_window_cmd_actions_reset_warnings (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
192 static void gnc_main_window_cmd_actions_rename_page (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
193 static void gnc_main_window_cmd_window_new (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
194 static void gnc_main_window_cmd_window_move_page (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
195 #ifndef MAC_INTEGRATION
196 static void gnc_main_window_cmd_window_raise (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
197 #endif
198 static void gnc_main_window_cmd_help_tutorial (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
199 static void gnc_main_window_cmd_help_contents (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
200 static void gnc_main_window_cmd_help_about (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
201 
202 static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
203 static GtkWidget *gnc_main_window_get_statusbar (GncWindow *window_in);
204 static void statusbar_notification_lastmodified (void);
205 static void gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data);
206 static void gnc_main_window_remove_prefs (GncMainWindow *window);
207 
208 #ifdef MAC_INTEGRATION
209 static void gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data);
210 static gboolean gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window);
211 static void gnc_quartz_set_menu (GncMainWindow* window);
212 #endif
213 static void gnc_main_window_init_menu_updaters (GncMainWindow *window);
214 
215 
218 typedef struct GncMainWindowPrivate
219 {
224  GtkWidget *menu_dock;
226  GtkWidget *menubar;
228  GMenuModel *menubar_model;
231  GtkWidget *toolbar;
233  GtkWidget *notebook;
235  gboolean show_color_tabs;
239  GtkWidget *statusbar;
243  GtkWidget *progressbar;
247  GList *usage_order;
253  gint pos[2];
255  gboolean restoring_pages;
256 
257  const gchar *previous_plugin_page_name;
258  const gchar *previous_menu_qualifier;
259 
261  GtkAccelGroup *accel_group;
262 
263  GHashTable *display_item_hash;
264 
266 
267 GNC_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_APPLICATION_WINDOW,
268  G_ADD_PRIVATE (GncMainWindow)
269  G_IMPLEMENT_INTERFACE (GNC_TYPE_WINDOW,
270  gnc_window_main_window_init))
271 
272 #define GNC_MAIN_WINDOW_GET_PRIVATE(o) \
273  ((GncMainWindowPrivate*)gnc_main_window_get_instance_private((GncMainWindow*)o))
274 
277 static guint main_window_signals[LAST_SIGNAL] = { 0 };
278 
279 static void
280 toggle_change_state (GSimpleAction *simple,
281  GVariant *state,
282  gpointer user_data)
283 {
284  g_simple_action_set_state (simple, state);
285 }
286 
287 static void
288 radio_change_state (GSimpleAction *simple,
289  GVariant *state,
290  gpointer user_data)
291 {
292  g_simple_action_set_state (simple, state);
293 }
294 
299 static GActionEntry gnc_menu_actions [] =
300 {
301  { "FilePageSetupAction", gnc_main_window_cmd_page_setup, nullptr, nullptr, nullptr },
302  { "FilePropertiesAction", gnc_main_window_cmd_file_properties, nullptr, nullptr, nullptr },
303  { "FileCloseAction", gnc_main_window_cmd_file_close, nullptr, nullptr, nullptr },
304  { "FilePrintAction", gnc_main_window_cmd_redirect, nullptr, nullptr, nullptr },
305  { "FileQuitAction", gnc_main_window_cmd_file_quit, nullptr, nullptr, nullptr },
306 
307  { "EditCutAction", gnc_main_window_cmd_edit_cut, nullptr, nullptr, nullptr },
308  { "EditCopyAction", gnc_main_window_cmd_edit_copy, nullptr, nullptr, nullptr },
309  { "EditPasteAction", gnc_main_window_cmd_edit_paste, nullptr, nullptr, nullptr },
310  { "EditPreferencesAction", gnc_main_window_cmd_edit_preferences, nullptr, nullptr, nullptr },
311 
312  { "ActionsForgetWarningsAction", gnc_main_window_cmd_actions_reset_warnings, nullptr, nullptr, nullptr },
313  { "ActionsRenamePageAction", gnc_main_window_cmd_actions_rename_page, nullptr, nullptr, nullptr },
314 
315  { "TransactionAction", nullptr, nullptr, nullptr, nullptr },
316 
317  { "ViewSortByAction", nullptr, nullptr, nullptr, nullptr },
318  { "ViewFilterByAction", nullptr, nullptr, nullptr, nullptr },
319  { "ViewRefreshAction", gnc_main_window_cmd_view_refresh, nullptr, nullptr, nullptr },
320  { "ViewToolbarAction", gnc_main_window_cmd_view_toolbar, nullptr, "true", toggle_change_state },
321  { "ViewSummaryAction", gnc_main_window_cmd_view_summary, nullptr, "true", toggle_change_state },
322  { "ViewStatusbarAction", gnc_main_window_cmd_view_statusbar, nullptr, "true", toggle_change_state },
323  { "ViewTabPositionAction", gnc_main_window_cmd_view_tab_position, "i", "@i 0", radio_change_state },
324 
325  { "ScheduledAction", nullptr, nullptr, nullptr, nullptr },
326 
327  { "ExtensionsAction", nullptr, nullptr, nullptr, nullptr },
328 
329  { "WindowNewAction", gnc_main_window_cmd_window_new, nullptr, nullptr, nullptr },
330  { "WindowMovePageAction", gnc_main_window_cmd_window_move_page, nullptr, nullptr, nullptr },
331 #ifndef MAC_INTEGRATION
332  { "WindowAction", gnc_main_window_cmd_window_raise, "i", "@i 0", radio_change_state },
333 #endif
334  { "HelpTutorialAction", gnc_main_window_cmd_help_tutorial, nullptr, nullptr, nullptr },
335  { "HelpContentsAction", gnc_main_window_cmd_help_contents, nullptr, nullptr, nullptr },
336  { "HelpAboutAction", gnc_main_window_cmd_help_about, nullptr, nullptr, nullptr },
337 };
339 static guint gnc_menu_n_actions = G_N_ELEMENTS(gnc_menu_actions);
340 
345 static const gchar *always_insensitive_actions[] =
346 {
347  "FilePrintAction",
348  nullptr
349 };
350 
351 
355 static const gchar *initially_insensitive_actions[] =
356 {
357  "FileCloseAction",
358  nullptr
359 };
360 
361 
366 static const gchar *always_hidden_actions[] =
367 {
368  "ViewSortByAction",
369  "ViewFilterByAction",
370  nullptr
371 };
372 
373 
376 static const gchar *immutable_page_actions[] =
377 {
378  "FileCloseAction",
379  nullptr
380 };
381 
382 
385 static const gchar *multiple_page_actions[] =
386 {
387  "WindowMovePageAction",
388  nullptr
389 };
390 
391 
392 /************************************************************
393  * *
394  ************************************************************/
395 #define WINDOW_COUNT "WindowCount"
396 #define WINDOW_STRING "Window %d"
397 #define WINDOW_GEOMETRY "WindowGeometry"
398 #define WINDOW_POSITION "WindowPosition"
399 #define WINDOW_MAXIMIZED "WindowMaximized"
400 #define TOOLBAR_VISIBLE "ToolbarVisible"
401 #define STATUSBAR_VISIBLE "StatusbarVisible"
402 #define SUMMARYBAR_VISIBLE "SummarybarVisible"
403 #define WINDOW_FIRSTPAGE "FirstPage"
404 #define WINDOW_PAGECOUNT "PageCount"
405 #define WINDOW_PAGEORDER "PageOrder"
406 #define PAGE_TYPE "PageType"
407 #define PAGE_NAME "PageName"
408 #define PAGE_STRING "Page %d"
409 
410 typedef struct
411 {
412  GKeyFile *key_file;
413  const gchar *group_name;
414  gint window_num;
415  gint page_num;
416  gint page_offset;
418 
419 
420 gboolean
422 {
423  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
424  return priv->restoring_pages;
425 }
426 
427 
428 /* Iterator function to walk all pages in all windows, calling the
429  * specified function for each page. */
430 void
431 gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data)
432 {
433  ENTER(" ");
434  for (auto w = active_windows; w; w = g_list_next(w))
435  {
436  auto window{static_cast<GncMainWindow*>(w->data)};
437  auto priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
438  for (auto p = priv->installed_pages; p; p = g_list_next(p))
439  {
440  auto page{static_cast<GncPluginPage*>(p->data)};
441  fn(page, user_data);
442  }
443  }
444  LEAVE(" ");
445 }
446 
447 
459 static void
460 gnc_main_window_restore_page (GncMainWindow *window,
461  GncMainWindowSaveData *data)
462 {
463  GncMainWindowPrivate *priv;
464  GncPluginPage *page;
465  gchar *page_group, *page_type = nullptr, *name = nullptr;
466  const gchar *class_type;
467  GError *error = nullptr;
468 
469  ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
470  window, data, data->key_file, data->window_num, data->page_offset,
471  data->page_num);
472 
473  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
474  page_group = g_strdup_printf(PAGE_STRING,
475  data->page_offset + data->page_num);
476  page_type = g_key_file_get_string(data->key_file, page_group,
477  PAGE_TYPE, &error);
478  if (error)
479  {
480  g_warning("error reading group %s key %s: %s",
481  page_group, PAGE_TYPE, error->message);
482  goto cleanup;
483  }
484 
485  /* See if the page already exists. */
486  page = static_cast<GncPluginPage*>(g_list_nth_data(priv->installed_pages,
487  data->page_num));
488  if (page)
489  {
490  class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
491  if (strcmp(page_type, class_type) != 0)
492  {
493  g_warning("error: page types don't match: state %s, existing page %s",
494  page_type, class_type);
495  goto cleanup;
496  }
497  }
498  else
499  {
500  /* create and install the page */
501  page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
502  data->key_file, page_group);
503  if (page)
504  {
505  /* Does the page still need to be installed into the window? */
506  if (page->window == nullptr)
507  {
509  gnc_main_window_open_page(window, page);
510  }
511 
512  /* Restore the page name */
513  name = g_key_file_get_string(data->key_file, page_group,
514  PAGE_NAME, &error);
515  if (error)
516  {
517  g_warning("error reading group %s key %s: %s",
518  page_group, PAGE_NAME, error->message);
519  /* Fall through and still show the page. */
520  }
521  else
522  {
523  DEBUG("updating page name for %p to %s.", page, name);
524  main_window_update_page_name(page, name);
525  g_free(name);
526  }
527  }
528  }
529 
530  LEAVE("ok");
531 cleanup:
532  if (error)
533  g_error_free(error);
534  if (page_type)
535  g_free(page_type);
536  g_free(page_group);
537 }
538 
539 
548 static void
549 gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
550 {
551  GncMainWindowPrivate *priv;
552  GAction *action;
553  gint *pos, *geom, *order;
554  gsize length;
555  gboolean max;
556  gchar *window_group;
557  gsize page_start, page_count, i;
558  GError *error = nullptr;
559 
560  /* Setup */
561  ENTER("window %p, data %p (key file %p, window %d)",
562  window, data, data->key_file, data->window_num);
563  window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);
564 
565  /* Deal with the uncommon case that the state file defines a window
566  * but no pages. An example to get in such a situation can be found
567  * here: https://bugs.gnucash.org/show_bug.cgi?id=436479#c3
568  * If this happens on the first window, we will open an account hierarchy
569  * to avoid confusing the user by presenting a completely empty window.
570  * If it happens on a later window, we'll just skip restoring that window.
571  */
572  if (!g_key_file_has_group (data->key_file, window_group) ||
573  !g_key_file_has_key (data->key_file, window_group, WINDOW_PAGECOUNT, &error))
574  {
575  if (window)
576  {
578  PINFO ("saved state had an empty first main window\n"
579  "an account hierarchy page was added automatically to avoid confusion");
580  }
581  else
582  PINFO ("saved state had an empty main window, skipping restore");
583 
584  goto cleanup;
585  }
586 
587 
588  /* Get this window's notebook info */
589  page_count = g_key_file_get_integer(data->key_file,
590  window_group, WINDOW_PAGECOUNT, &error);
591  if (error)
592  {
593  g_warning("error reading group %s key %s: %s",
594  window_group, WINDOW_PAGECOUNT, error->message);
595  goto cleanup;
596  }
597  if (page_count == 0)
598  {
599  /* Should never happen, but has during alpha testing. Having this
600  * check doesn't hurt anything. */
601  goto cleanup;
602  }
603  page_start = g_key_file_get_integer(data->key_file,
604  window_group, WINDOW_FIRSTPAGE, &error);
605  if (error)
606  {
607  g_warning("error reading group %s key %s: %s",
608  window_group, WINDOW_FIRSTPAGE, error->message);
609  goto cleanup;
610  }
611 
612  /* Build a window if we don't already have one */
613  if (window == nullptr)
614  {
615  DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
616  DEBUG("active_windows %p.", active_windows);
617  if (active_windows)
618  DEBUG("first window %p.", active_windows->data);
619  window = gnc_main_window_new();
620  }
621 
622  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
623 
624  /* Get the window coordinates, etc. */
625  geom = g_key_file_get_integer_list(data->key_file, window_group,
626  WINDOW_GEOMETRY, &length, &error);
627  if (error)
628  {
629  g_warning("error reading group %s key %s: %s",
630  window_group, WINDOW_GEOMETRY, error->message);
631  g_error_free(error);
632  error = nullptr;
633  }
634  else if (length != 2)
635  {
636  g_warning("invalid number of values for group %s key %s",
637  window_group, WINDOW_GEOMETRY);
638  }
639  else
640  {
641  gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
642  DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
643  }
644  /* keep the geometry for a test whether the windows position
645  is offscreen */
646 
647  pos = g_key_file_get_integer_list(data->key_file, window_group,
648  WINDOW_POSITION, &length, &error);
649  if (error)
650  {
651  g_warning("error reading group %s key %s: %s",
652  window_group, WINDOW_POSITION, error->message);
653  g_error_free(error);
654  error = nullptr;
655  }
656  else if (length != 2)
657  {
658  g_warning("invalid number of values for group %s key %s",
659  window_group, WINDOW_POSITION);
660  }
661  /* Prevent restoring coordinates if this would move the window off-screen */
662  else if ((pos[0] + (geom ? geom[0] : 0) < 0) ||
663  (pos[0] > gdk_screen_width()) ||
664  (pos[1] + (geom ? geom[1] : 0) < 0) ||
665  (pos[1] > gdk_screen_height()))
666  {
667  DEBUG("position %dx%d, size%dx%d is offscreen; will not move",
668  pos[0], pos[1], geom ? geom[0] : 0, geom ? geom[1] : 0);
669  }
670  else
671  {
672  gtk_window_move(GTK_WINDOW(window), pos[0], pos[1]);
673  priv->pos[0] = pos[0];
674  priv->pos[1] = pos[1];
675  DEBUG("window (%p) position %dx%d", window, pos[0], pos[1]);
676  }
677  if (geom)
678  {
679  g_free(geom);
680  }
681  if (pos)
682  {
683  g_free(pos);
684  }
685 
686  max = g_key_file_get_boolean(data->key_file, window_group,
687  WINDOW_MAXIMIZED, &error);
688  if (error)
689  {
690  g_warning("error reading group %s key %s: %s",
691  window_group, WINDOW_MAXIMIZED, error->message);
692  g_error_free(error);
693  error = nullptr;
694  }
695  else if (max)
696  {
697  gtk_window_maximize(GTK_WINDOW(window));
698  }
699 
700  // need to add the accelerator keys
701  gnc_add_accelerator_keys_for_menu (GTK_WIDGET(priv->menubar), priv->accel_group);
702 
703  /* Common view menu items */
704  action = gnc_main_window_find_action (window, "ViewToolbarAction");
705  if (action)
706  {
707  GVariant *state = g_action_get_state (G_ACTION(action));
708  gboolean visible = g_variant_get_boolean (state);
709  gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
710  TOOLBAR_VISIBLE, &error);
711 
712  if (error)
713  {
714  g_warning ("error reading group %s key %s: %s",
715  window_group, TOOLBAR_VISIBLE, error->message);
716  g_error_free (error);
717  error = nullptr;
718  }
719  else if (visible != desired_visibility)
720  {
721  g_action_activate (action, nullptr);
722  }
723  g_variant_unref (state);
724  }
725 
726  action = gnc_main_window_find_action (window, "ViewSummaryAction");
727  if (action)
728  {
729  GVariant *state = g_action_get_state (G_ACTION(action));
730  gboolean visible = g_variant_get_boolean (state);
731  gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
732  SUMMARYBAR_VISIBLE, &error);
733 
734  if (error)
735  {
736  g_warning ("error reading group %s key %s: %s",
737  window_group, SUMMARYBAR_VISIBLE, error->message);
738  g_error_free (error);
739  error = nullptr;
740  }
741  else if (visible != desired_visibility)
742  {
743  g_action_activate (action, nullptr);
744  }
745  g_variant_unref (state);
746  }
747 
748  action = gnc_main_window_find_action (window, "ViewStatusbarAction");
749  if (action)
750  {
751  GVariant *state = g_action_get_state (G_ACTION(action));
752  gboolean visible = g_variant_get_boolean (state);
753  gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
754  STATUSBAR_VISIBLE, &error);
755 
756  if (error)
757  {
758  g_warning ("error reading group %s key %s: %s",
759  window_group, STATUSBAR_VISIBLE, error->message);
760  g_error_free (error);
761  error = nullptr;
762  }
763  else if (visible != desired_visibility)
764  {
765  g_action_activate (action, nullptr);
766  }
767  g_variant_unref (state);
768  }
769  priv->restoring_pages = TRUE;
770  /* Now populate the window with pages. */
771  for (i = 0; i < page_count; i++)
772  {
773  data->page_offset = page_start;
774  data->page_num = i;
775  gnc_main_window_restore_page(window, data);
776 
777  /* give the page a chance to display */
778  while (gtk_events_pending ())
779  gtk_main_iteration ();
780  }
781  priv->restoring_pages = FALSE;
782  /* Restore page ordering within the notebook. Use +1 notation so the
783  * numbers in the page order match the page sections, at least for
784  * the one window case. */
785  order = g_key_file_get_integer_list (data->key_file, window_group,
786  WINDOW_PAGEORDER, &length, &error);
787  if (error)
788  {
789  g_warning("error reading group %s key %s: %s",
790  window_group, WINDOW_PAGEORDER, error->message);
791  g_error_free(error);
792  error = nullptr;
793  }
794  else if (length != page_count)
795  {
796  g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %" G_GSIZE_FORMAT,
797  window_group, WINDOW_PAGEORDER, length, page_count);
798  }
799  else
800  {
801  /* Dump any list that might exist */
802  g_list_free(priv->usage_order);
803  priv->usage_order = nullptr;
804  /* Now rebuild the list from the key file. */
805  for (i = 0; i < length; i++)
806  {
807  gpointer page = g_list_nth_data(priv->installed_pages, order[i] - 1);
808  if (page)
809  {
810  priv->usage_order = g_list_append(priv->usage_order, page);
811  }
812  }
813  gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
814  order[0] - 1);
815 
816  g_signal_emit_by_name (window, "page_changed",
817  g_list_nth_data (priv->usage_order, 0));
818  }
819  if (order)
820  {
821  g_free(order);
822  }
823 
824  LEAVE("window %p", window);
825 cleanup:
826  if (error)
827  g_error_free(error);
828  g_free(window_group);
829  if (window)
830  gtk_widget_show (GTK_WIDGET(window));
831 }
832 
833 void
834 gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
835 {
836  gint i, window_count;
837  GError *error = nullptr;
839 
840  /* We use the same struct for reading and for writing, so we cast
841  away the const. */
842  data.key_file = (GKeyFile *) keyfile;
843  window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
844  WINDOW_COUNT, &error);
845  if (error)
846  {
847  g_warning("error reading group %s key %s: %s",
848  STATE_FILE_TOP, WINDOW_COUNT, error->message);
849  g_error_free(error);
850  LEAVE("can't read count");
851  return;
852  }
853 
854  /* Restore all state information on the open windows. Window
855  numbers in state file are 1-based. GList indices are 0-based. */
856  gnc_set_busy_cursor (nullptr, TRUE);
857  for (i = 0; i < window_count; i++)
858  {
859  data.window_num = i;
860  auto window{static_cast<GncMainWindow*>(g_list_nth_data(active_windows,
861  i))};
862  gnc_main_window_restore_window(window, &data);
863  }
864  gnc_unset_busy_cursor (nullptr);
865 
866  statusbar_notification_lastmodified();
867 }
868 
869 void
871 {
872  GAction *action;
873 
874  /* The default state should be to have an Account Tree page open
875  * in the window. */
876  DEBUG("no saved state file");
877  if (!window)
878  window = static_cast<GncMainWindow*>(g_list_nth_data(active_windows, 0));
879  gtk_widget_show (GTK_WIDGET(window));
880  action = gnc_main_window_find_action_in_group (window,
881  "gnc-plugin-account-tree-actions",
882  "ViewAccountTreeAction");
883  g_action_activate (action, nullptr);
884 }
885 
895 static void
896 gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
897 {
898  gchar *page_group;
899  const gchar *plugin_name, *page_name;
900 
901  ENTER("page %p, data %p (key file %p, window %d, page %d)",
902  page, data, data->key_file, data->window_num, data->page_num);
903  plugin_name = gnc_plugin_page_get_plugin_name(page);
904  page_name = gnc_plugin_page_get_page_name(page);
905  if (!plugin_name || !page_name)
906  {
907  LEAVE("not saving invalid page");
908  return;
909  }
910  page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
911  g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
912  g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
913 
914  gnc_plugin_page_save_page(page, data->key_file, page_group);
915  g_free(page_group);
916  LEAVE(" ");
917 }
918 
919 
928 static void
929 gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
930 {
931  GncMainWindowPrivate *priv;
932  GAction *action;
933  gint i, num_pages, coords[4], *order;
934  gboolean maximized, minimized, visible = true;
935  gchar *window_group;
936 
937  /* Setup */
938  ENTER("window %p, data %p (key file %p, window %d)",
939  window, data, data->key_file, data->window_num);
940  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
941 
942  /* Check for bogus window structures. */
943  num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
944  if (0 == num_pages)
945  {
946  LEAVE("empty window %p", window);
947  return;
948  }
949 
950  /* Save this window's notebook info */
951  window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
952  g_key_file_set_integer(data->key_file, window_group,
953  WINDOW_PAGECOUNT, num_pages);
954  g_key_file_set_integer(data->key_file, window_group,
955  WINDOW_FIRSTPAGE, data->page_num);
956 
957  /* Save page ordering within the notebook. Use +1 notation so the
958  * numbers in the page order match the page sections, at least for
959  * the one window case. */
960  order = static_cast<int*>(g_malloc(sizeof(gint) * num_pages));
961  for (i = 0; i < num_pages; i++)
962  {
963  gpointer page = g_list_nth_data(priv->usage_order, i);
964  order[i] = g_list_index(priv->installed_pages, page) + 1;
965  }
966  g_key_file_set_integer_list(data->key_file, window_group,
967  WINDOW_PAGEORDER, order, num_pages);
968  g_free(order);
969 
970  /* Save the window coordinates, etc. */
971  gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
972  gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
973  maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
974  & GDK_WINDOW_STATE_MAXIMIZED) != 0;
975  minimized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
976  & GDK_WINDOW_STATE_ICONIFIED) != 0;
977 
978  if (minimized)
979  {
980  gint *pos = priv->pos;
981  g_key_file_set_integer_list(data->key_file, window_group,
982  WINDOW_POSITION, &pos[0], 2);
983  DEBUG("window minimized (%p) position %dx%d", window, pos[0], pos[1]);
984  }
985  else
986  g_key_file_set_integer_list(data->key_file, window_group,
987  WINDOW_POSITION, &coords[0], 2);
988  g_key_file_set_integer_list(data->key_file, window_group,
989  WINDOW_GEOMETRY, &coords[2], 2);
990  g_key_file_set_boolean(data->key_file, window_group,
991  WINDOW_MAXIMIZED, maximized);
992  DEBUG("window (%p) position %dx%d, size %dx%d, %s", window, coords[0], coords[1],
993  coords[2], coords[3],
994  maximized ? "maximized" : "not maximized");
995 
996  /* Common view menu items */
997  action = gnc_main_window_find_action (window, "ViewToolbarAction");
998  if (action)
999  {
1000  GVariant *state = g_action_get_state (G_ACTION(action));
1001  visible = g_variant_get_boolean (state);
1002  g_variant_unref (state);
1003  }
1004  g_key_file_set_boolean (data->key_file, window_group,
1005  TOOLBAR_VISIBLE, visible);
1006  action = gnc_main_window_find_action (window, "ViewSummaryAction");
1007  if (action)
1008  {
1009  GVariant *state = g_action_get_state (G_ACTION(action));
1010  visible = g_variant_get_boolean (state);
1011  g_variant_unref (state);
1012  }
1013  g_key_file_set_boolean (data->key_file, window_group,
1014  SUMMARYBAR_VISIBLE, visible);
1015  action = gnc_main_window_find_action (window, "ViewStatusbarAction");
1016  if (action)
1017  {
1018  GVariant *state = g_action_get_state (G_ACTION(action));
1019  visible = g_variant_get_boolean (state);
1020  g_variant_unref (state);
1021  }
1022  g_key_file_set_boolean (data->key_file, window_group,
1023  STATUSBAR_VISIBLE, visible);
1024 
1025  /* Save individual pages in this window */
1026  g_list_foreach (priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
1027 
1028  g_free(window_group);
1029  LEAVE("window %p", window);
1030 }
1031 
1032 void
1034 {
1035  GncMainWindowSaveData data;
1036 
1037  /* Set up the iterator data structures */
1038  data.key_file = keyfile;
1039  data.window_num = 1;
1040  data.page_num = 1;
1041 
1042  g_key_file_set_integer(data.key_file,
1043  STATE_FILE_TOP, WINDOW_COUNT,
1044  g_list_length(active_windows));
1045  /* Dump all state information on the open windows */
1046  g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
1047 }
1048 
1049 
1050 gboolean
1052 {
1053  GncMainWindowPrivate *priv;
1054  GList *item;
1055 
1056  g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
1057 
1058  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1059  for (item = priv->installed_pages; item; item = g_list_next(item))
1060  {
1061  if (!gnc_plugin_page_finish_pending(static_cast<GncPluginPage*>(item->data)))
1062  {
1063  return FALSE;
1064  }
1065  }
1066  return TRUE;
1067 }
1068 
1069 
1070 gboolean
1072 {
1073  const GList *windows, *item;
1074 
1075  windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
1076  for (item = windows; item; item = g_list_next(item))
1077  {
1078  if (!gnc_main_window_finish_pending(static_cast<GncMainWindow*>(item->data)))
1079  {
1080  return FALSE;
1081  }
1082  }
1083  if (gnc_gui_refresh_suspended ())
1084  {
1085  gnc_warning_dialog (nullptr, "%s", "An operation is still running, wait for it to complete before quitting.");
1086  return FALSE;
1087  }
1088  return TRUE;
1089 }
1090 
1091 
1102 static gboolean
1103 gnc_main_window_page_exists (GncPluginPage *page)
1104 {
1105  GncMainWindowPrivate *priv;
1106  GList *walker;
1107 
1108  for (walker = active_windows; walker; walker = g_list_next(walker))
1109  {
1110  auto window{static_cast<GncMainWindow*>(walker->data)};
1111  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1112  if (g_list_find(priv->installed_pages, page))
1113  {
1114  return TRUE;
1115  }
1116  }
1117  return FALSE;
1118 }
1119 
1120 static gboolean auto_save_countdown (GtkWidget *dialog)
1121 {
1122  GtkWidget *label;
1123  gchar *timeoutstr = nullptr;
1124 
1125  /* Stop count down if user closed the dialog since the last time we were called */
1126  if (!GTK_IS_DIALOG (dialog))
1127  return FALSE; /* remove timer */
1128 
1129  /* Stop count down if count down text can't be updated */
1130  label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
1131  if (!GTK_IS_LABEL (label))
1132  return FALSE; /* remove timer */
1133 
1134  /* Protect against rolling over to MAXUINT */
1135  if (secs_to_save)
1136  --secs_to_save;
1137  DEBUG ("Counting down: %d seconds", secs_to_save);
1138 
1139  timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1140  gtk_label_set_text (GTK_LABEL (label), timeoutstr);
1141  g_free (timeoutstr);
1142 
1143  /* Count down reached 0. Save and close dialog */
1144  if (!secs_to_save)
1145  {
1146  gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1147  return FALSE; /* remove timer */
1148  }
1149 
1150  /* Run another cycle */
1151  return TRUE;
1152 }
1153 
1154 
1164 static gboolean
1165 gnc_main_window_prompt_for_save (GtkWidget *window)
1166 {
1167  QofSession *session;
1168  QofBook *book;
1169  GtkWidget *dialog, *msg_area, *label;
1170  gint response;
1171  const gchar *filename, *tmp;
1172  const gchar *title = _("Save changes to file %s before closing?");
1173  /* This should be the same message as in gnc-file.c */
1174  const gchar *message_hours =
1175  _("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
1176  const gchar *message_days =
1177  _("If you don't save, changes from the past %d days and %d hours will be discarded.");
1178  time64 oldest_change;
1179  gint minutes, hours, days;
1180  guint timer_source = 0;
1181  if (!gnc_current_session_exist())
1182  return FALSE;
1183  session = gnc_get_current_session();
1184  book = qof_session_get_book(session);
1185  if (!qof_book_session_not_saved(book))
1186  return FALSE;
1187  filename = qof_session_get_url(session);
1188  if (!strlen (filename))
1189  filename = _("<unknown>");
1190  if ((tmp = strrchr(filename, '/')) != nullptr)
1191  filename = tmp + 1;
1192 
1193  /* Remove any pending auto-save timeouts */
1194  gnc_autosave_remove_timer(book);
1195 
1196  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1197  GTK_DIALOG_MODAL,
1198  GTK_MESSAGE_WARNING,
1199  GTK_BUTTONS_NONE,
1200  title,
1201  filename);
1202  oldest_change = qof_book_get_session_dirty_time(book);
1203  minutes = (gnc_time (nullptr) - oldest_change) / 60 + 1;
1204  hours = minutes / 60;
1205  minutes = minutes % 60;
1206  days = hours / 24;
1207  hours = hours % 24;
1208  if (days > 0)
1209  {
1210  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1211  message_days, days, hours);
1212  }
1213  else if (hours > 0)
1214  {
1215  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1216  message_hours, hours, minutes);
1217  }
1218  else
1219  {
1220  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1221  ngettext("If you don't save, changes from the past %d minute will be discarded.",
1222  "If you don't save, changes from the past %d minutes will be discarded.",
1223  minutes), minutes);
1224  }
1225  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
1226  _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
1227  _("_Cancel"), GTK_RESPONSE_CANCEL,
1228  _("_Save"), GTK_RESPONSE_APPLY,
1229  nullptr);
1230  gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1231 
1232  /* If requested by the user, add a timeout to the question to save automatically
1233  * if the user doesn't answer after a chosen number of seconds.
1234  */
1235  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
1236  {
1237  gchar *timeoutstr = nullptr;
1238 
1239  secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
1240  timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1241  label = GTK_WIDGET(gtk_label_new (timeoutstr));
1242  g_free (timeoutstr);
1243  gtk_widget_show (label);
1244 
1245  msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
1246  gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
1247  g_object_set (G_OBJECT (label), "xalign", 0.0, nullptr);
1248 
1249  g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
1250  timer_source = g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
1251  }
1252 
1253  response = gtk_dialog_run (GTK_DIALOG (dialog));
1254  if (timer_source)
1255  g_source_remove (timer_source);
1256  gtk_widget_destroy(dialog);
1257 
1258  switch (response)
1259  {
1260  case GTK_RESPONSE_APPLY:
1261  gnc_file_save (GTK_WINDOW (window));
1262  return FALSE;
1263 
1264  case GTK_RESPONSE_CLOSE:
1266  return FALSE;
1267 
1268  default:
1269  return TRUE;
1270  }
1271 }
1272 
1273 
1274 static void
1275 gnc_main_window_add_plugin (gpointer plugin,
1276  gpointer window)
1277 {
1278  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1279  g_return_if_fail (GNC_IS_PLUGIN (plugin));
1280 
1281  ENTER(" ");
1282  gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
1283  GNC_MAIN_WINDOW (window),
1284  window_type);
1285  LEAVE(" ");
1286 }
1287 
1288 static void
1289 gnc_main_window_remove_plugin (gpointer plugin,
1290  gpointer window)
1291 {
1292  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1293  g_return_if_fail (GNC_IS_PLUGIN (plugin));
1294 
1295  ENTER(" ");
1296  gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
1297  GNC_MAIN_WINDOW (window),
1298  window_type);
1299  LEAVE(" ");
1300 }
1301 
1302 
1303 static gboolean
1304 gnc_main_window_timed_quit (gpointer dummy)
1305 {
1306  if (gnc_file_save_in_progress())
1307  return TRUE;
1308 
1309  gnc_shutdown (0);
1310  return FALSE;
1311 }
1312 
1313 static gboolean
1314 gnc_main_window_quit(GncMainWindow *window)
1315 {
1316  QofSession *session;
1317  gboolean needs_save, do_shutdown = TRUE;
1318  if (gnc_current_session_exist())
1319  {
1320  session = gnc_get_current_session();
1321  needs_save =
1323  !gnc_file_save_in_progress();
1324  do_shutdown = !needs_save ||
1325  (needs_save &&
1326  !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
1327  }
1328  if (do_shutdown)
1329  {
1330  GList *w, *next;
1331 
1332  /* This is not a typical list iteration. There is a possibility
1333  * that the window may be removed from the active_windows list so
1334  * we have to cache the 'next' pointer before executing any code
1335  * in the loop. */
1336  for (w = active_windows; w; w = next)
1337  {
1338  GncMainWindowPrivate *priv;
1339  GncMainWindow *window = static_cast<GncMainWindow*>(w->data);
1340 
1341  next = g_list_next (w);
1342 
1343  window->window_quitting = TRUE; //set window_quitting on all windows
1344 
1345  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1346 
1347  // if there are no pages destroy window
1348  if (priv->installed_pages == NULL)
1349  gtk_widget_destroy (GTK_WIDGET(window));
1350  }
1351  /* remove the preference callbacks from the main window */
1352  gnc_main_window_remove_prefs (window);
1353  g_timeout_add(250, gnc_main_window_timed_quit, nullptr);
1354  return TRUE;
1355  }
1356  return FALSE;
1357 }
1358 
1359 static gboolean
1360 gnc_main_window_delete_event (GtkWidget *window,
1361  GdkEvent *event,
1362  gpointer user_data)
1363 {
1364  static gboolean already_dead = FALSE;
1365 
1366  if (already_dead)
1367  return TRUE;
1368 
1369  if (gnc_list_length_cmp (active_windows, 1) > 0)
1370  {
1371  gint response;
1372  GtkWidget *dialog;
1373  gchar *message = _("This window is closing and will not be restored.");
1374 
1375  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
1376  GTK_DIALOG_DESTROY_WITH_PARENT,
1377  GTK_MESSAGE_QUESTION,
1378  GTK_BUTTONS_NONE,
1379  "%s", _("Close Window?"));
1380  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
1381  "%s", message);
1382 
1383  gtk_dialog_add_buttons (GTK_DIALOG(dialog),
1384  _("_Cancel"), GTK_RESPONSE_CANCEL,
1385  _("_OK"), GTK_RESPONSE_YES,
1386  (gchar *)NULL);
1387  gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1388  response = gnc_dialog_run (GTK_DIALOG(dialog), GNC_PREF_WARN_CLOSING_WINDOW_QUESTION);
1389  gtk_widget_destroy (dialog);
1390 
1391  if (response == GTK_RESPONSE_CANCEL)
1392  return TRUE;
1393  }
1394 
1395  if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
1396  {
1397  /* Don't close the window. */
1398  return TRUE;
1399  }
1400 
1401  if (gnc_list_length_cmp (active_windows, 1) > 0)
1402  return FALSE;
1403 
1404  already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
1405  return TRUE;
1406 }
1407 
1408 
1428 static void
1429 gnc_main_window_event_handler (QofInstance *entity, QofEventId event_type,
1430  gpointer user_data, gpointer event_data)
1431 {
1432  GncMainWindow *window;
1433  GncMainWindowPrivate *priv;
1434  GncPluginPage *page;
1435  GList *item, *next;
1436 
1437  /* hard failures */
1438  g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
1439 
1440  /* soft failures */
1441  if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
1442  return;
1443  if (event_type != QOF_EVENT_DESTROY)
1444  return;
1445 
1446  ENTER("entity %p, event %d, window %p, event data %p",
1447  entity, event_type, user_data, event_data);
1448  window = GNC_MAIN_WINDOW(user_data);
1449  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1450 
1451  /* This is not a typical list iteration. We're removing while
1452  * we iterate, so we have to cache the 'next' pointer before
1453  * executing any code in the loop. */
1454  for (item = priv->installed_pages; item; item = next)
1455  {
1456  next = g_list_next(item);
1457  page = GNC_PLUGIN_PAGE(item->data);
1458  if (gnc_plugin_page_has_book (page, (QofBook *)entity))
1460  }
1461 
1462  if (GTK_IS_WIDGET(window) && window->window_quitting)
1463  gtk_widget_destroy (GTK_WIDGET(window));
1464 
1465  LEAVE(" ");
1466 }
1467 
1468 
1485 static gchar *
1486 gnc_main_window_generate_title (GncMainWindow *window)
1487 {
1488  GncMainWindowPrivate *priv;
1489  GncPluginPage *page;
1490  QofBook *book;
1491  gboolean immutable;
1492  gchar *filename = nullptr;
1493  const gchar *uri = nullptr;
1494  const gchar *dirty = "";
1495  const gchar *readonly_text = nullptr;
1496  gchar *readonly;
1497  gchar *title;
1498 
1499  if (gnc_current_session_exist())
1500  {
1501  uri = qof_session_get_url (gnc_get_current_session ());
1502  book = gnc_get_current_book();
1503  if (qof_book_session_not_saved (book))
1504  dirty = "*";
1505  if (qof_book_is_readonly(book))
1506  {
1507  /* Translators: This string is shown in the window title if this
1508  document is, well, read-only. */
1509  readonly_text = _("(read-only)");
1510  }
1511  }
1512  readonly = (readonly_text != nullptr)
1513  ? g_strdup_printf(" %s", readonly_text)
1514  : g_strdup("");
1515 
1516  if (!uri || g_strcmp0 (uri, "") == 0)
1517  filename = g_strdup(_("Unsaved Book"));
1518  else
1519  {
1520  if (gnc_uri_targets_local_fs (uri))
1521  {
1522  /* The filename is a true file.
1523  The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
1524  gchar *path = gnc_uri_get_path ( uri );
1525  filename = g_path_get_basename ( path );
1526  g_free ( path );
1527  }
1528  else
1529  {
1530  /* The filename is composed of database connection parameters.
1531  For this we will show access_method://username@database[:port] */
1532  filename = gnc_uri_normalize_uri (uri, FALSE);
1533  }
1534  }
1535 
1536  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1537  page = priv->current_page;
1538  if (page)
1539  {
1540  /* The Gnome HIG 2.0 recommends the application name not be used. (p16)
1541  but several developers prefer to use it anyway. */
1542  title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
1544  }
1545  else
1546  {
1547  title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
1548  }
1549  /* Update the menus based upon whether this is an "immutable" page. */
1550  immutable = page &&
1551  g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
1552  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
1553  immutable_page_actions,
1554  !immutable);
1555  /* Trigger sensitivity updtates of other actions such as Save/Revert */
1556  g_signal_emit_by_name (window, "page_changed", page);
1557  g_free( filename );
1558  g_free(readonly);
1559 
1560  return title;
1561 }
1562 
1563 
1573 static void
1574 gnc_main_window_update_title (GncMainWindow *window)
1575 {
1576  gchar *title;
1577 
1578  title = gnc_main_window_generate_title(window);
1579  gtk_window_set_title(GTK_WINDOW(window), title);
1580  g_free(title);
1581 }
1582 
1583 static void
1584 gnc_main_window_update_all_titles (void)
1585 {
1586  g_list_foreach(active_windows,
1587  (GFunc)gnc_main_window_update_title,
1588  nullptr);
1589 }
1590 
1591 static void
1592 gnc_main_window_book_dirty_cb (QofBook *book,
1593  gboolean dirty,
1594  gpointer user_data)
1595 {
1596  gnc_main_window_update_all_titles();
1597 
1598  /* Auto-save feature */
1599  gnc_autosave_dirty_handler(book, dirty);
1600 }
1601 
1602 static void
1603 gnc_main_window_attach_to_book (QofSession *session)
1604 {
1605  QofBook *book;
1606 
1607  g_return_if_fail(session);
1608 
1609  book = qof_session_get_book(session);
1610  qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, nullptr);
1611  gnc_main_window_update_all_titles();
1612 #ifndef MAC_INTEGRATION
1613  gnc_main_window_update_all_menu_items();
1614 #endif
1615 }
1616 
1617 static guint gnc_statusbar_notification_messageid = 0;
1618 //#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
1619 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1620 /* Removes the statusbar notification again that has been pushed to the
1621  * statusbar by generate_statusbar_lastmodified_message. */
1622 static gboolean statusbar_notification_off(gpointer user_data_unused)
1623 {
1624  GncMainWindow *mainwindow = GNC_MAIN_WINDOW (gnc_ui_get_main_window (nullptr));
1625  //g_warning("statusbar_notification_off\n");
1626  if (gnc_statusbar_notification_messageid == 0)
1627  return FALSE;
1628 
1629  if (mainwindow)
1630  {
1631  GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1632  gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
1633  gnc_statusbar_notification_messageid = 0;
1634  }
1635  else
1636  {
1637  g_warning("oops, no GncMainWindow obtained\n");
1638  }
1639  return FALSE; // should not be called again
1640 }
1641 #endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
1642 
1643 /* Creates a statusbar message stating the last modification time of the opened
1644  * data file. */
1645 static gchar *generate_statusbar_lastmodified_message()
1646 {
1647  gchar *message = nullptr;
1648  const gchar *uri = nullptr;
1649 
1650  if (gnc_current_session_exist())
1651  {
1652  uri = qof_session_get_url (gnc_get_current_session ());
1653  }
1654 
1655  if (!(uri && strlen (uri)))
1656  return nullptr;
1657  else
1658  {
1659  if (gnc_uri_targets_local_fs (uri))
1660  {
1661  /* The filename is a true file. */
1662  gchar *filepath = gnc_uri_get_path ( uri );
1663  gchar *filename = g_path_get_basename ( filepath );
1664  GFile *file = g_file_new_for_uri (uri);
1665  GFileInfo *info = g_file_query_info (file,
1666  G_FILE_ATTRIBUTE_TIME_MODIFIED,
1667  G_FILE_QUERY_INFO_NONE,
1668  NULL, NULL);
1669 
1670  if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
1671  {
1672  // Access the mtime information through stat(2)
1673  struct stat statbuf;
1674  int r = stat(filepath, &statbuf);
1675  if (r == 0)
1676  {
1677  /* Translators: This is the date and time that is shown in
1678  the status bar after opening a file: The date and time of
1679  last modification. The string is a format string using
1680  boost::date_time's format flags, see the boost docs for an
1681  explanation of the modifiers. */
1682  char *time_string = gnc_print_time64(statbuf.st_mtime,
1683  _("Last modified on %a, %b %d, %Y at %I:%M %p"));
1684  //g_warning("got time %ld, str=%s\n", mtime, time_string);
1685  /* Translators: This message appears in the status bar after opening the file. */
1686  message = g_strdup_printf(_("File %s opened. %s"),
1687  filename, time_string);
1688  free(time_string);
1689  }
1690  else
1691  {
1692  g_warning("Unable to read mtime for file %s\n", filepath);
1693  // message is still nullptr
1694  }
1695  }
1696  g_free(filename);
1697  g_free(filepath);
1698  g_object_unref (info);
1699  g_object_unref (file);
1700  }
1701  // If the URI is not a file but a database, we can maybe also show
1702  // something useful, but I have no idea how to obtain this information.
1703  }
1704  return message;
1705 }
1706 
1707 static void
1708 statusbar_notification_lastmodified()
1709 {
1710  // First look up the first GncMainWindow to set the statusbar there
1711  GList *iter;
1712  GtkWidget *widget = nullptr;
1713  for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
1714  iter = g_list_next(iter))
1715  {
1716  widget = static_cast<GtkWidget*>(iter->data);
1717  }
1718  if (widget && GNC_IS_MAIN_WINDOW(widget))
1719  {
1720  // Ok, we found a mainwindow where we can set a statusbar message
1721  GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
1722  GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1723 
1724  gchar *msg = generate_statusbar_lastmodified_message();
1725  if (msg)
1726  {
1727  gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
1728  }
1729  g_free(msg);
1730 
1731 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1732  // Also register a timeout callback to remove that statusbar
1733  // notification again after 10 seconds
1734  g_timeout_add(10 * 1000, statusbar_notification_off, nullptr); // maybe not needed anyway?
1735 #endif
1736  }
1737  else
1738  {
1739  g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
1740  }
1741 }
1742 
1743 
1747 {
1749  gchar *action_name;
1750 
1752  gchar *label;
1753 
1755  gboolean visible;
1756 
1758  gint index;
1759 };
1760 
1761 #ifndef MAC_INTEGRATION
1762 
1775 static void
1776 gnc_main_window_update_one_menu_action (GncMainWindow *window,
1777  struct menu_update *data)
1778 {
1779  GncMainWindowPrivate *priv;
1780  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
1781  GMenuItem *item;
1782  gint pos;
1783 
1784  ENTER("window %p, action %s, label %s, index %d, visible %d", window,
1785  data->action_name, data->label, data->index, data->visible);
1786 
1787  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1788 
1789  gsm->search_action_label = nullptr;
1790  gsm->search_action_name = "WindowsPlaceholder1"; // placeholder
1791 
1792  if (!gnc_menubar_model_find_item (priv->menubar_model, gsm))
1793  {
1794  LEAVE("Could not find placeholder 'WindowsPlaceholder1' for windows entries");
1795  g_free (gsm);
1796  return;
1797  }
1798 
1799  pos = gsm->index + data->index + 1;
1800 
1801  if (!data->visible)
1802  {
1803  if (pos < g_menu_model_get_n_items (gsm->model))
1804  g_menu_remove (G_MENU(gsm->model), pos);
1805 
1806  g_free (gsm);
1807  LEAVE(" ");
1808  return;
1809  }
1810 
1811  item = g_menu_item_new (data->label, "mainwin.WindowAction");
1812  g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_TARGET, "i", data->index);
1813 
1814  if (pos < g_menu_model_get_n_items (gsm->model))
1815  g_menu_remove (G_MENU(gsm->model), pos);
1816  g_menu_insert_item (G_MENU(gsm->model), pos, item);
1817 
1818  g_free (gsm);
1819  LEAVE(" ");
1820 }
1821 
1834 static void
1835 gnc_main_window_update_radio_button (GncMainWindow *window)
1836 {
1837  GncMainWindowPrivate *priv;
1838  GAction *action;
1839  gsize index;
1840 
1841  ENTER("window %p", window);
1842 
1843  /* Show the new entry in all windows. */
1844  index = g_list_index (active_windows, window);
1845 
1846  if (index >= gnc_main_window_max_number)
1847  {
1848  LEAVE("window %" G_GSIZE_FORMAT ", only %d actions", index, gnc_main_window_max_number);
1849  return;
1850  }
1851 
1852  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1853 
1854  action = g_action_map_lookup_action (G_ACTION_MAP(window),
1855  "WindowAction");
1856 
1857  /* Block the signal so as not to affect window ordering (top to
1858  * bottom) on the screen */
1859  g_signal_handlers_block_by_func (G_OBJECT(action),
1860  (gpointer)gnc_main_window_cmd_window_raise,
1861  window);
1862 
1863  DEBUG("blocked signal on action %p, window %p", action, window);
1864  g_action_change_state (G_ACTION(action), g_variant_new_int32 (index));
1865 
1866  g_signal_handlers_unblock_by_func (G_OBJECT(action),
1867  (gpointer)gnc_main_window_cmd_window_raise,
1868  window);
1869  LEAVE(" ");
1870 }
1871 
1884 static void
1885 gnc_main_window_update_menu_item (GncMainWindow *window)
1886 {
1887  struct menu_update data;
1888  gchar **strings, *title, *expanded;
1889  gsize index;
1890 
1891  ENTER("window %p", window);
1892 
1893  index = g_list_index (active_windows, window);
1894 
1896  {
1897  LEAVE("skip window %" G_GSIZE_FORMAT " (only %d entries)", index, gnc_main_window_max_number);
1898  return;
1899  }
1900 
1901  /* Figure out the label name. Add the accelerator if possible. */
1902  title = gnc_main_window_generate_title (window);
1903  strings = g_strsplit (title, "_", 0);
1904  g_free (title);
1905  expanded = g_strjoinv ("__", strings);
1907  {
1908  data.label = g_strdup_printf ("_%" G_GSIZE_FORMAT " %s", (index + 1) % 10, expanded);
1909  g_free (expanded);
1910  }
1911  else
1912  {
1913  data.label = expanded;
1914  }
1915  g_strfreev (strings);
1916 
1917  data.visible = TRUE;
1918 // data.action_name = g_strdup_printf ("Window%" G_GSIZE_FORMAT "Action", index);
1919  data.index = index;
1920 
1921  g_list_foreach (active_windows,
1922  (GFunc)gnc_main_window_update_one_menu_action,
1923  &data);
1924 
1925 // g_free (data.action_name);
1926  g_free (data.label);
1927 
1928  LEAVE(" ");
1929 }
1930 #endif /* !MAC_INTEGRATION */
1931 
1940 #ifndef MAC_INTEGRATION
1941 static void
1942 gnc_main_window_update_all_menu_items (void)
1943 {
1944  struct menu_update data;
1945 
1946  ENTER("");
1947  /* First update the entries for all existing windows */
1948  g_list_foreach (active_windows,
1949  (GFunc)gnc_main_window_update_menu_item,
1950  nullptr);
1951 
1952  g_list_foreach (active_windows,
1953  (GFunc)gnc_main_window_update_radio_button,
1954  nullptr);
1955 
1956  /* Now hide any entries that aren't being used. */
1957  data.visible = FALSE;
1958  // need i to descend from gnc_main_window_max_number
1959  for (gsize i = gnc_main_window_max_number - 1; i > 0 && i >= g_list_length (active_windows); i--)
1960  {
1961  data.index = i;
1962  data.action_name = g_strdup_printf ("Window%dAction", data.index);
1963  data.label = g_strdup_printf ("mywin%" G_GSIZE_FORMAT, i % 10);
1964 
1965  g_list_foreach (active_windows,
1966  (GFunc)gnc_main_window_update_one_menu_action,
1967  &data);
1968 
1969  g_free (data.action_name);
1970  g_free (data.label);
1971  }
1972  LEAVE(" ");
1973 }
1974 #endif /* !MAC_INTEGRATION */
1975 
1987 static void
1988 gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
1989  gpointer user_data)
1990 {
1991  auto new_value{static_cast<gboolean*>(user_data)};
1992  ENTER("page %p, visible %d", page, *new_value);
1993  auto close_button{static_cast<GtkWidget*>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON))};
1994  if (!close_button)
1995  {
1996  LEAVE("no close button");
1997  return;
1998  }
1999 
2000  if (*new_value)
2001  gtk_widget_show (close_button);
2002  else
2003  gtk_widget_hide (close_button);
2004  LEAVE(" ");
2005 }
2006 
2007 
2020 static void
2021 gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
2022 {
2023  gboolean new_value;
2024 
2025  ENTER(" ");
2026  new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
2028  gnc_main_window_update_tab_close_one_page,
2029  &new_value);
2030  LEAVE(" ");
2031 }
2032 
2033 
2042 static void
2043 gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
2044  gpointer user_data)
2045 {
2046  const gchar *color_string;
2047 
2048  ENTER("page %p", page);
2049  color_string = gnc_plugin_page_get_page_color(page);
2050  main_window_update_page_color (page, color_string);
2051  LEAVE(" ");
2052 }
2053 
2054 
2065 static void
2066 gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
2067 {
2068  ENTER(" ");
2069  g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
2070  auto window{static_cast<GncMainWindow*>(user_data)};
2071  auto priv{GNC_MAIN_WINDOW_GET_PRIVATE(window)};
2072  if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
2073  priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2074  gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
2075  LEAVE(" ");
2076 }
2077 
2078 
2082 typedef struct
2083 {
2084  gint tab_width;
2085  gboolean tabs_left_right;
2086 } TabWidth;
2087 
2088 static TabWidth *
2089 populate_tab_width_struct (void)
2090 {
2091  TabWidth *tw;
2092 
2093  tw = g_new0 (TabWidth, 1);
2094  tw->tab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2095  tw->tabs_left_right = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT) ||
2096  gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT);
2097 
2098  return tw;
2099 }
2100 
2122 static void
2123 gnc_main_window_set_tab_ellipsize (GtkWidget *label, gint tab_width, gboolean tab_left_right)
2124 {
2125  const gchar *lab_text = gtk_label_get_text (GTK_LABEL(label));
2126 
2127  if (tab_width != 0)
2128  {
2129  gint text_length = g_utf8_strlen (lab_text, -1);
2130  if (text_length < tab_width)
2131  {
2132  if (tab_left_right) // tabs position is left or right
2133  gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2134  else // tabs position is top or bottom
2135  gtk_label_set_width_chars (GTK_LABEL(label), text_length);
2136 
2137  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2138  }
2139  else
2140  {
2141  gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2142  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
2143  }
2144  }
2145  else
2146  {
2147  gtk_label_set_width_chars (GTK_LABEL(label), 15);
2148  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2149  }
2150 }
2151 
2152 
2163 static void
2164 gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
2165  gpointer user_data)
2166 {
2167  auto tw{static_cast<TabWidth*>(user_data)};
2168 
2169  ENTER("page %p, tab width %d, tabs on left or right %d",
2170  page, tw->tab_width, tw->tabs_left_right);
2171 
2172  auto label{static_cast<GtkWidget *>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL))};
2173  if (!label)
2174  {
2175  LEAVE("no label");
2176  return;
2177  }
2178  gnc_main_window_set_tab_ellipsize (label, tw->tab_width, tw->tabs_left_right);
2179  LEAVE(" ");
2180 }
2181 
2182 
2195 static void
2196 gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
2197 {
2198  TabWidth *tw;
2199 
2200  ENTER(" ");
2201 
2202  tw = populate_tab_width_struct ();
2203 
2204  gnc_main_window_foreach_page (gnc_main_window_update_tab_width_one_page, tw);
2205  g_free (tw);
2206 
2207  LEAVE(" ");
2208 }
2209 
2210 
2211 /************************************************************
2212  * Tab Label Implementation *
2213  ************************************************************/
2214 static gboolean
2215 main_window_find_tab_items (GncMainWindow *window,
2216  GncPluginPage *page,
2217  GtkWidget **label_p,
2218  GtkWidget **entry_p)
2219 {
2220  GncMainWindowPrivate *priv;
2221  GtkWidget *tab_hbox, *widget, *tab_widget;
2222  GList *children, *tmp;
2223 
2224  ENTER("window %p, page %p, label_p %p, entry_p %p",
2225  window, page, label_p, entry_p);
2226  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2227  *label_p = *entry_p = nullptr;
2228 
2229  if (!page->notebook_page)
2230  {
2231  LEAVE("invalid notebook_page");
2232  return FALSE;
2233  }
2234 
2235  tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2236  page->notebook_page);
2237  if (GTK_IS_EVENT_BOX (tab_widget))
2238  tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2239  else if (GTK_IS_BOX (tab_widget))
2240  tab_hbox = tab_widget;
2241  else
2242  {
2243  PWARN ("Unknown widget for tab label %p", tab_widget);
2244  return FALSE;
2245  }
2246 
2247  children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
2248  for (tmp = children; tmp; tmp = g_list_next(tmp))
2249  {
2250  widget = static_cast<GtkWidget*>(tmp->data);
2251  if (GTK_IS_LABEL(widget))
2252  {
2253  *label_p = widget;
2254  }
2255  else if (GTK_IS_ENTRY(widget))
2256  {
2257  *entry_p = widget;
2258  }
2259  }
2260  g_list_free(children);
2261 
2262  LEAVE("label %p, entry %p", *label_p, *entry_p);
2263  return (*label_p && *entry_p);
2264 }
2265 
2266 static gboolean
2267 main_window_find_tab_widget (GncMainWindow *window,
2268  GncPluginPage *page,
2269  GtkWidget **widget_p)
2270 {
2271  GncMainWindowPrivate *priv;
2272 
2273  ENTER("window %p, page %p, widget %p",
2274  window, page, widget_p);
2275  *widget_p = nullptr;
2276 
2277  if (!page->notebook_page)
2278  {
2279  LEAVE("invalid notebook_page");
2280  return FALSE;
2281  }
2282 
2283  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2284  *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2285  page->notebook_page);
2286 
2287  LEAVE("widget %p", *widget_p);
2288  return TRUE;
2289 }
2290 
2291 void
2293  const gchar *name_in)
2294 {
2295  GncMainWindow *window;
2296  GncMainWindowPrivate *priv;
2297  GtkWidget *label, *entry;
2298  gchar *name, *old_page_name, *old_page_long_name;
2299  TabWidth *tw;
2300 
2301  ENTER(" ");
2302 
2303  if ((name_in == nullptr) || (*name_in == '\0'))
2304  {
2305  LEAVE("no string");
2306  return;
2307  }
2308  name = g_strstrip(g_strdup(name_in));
2309 
2310  /* Optimization, if the name hasn't changed, don't update X. */
2311  if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
2312  {
2313  g_free(name);
2314  LEAVE("empty string or name unchanged");
2315  return;
2316  }
2317 
2318  old_page_name = g_strdup( gnc_plugin_page_get_page_name(page));
2319  old_page_long_name = g_strdup( gnc_plugin_page_get_page_long_name(page));
2320 
2321  /* Update the plugin */
2322  gnc_plugin_page_set_page_name(page, name);
2323 
2324  /* Update the notebook tab */
2325  window = GNC_MAIN_WINDOW(page->window);
2326  if (!window)
2327  {
2328  g_free(old_page_name);
2329  g_free(old_page_long_name);
2330  g_free(name);
2331  LEAVE("no window widget available");
2332  return;
2333  }
2334 
2335  if (main_window_find_tab_items(window, page, &label, &entry))
2336  gtk_label_set_text(GTK_LABEL(label), name);
2337 
2338  /* Adjust the label width for new text */
2339  tw = populate_tab_width_struct ();
2340  gnc_main_window_update_tab_width_one_page (page, tw);
2341  g_free (tw);
2342 
2343  /* Update Tooltip on notebook Tab */
2344  if (old_page_long_name && old_page_name
2345  && g_strrstr(old_page_long_name, old_page_name) != nullptr)
2346  {
2347  gchar *new_page_long_name;
2348  gint string_position;
2349  GtkWidget *tab_widget;
2350 
2351  string_position = strlen(old_page_long_name) - strlen(old_page_name);
2352  new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, nullptr);
2353 
2354  gnc_plugin_page_set_page_long_name(page, new_page_long_name);
2355 
2356  if (main_window_find_tab_widget(window, page, &tab_widget))
2357  gtk_widget_set_tooltip_text(tab_widget, new_page_long_name);
2358 
2359  g_free(new_page_long_name);
2360  }
2361 
2362  /* Update the notebook menu */
2363  if (page->notebook_page)
2364  {
2365  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2366  label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
2367  page->notebook_page);
2368  gtk_label_set_text(GTK_LABEL(label), name);
2369  }
2370 
2371  /* Force an update of the window title */
2372  gnc_main_window_update_title(window);
2373  g_free(old_page_long_name);
2374  g_free(old_page_name);
2375  g_free(name);
2376  LEAVE("done");
2377 }
2378 
2379 
2380 void
2382  const gchar *color_in)
2383 {
2384  GncMainWindow *window;
2385  GncMainWindowPrivate *priv;
2386  GtkWidget *tab_widget;
2387  GdkRGBA tab_color;
2388  gchar *color_string = nullptr;
2389  gboolean want_color = FALSE;
2390 
2391  ENTER(" ");
2392  if (color_in)
2393  color_string = g_strstrip(g_strdup(color_in));
2394 
2395  if (color_string && *color_string != '\0')
2396  want_color = TRUE;
2397 
2398  /* Update the plugin */
2399  window = GNC_MAIN_WINDOW(page->window);
2400  if (want_color)
2401  gnc_plugin_page_set_page_color(page, color_string);
2402  else
2403  gnc_plugin_page_set_page_color(page, nullptr);
2404 
2405  /* Update the notebook tab */
2406  main_window_find_tab_widget (window, page, &tab_widget);
2407  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2408 
2409  if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
2410  {
2411  GtkCssProvider *provider = gtk_css_provider_new();
2412  GtkStyleContext *stylectxt;
2413  gchar *col_str, *widget_css;
2414 
2415  if (!GTK_IS_EVENT_BOX (tab_widget))
2416  {
2417  GtkWidget *event_box = gtk_event_box_new ();
2418  g_object_ref (tab_widget);
2419  gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2420  page->notebook_page, event_box);
2421  gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
2422  g_object_unref (tab_widget);
2423  tab_widget = event_box;
2424  }
2425 
2426  stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
2427  col_str = gdk_rgba_to_string (&tab_color);
2428  widget_css = g_strconcat ("*{\n background-color:", col_str, ";\n}\n", nullptr);
2429 
2430  gtk_css_provider_load_from_data (provider, widget_css, -1, nullptr);
2431  gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
2432  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2433  g_object_unref (provider);
2434  g_free (col_str);
2435  g_free (widget_css);
2436  }
2437  else
2438  {
2439  if (GTK_IS_EVENT_BOX (tab_widget))
2440  {
2441  GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2442  g_object_ref (tab_hbox);
2443  gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
2444  gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2445  page->notebook_page, tab_hbox);
2446  g_object_unref (tab_hbox);
2447  }
2448  }
2449  g_free(color_string);
2450  LEAVE("done");
2451 }
2452 
2453 
2454 void
2456  gboolean read_only)
2457 {
2458  GncMainWindow *window;
2459  GncMainWindowPrivate *priv;
2460  GtkWidget *tab_widget;
2461  GtkWidget *image = NULL;
2462  GList *children;
2463  gchar *image_name = NULL;
2464  const gchar *icon_name;
2465 
2466  ENTER(" ");
2467 
2468  g_return_if_fail (page && page->window);
2469 
2470  if (!GNC_IS_MAIN_WINDOW (page->window))
2471  return;
2472 
2473  window = GNC_MAIN_WINDOW(page->window);
2474 
2475  /* Get the notebook tab widget */
2476  main_window_find_tab_widget (window, page, &tab_widget);
2477  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2478 
2479  if (!tab_widget)
2480  {
2481  LEAVE("no tab widget");
2482  return;
2483  }
2484 
2485  if (GTK_IS_EVENT_BOX(tab_widget))
2486  tab_widget = gtk_bin_get_child (GTK_BIN(tab_widget));
2487 
2488  children = gtk_container_get_children (GTK_CONTAINER(tab_widget));
2489  /* For each, walk the list of container children to get image widget */
2490  for (GList *child = children; child; child = g_list_next (child))
2491  {
2492  GtkWidget *widget = static_cast<GtkWidget*>(child->data);
2493  if (GTK_IS_IMAGE(widget))
2494  image = widget;
2495  }
2496  g_list_free (children);
2497 
2498  if (!image)
2499  {
2500  LEAVE("no image to replace");
2501  return;
2502  }
2503 
2504  g_object_get (image, "icon-name", &image_name, NULL);
2505 
2506  if (read_only)
2507  icon_name = "changes-prevent-symbolic";
2508  else
2509  icon_name = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
2510 
2511  if (g_strcmp0 (icon_name, image_name) == 0)
2512  {
2513  LEAVE("page icon the same, no need to replace");
2514  g_free (image_name);
2515  return;
2516  }
2517  gtk_container_remove (GTK_CONTAINER(tab_widget), image);
2518  image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
2519  gtk_widget_show (image);
2520 
2521  gtk_container_add (GTK_CONTAINER(tab_widget), image);
2522  gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
2523  gtk_box_reorder_child (GTK_BOX(tab_widget), image, 0);
2524 
2525  g_free (image_name);
2526  LEAVE("done");
2527 }
2528 
2529 
2530 static void
2531 gnc_main_window_tab_entry_activate (GtkWidget *entry,
2532  GncPluginPage *page)
2533 {
2534  GtkWidget *label, *entry2;
2535 
2536  g_return_if_fail(GTK_IS_ENTRY(entry));
2537  g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
2538 
2539  ENTER("");
2540  if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2541  page, &label, &entry2))
2542  {
2543  LEAVE("can't find required widgets");
2544  return;
2545  }
2546 
2547  main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
2548 
2549  gtk_widget_hide(entry);
2550  gtk_widget_show(label);
2551  LEAVE("");
2552 }
2553 
2554 
2555 static gboolean
2556 gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
2557  GncPluginPage *page)
2558 {
2559  ENTER("");
2560  gnc_main_window_tab_entry_activate(entry, page);
2561  LEAVE("");
2562  return FALSE;
2563 }
2564 
2565 static gboolean
2566 gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
2567  GdkEvent *event,
2568  GncPluginPage *page)
2569 {
2570  ENTER("");
2571  gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
2572  LEAVE("");
2573  return FALSE;
2574 }
2575 
2576 static gboolean
2577 gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
2578  GdkEventKey *event,
2579  GncPluginPage *page)
2580 {
2581  if (event->keyval == GDK_KEY_Escape)
2582  {
2583  GtkWidget *label, *entry2;
2584 
2585  g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
2586  g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
2587 
2588  ENTER("");
2589  if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2590  page, &label, &entry2))
2591  {
2592  LEAVE("can't find required widgets");
2593  return FALSE;
2594  }
2595 
2596  gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
2597  gtk_widget_hide(entry);
2598  gtk_widget_show(label);
2599  LEAVE("");
2600  }
2601  return FALSE;
2602 }
2603 
2604 /************************************************************
2605  * Widget Implementation *
2606  ************************************************************/
2607 
2608 
2609 
2617 static void
2618 gnc_main_window_class_init (GncMainWindowClass *klass)
2619 {
2620  GObjectClass *object_class = G_OBJECT_CLASS (klass);
2621  GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
2622 
2623  parent_class = static_cast<GObjectClass*>(g_type_class_peek_parent (klass));
2624 
2625  window_type = g_quark_from_static_string ("gnc-main-window");
2626 
2627  object_class->finalize = gnc_main_window_finalize;
2628 
2629  /* GtkWidget signals */
2630  gtkwidget_class->destroy = gnc_main_window_destroy;
2631 
2643  main_window_signals[PAGE_ADDED] =
2644  g_signal_new ("page_added",
2645  G_OBJECT_CLASS_TYPE (object_class),
2646  G_SIGNAL_RUN_FIRST,
2647  G_STRUCT_OFFSET (GncMainWindowClass, page_added),
2648  nullptr, nullptr,
2649  g_cclosure_marshal_VOID__OBJECT,
2650  G_TYPE_NONE, 1,
2651  G_TYPE_OBJECT);
2652 
2663  main_window_signals[PAGE_CHANGED] =
2664  g_signal_new ("page_changed",
2665  G_OBJECT_CLASS_TYPE (object_class),
2666  G_SIGNAL_RUN_FIRST,
2667  G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
2668  nullptr, nullptr,
2669  g_cclosure_marshal_VOID__OBJECT,
2670  G_TYPE_NONE, 1,
2671  G_TYPE_OBJECT);
2672 
2681  main_window_signals[MENU_CHANGED] =
2682  g_signal_new ("menu_changed",
2683  G_OBJECT_CLASS_TYPE (object_class),
2684  G_SIGNAL_RUN_FIRST,
2685  G_STRUCT_OFFSET (GncMainWindowClass, menu_changed),
2686  nullptr, nullptr,
2687  g_cclosure_marshal_VOID__OBJECT,
2688  G_TYPE_NONE, 1,
2689  G_TYPE_OBJECT);
2690 
2691  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2692  GNC_PREF_SHOW_CLOSE_BUTTON,
2693  (gpointer)gnc_main_window_update_tab_close,
2694  nullptr);
2695  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2696  GNC_PREF_TAB_WIDTH,
2697  (gpointer)gnc_main_window_update_tab_width,
2698  nullptr);
2699 
2700  gnc_hook_add_dangler(HOOK_BOOK_SAVED,
2701  (GFunc)gnc_main_window_update_all_titles, nullptr, nullptr);
2702  gnc_hook_add_dangler(HOOK_BOOK_OPENED,
2703  (GFunc)gnc_main_window_attach_to_book, nullptr, nullptr);
2704 
2705 }
2706 
2707 
2716 static void
2717 gnc_main_window_init (GncMainWindow *window, void *data)
2718 {
2719  GncMainWindowPrivate *priv;
2720 
2721  GncMainWindowClass *klass = (GncMainWindowClass*)data;
2722 
2723  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2724 
2725  // Set the name for this dialog so it can be easily manipulated with css
2726  gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-main-window");
2727 
2728  priv->event_handler_id =
2729  qof_event_register_handler(gnc_main_window_event_handler, window);
2730 
2731  priv->restoring_pages = FALSE;
2732 
2733  priv->display_item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nullptr);
2734 
2735  priv->previous_plugin_page_name = nullptr;
2736  priv->previous_menu_qualifier = nullptr;
2737 
2738  priv->accel_group = gtk_accel_group_new ();
2739  gtk_window_add_accel_group (GTK_WINDOW(window), priv->accel_group);
2740 
2741  /* Get the show_color_tabs value preference */
2742  priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2743 
2744  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2745  GNC_PREF_TAB_COLOR,
2746  (gpointer)gnc_main_window_update_tab_color,
2747  window);
2748 
2749  gnc_main_window_setup_window (window);
2750  gnc_gobject_tracking_remember(G_OBJECT(window),
2751  G_OBJECT_CLASS(klass));
2752 }
2753 
2754 
2765 static void
2766 gnc_main_window_finalize (GObject *object)
2767 {
2768  g_return_if_fail (object != nullptr);
2769  g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
2770 
2771  if (active_windows == nullptr)
2772  {
2773  /* Oops. User killed last window and we didn't catch it. */
2774  g_idle_add((GSourceFunc)gnc_shutdown, 0);
2775  }
2776 
2778  G_OBJECT_CLASS (parent_class)->finalize (object);
2779 }
2780 
2781 
2782 static void
2783 gnc_main_window_remove_prefs (GncMainWindow *window)
2784 {
2785  // remove the registered preference callbacks setup in this file.
2786  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2787  GNC_PREF_TAB_COLOR,
2788  (gpointer)gnc_main_window_update_tab_color,
2789  window);
2790 
2791  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2792  GNC_PREF_SHOW_CLOSE_BUTTON,
2793  (gpointer)gnc_main_window_update_tab_close,
2794  nullptr);
2795 
2796  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2797  GNC_PREF_TAB_WIDTH,
2798  (gpointer)gnc_main_window_update_tab_width,
2799  nullptr);
2800 
2801  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2802  GNC_PREF_TAB_POSITION_TOP,
2803  (gpointer)gnc_main_window_update_tab_position,
2804  window);
2805 
2806  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2807  GNC_PREF_TAB_POSITION_BOTTOM,
2808  (gpointer)gnc_main_window_update_tab_position,
2809  window);
2810 
2811  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2812  GNC_PREF_TAB_POSITION_LEFT,
2813  (gpointer)gnc_main_window_update_tab_position,
2814  window);
2815 
2816  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2817  GNC_PREF_TAB_POSITION_RIGHT,
2818  (gpointer)gnc_main_window_update_tab_position,
2819  window);
2820 
2821  // remove the registered negative color preference callback.
2822  if (gnc_prefs_get_reg_negative_color_pref_id() > 0 && window->window_quitting)
2823  {
2824  gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL,
2826  gnc_prefs_set_reg_negative_color_pref_id (0);
2827  }
2828 
2829  // remove the registered auto_raise_lists preference callback.
2830  if (gnc_prefs_get_reg_auto_raise_lists_id() > 0 && window->window_quitting)
2831  {
2832  gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL_REGISTER,
2834  gnc_prefs_set_reg_auto_raise_lists_id (0);
2835  }
2836 }
2837 
2838 
2839 static void
2840 gnc_main_window_destroy (GtkWidget *widget)
2841 {
2842  GncMainWindow *window;
2843  GncMainWindowPrivate *priv;
2844  GncPluginManager *manager;
2845  GList *plugins;
2846 
2847  g_return_if_fail (widget != nullptr);
2848  g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
2849 
2850  window = GNC_MAIN_WINDOW (widget);
2851 
2852  active_windows = g_list_remove (active_windows, window);
2853 
2854  /* Do these things once */
2855  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2856  if (priv->event_handler_id > 0)
2857  {
2858 
2859  /* Close any pages in this window */
2860  while (priv->current_page)
2862 
2863  if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
2864  gnc_window_set_progressbar_window(nullptr);
2865 #ifndef MAC_INTEGRATION
2866  /* Update the "Windows" menu in all other windows */
2867  gnc_main_window_update_all_menu_items();
2868 #endif
2869  /* remove the preference callbacks from the main window */
2870  gnc_main_window_remove_prefs (window);
2871 
2873  priv->event_handler_id = 0;
2874 
2875  g_hash_table_destroy (priv->display_item_hash);
2876 
2877  /* GncPluginManager stuff */
2878  manager = gnc_plugin_manager_get ();
2879  plugins = gnc_plugin_manager_get_plugins (manager);
2880  g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
2881  g_list_free (plugins);
2882  }
2883  GTK_WIDGET_CLASS (parent_class)->destroy (widget);
2884 }
2885 
2886 
2887 static gboolean
2888 gnc_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2889 {
2890  GncMainWindowPrivate *priv;
2891  GdkModifierType modifiers;
2892 
2893  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(widget), FALSE);
2894 
2895  priv = GNC_MAIN_WINDOW_GET_PRIVATE(widget);
2896 
2897  modifiers = gtk_accelerator_get_default_mod_mask ();
2898 
2899  if ((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) // Ctrl+Alt+
2900  {
2901  const gchar *account_key = C_ ("lower case key for short cut to 'Accounts'", "a");
2902  guint account_keyval = gdk_keyval_from_name (account_key);
2903 
2904  if ((account_keyval == event->keyval) || (account_keyval == gdk_keyval_to_lower (event->keyval)))
2905  {
2906  gint page = 0;
2907 
2908  for (GList *item = priv->installed_pages; item; item = g_list_next (item))
2909  {
2910  const gchar *pname = gnc_plugin_page_get_plugin_name (GNC_PLUGIN_PAGE(item->data));
2911 
2912  if (g_strcmp0 (pname, "GncPluginPageAccountTree") == 0)
2913  {
2914  gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook), page);
2915  return TRUE;
2916  }
2917  page++;
2918  }
2919  }
2920  else if ((GDK_KEY_Menu == event->keyval) || (GDK_KEY_space == event->keyval))
2921  {
2922  GList *menu = gtk_menu_get_for_attach_widget (GTK_WIDGET(priv->notebook));
2923 
2924  if (menu)
2925  {
2926  gtk_menu_popup_at_widget (GTK_MENU(menu->data),
2927  GTK_WIDGET(priv->notebook),
2928  GDK_GRAVITY_SOUTH,
2929  GDK_GRAVITY_SOUTH,
2930  NULL);
2931  return TRUE;
2932  }
2933  }
2934  }
2935  return FALSE;
2936 }
2937 
2938 
2939 /* Create a new gnc main window plugin.
2940  */
2941 GncMainWindow *
2943 {
2944  auto window{static_cast<GncMainWindow*>(g_object_new (GNC_TYPE_MAIN_WINDOW, nullptr))};
2945  gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
2946 
2947  auto old_window = gnc_ui_get_main_window (nullptr);
2948  if (old_window)
2949  {
2950  gint width, height;
2951  gtk_window_get_size (old_window, &width, &height);
2952  gtk_window_resize (GTK_WINDOW (window), width, height);
2953  if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
2954  & GDK_WINDOW_STATE_MAXIMIZED) != 0)
2955  {
2956  gtk_window_maximize (GTK_WINDOW (window));
2957  }
2958  }
2959  active_windows = g_list_append (active_windows, window);
2960  gnc_main_window_update_title(window);
2961  window->window_quitting = FALSE;
2962  window->just_plugin_prefs = FALSE;
2963 #ifdef MAC_INTEGRATION
2964  gnc_quartz_set_menu(window);
2965 #else
2966  gnc_main_window_update_all_menu_items();
2967 #endif
2968  gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
2969 
2970  // set up a callback for notebook navigation
2971  g_signal_connect (G_OBJECT(window), "key-press-event",
2972  G_CALLBACK(gnc_main_window_key_press_event),
2973  NULL);
2974 
2975  return window;
2976 }
2977 
2978 /************************************************************
2979  * Utility Functions *
2980  ************************************************************/
2981 
2982 static void
2983 gnc_main_window_engine_commit_error_callback( gpointer data,
2984  QofBackendError errcode )
2985 {
2986  GncMainWindow* window = GNC_MAIN_WINDOW(data);
2987  GtkWidget* dialog;
2988  const gchar *reason = _("Unable to save to database.");
2989  if ( errcode == ERR_BACKEND_READONLY )
2990  reason = _("Unable to save to database: Book is marked read-only.");
2991  dialog = gtk_message_dialog_new( GTK_WINDOW(window),
2992  GTK_DIALOG_DESTROY_WITH_PARENT,
2993  GTK_MESSAGE_ERROR,
2994  GTK_BUTTONS_CLOSE,
2995  "%s",
2996  reason );
2997  gtk_dialog_run(GTK_DIALOG (dialog));
2998  gtk_widget_destroy(dialog);
2999 
3000 }
3001 
3019 static void
3020 gnc_main_window_connect (GncMainWindow *window,
3021  GncPluginPage *page,
3022  GtkWidget *tab_hbox,
3023  GtkWidget *menu_label)
3024 {
3025  GncMainWindowPrivate *priv;
3026  GtkNotebook *notebook;
3027  gint current_position = -1;
3028 
3029  page->window = GTK_WIDGET(window);
3030  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3031  notebook = GTK_NOTEBOOK (priv->notebook);
3032 
3033  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_OPEN_ADJACENT))
3034  current_position = g_list_index (priv->installed_pages, priv->current_page) + 1;
3035 
3036  priv->installed_pages = g_list_insert (priv->installed_pages, page, current_position);
3037  priv->usage_order = g_list_prepend (priv->usage_order, page);
3038  gtk_notebook_insert_page_menu (notebook, page->notebook_page,
3039  tab_hbox, menu_label, current_position);
3040  gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
3041  gnc_plugin_page_inserted (page);
3042  gtk_notebook_set_current_page (notebook, current_position);
3043 
3044  if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
3045  (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
3046  g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
3047 
3048  g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
3049  G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3050  g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
3051  G_CALLBACK(gnc_main_window_button_press_cb), page);
3052 }
3053 
3054 
3068 static void
3069 gnc_main_window_disconnect (GncMainWindow *window,
3070  GncPluginPage *page)
3071 {
3072  GncMainWindowPrivate *priv;
3073  GtkNotebook *notebook;
3074  GncPluginPage *new_page;
3075  gint page_num;
3076 
3077  /* Disconnect the callbacks */
3078  g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3079  (gpointer)gnc_main_window_popup_menu_cb, page);
3080  g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3081  (gpointer)gnc_main_window_button_press_cb, page);
3082 
3083  // Remove the page_changed signal callback
3084  gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
3085 
3086  /* Disconnect the page and summarybar from the window */
3087  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3088  if (priv->current_page == page)
3089  {
3090  gnc_plugin_page_unselected (page);
3091  priv->current_page = nullptr;
3092  }
3093 
3094  /* Remove it from the list of pages in the window */
3095  priv->installed_pages = g_list_remove (priv->installed_pages, page);
3096  priv->usage_order = g_list_remove (priv->usage_order, page);
3097 
3098  /* Switch to the last recently used page */
3099  notebook = GTK_NOTEBOOK (priv->notebook);
3100  if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
3101  {
3102  new_page = static_cast<GncPluginPage*>(g_list_nth_data (priv->usage_order, 0));
3103  if (new_page)
3104  {
3105  page_num = gtk_notebook_page_num(notebook, new_page->notebook_page);
3106  gtk_notebook_set_current_page(notebook, page_num);
3107  /* This may have caused WebKit to schedule a timer interrupt which it
3108  sometimes forgets to cancel before deleting the object. See
3109  <https://bugs.webkit.org/show_bug.cgi?id=119003>. Get around this
3110  by flushing all events to get rid of the timer interrupt. */
3111  while (gtk_events_pending())
3112  gtk_main_iteration();
3113  }
3114  }
3115 
3116  /* Remove the page from the notebook */
3117  page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3118  gtk_notebook_remove_page (notebook, page_num);
3119 
3120  if ( gtk_notebook_get_current_page(notebook) == -1)
3121  {
3122  /* Need to synthesize a page changed signal when the last
3123  * page is removed. The notebook doesn't generate a signal
3124  * for this, therefore the switch_page code in this file
3125  * never gets called to generate this signal. */
3126  gnc_main_window_switch_page(notebook, nullptr, -1, window);
3127  //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, nullptr);
3128  }
3129 
3130  gnc_plugin_page_removed (page);
3131 
3132  gnc_window_set_status (GNC_WINDOW(window), page, nullptr);
3133 }
3134 
3135 
3136 /************************************************************
3137  * *
3138  ************************************************************/
3139 
3140 
3141 void
3143 {
3144  GncMainWindow *window;
3145  GncMainWindowPrivate *priv;
3146  GtkNotebook *notebook;
3147  gint page_num;
3148 
3149  window = GNC_MAIN_WINDOW (page->window);
3150  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3151  notebook = GTK_NOTEBOOK (priv->notebook);
3152  page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3153  gtk_notebook_set_current_page (notebook, page_num);
3154  gtk_window_present(GTK_WINDOW(window));
3155 }
3156 
3157 
3158 /* Display a data plugin page in a window. If the page already
3159  * exists in any window, then that window will be brought to the
3160  * front and the notebook switch to display the specified page. If
3161  * the page is new then it will be added to the specified window. If
3162  * the window is nullptr, the new page will be added to the first
3163  * window.
3164  */
3165 void
3167  GncPluginPage *page)
3168 {
3169  GncMainWindowPrivate *priv;
3170  GtkWidget *tab_hbox;
3171  GtkWidget *label, *entry;
3172  const gchar *icon, *text, *color_string, *lab_text;
3173  GtkWidget *image;
3174  GList *tmp;
3175  TabWidth *tw;
3176 
3177  ENTER("window %p, page %p", window, page);
3178  if (window)
3179  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3180  g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
3181  g_return_if_fail (gnc_plugin_page_has_books(page));
3182 
3183  if (gnc_main_window_page_exists(page))
3184  {
3186  return;
3187  }
3188 
3189  /* Does the page want to be in a new window? */
3191  {
3192  /* See if there's a blank window. If so, use that. */
3193  for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
3194  {
3195  window = GNC_MAIN_WINDOW(tmp->data);
3196  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3197  if (priv->installed_pages == nullptr)
3198  {
3199  break;
3200  }
3201  }
3202  if (tmp == nullptr)
3203  window = gnc_main_window_new ();
3204  gtk_widget_show(GTK_WIDGET(window));
3205  }
3206  else if ((window == nullptr) && active_windows)
3207  {
3208  window = static_cast<GncMainWindow*>(active_windows->data);
3209  }
3210 
3211  page->window = GTK_WIDGET(window);
3213  g_object_set_data (G_OBJECT (page->notebook_page),
3214  PLUGIN_PAGE_LABEL, page);
3215 
3216  /*
3217  * The page tab.
3218  */
3219  icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
3220  lab_text = gnc_plugin_page_get_page_name(page);
3221  label = gtk_label_new (lab_text);
3222  g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
3223 
3224  tw = populate_tab_width_struct ();
3225  gnc_main_window_update_tab_width_one_page (page, tw);
3226  g_free (tw);
3227 
3228  gtk_widget_show (label);
3229 
3230  tab_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
3231 
3232  if (g_strcmp0 (gnc_plugin_page_get_plugin_name (page), "GncPluginPageAccountTree") == 0)
3233  gtk_widget_set_name (GTK_WIDGET(tab_hbox), "gnc-id-account-page-tab-box");
3234 
3235  gtk_box_set_homogeneous (GTK_BOX (tab_hbox), FALSE);
3236  gtk_widget_show (tab_hbox);
3237 
3238  if (icon != nullptr)
3239  {
3240  image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
3241  gtk_widget_show (image);
3242  gtk_box_pack_start (GTK_BOX (tab_hbox), image, FALSE, FALSE, 0);
3243  gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
3244  gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3245  }
3246  else
3247  gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3248 
3250  if (text)
3251  {
3252  gtk_widget_set_tooltip_text(tab_hbox, text);
3253  }
3254 
3255  entry = gtk_entry_new();
3256  gtk_widget_hide (entry);
3257  gtk_box_pack_start (GTK_BOX (tab_hbox), entry, TRUE, TRUE, 0);
3258  g_signal_connect(G_OBJECT(entry), "activate",
3259  G_CALLBACK(gnc_main_window_tab_entry_activate), page);
3260  g_signal_connect(G_OBJECT(entry), "focus-out-event",
3261  G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
3262  page);
3263  g_signal_connect(G_OBJECT(entry), "key-press-event",
3264  G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
3265  page);
3266  g_signal_connect(G_OBJECT(entry), "editing-done",
3267  G_CALLBACK(gnc_main_window_tab_entry_editing_done),
3268  page);
3269 
3270  /* Add close button - Not for immutable pages */
3271  if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
3272  {
3273  GtkWidget *close_image, *close_button;
3274  GtkRequisition requisition;
3275 
3276  close_button = gtk_button_new();
3277  gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
3278  close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
3279  gtk_widget_show(close_image);
3280  gtk_widget_get_preferred_size (close_image, &requisition, nullptr);
3281  gtk_widget_set_size_request(close_button, requisition.width + 4,
3282  requisition.height + 2);
3283  gtk_container_add(GTK_CONTAINER(close_button), close_image);
3284  if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
3285  gtk_widget_show (close_button);
3286  else
3287  gtk_widget_hide (close_button);
3288 
3289  g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
3290  G_CALLBACK(gnc_main_window_close_page), page);
3291 
3292  gtk_box_pack_start (GTK_BOX (tab_hbox), close_button, FALSE, FALSE, 0);
3293  gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
3294  g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
3295  }
3296 
3297  /*
3298  * The popup menu
3299  */
3300  label = gtk_label_new (gnc_plugin_page_get_page_name(page));
3301 
3302  /*
3303  * Now install it all in the window.
3304  */
3305  gnc_main_window_connect(window, page, tab_hbox, label);
3306 
3307  color_string = gnc_plugin_page_get_page_color(page);
3308  main_window_update_page_color (page, color_string);
3309  LEAVE("");
3310 }
3311 
3312 
3313 /* Remove a data plugin page from a window and display the previous
3314  * page. If the page removed was the last page in the window, and
3315  * there is more than one window open, then the entire window will be
3316  * destroyed.
3317  */
3318 void
3320 {
3321  GncMainWindow *window;
3322  GncMainWindowPrivate *priv;
3323 
3324  if (!page || !page->notebook_page)
3325  return;
3326 
3327  if (!gnc_plugin_page_finish_pending(page))
3328  return;
3329 
3330  if (!GNC_IS_MAIN_WINDOW (page->window))
3331  return;
3332 
3333  window = GNC_MAIN_WINDOW (page->window);
3334  if (!window)
3335  {
3336  g_warning("Page is not in a window.");
3337  return;
3338  }
3339 
3340  gnc_main_window_disconnect(window, page);
3342  g_object_unref(page);
3343 
3344  /* If this isn't the last window, go ahead and destroy the window. */
3345  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3346  if (priv->installed_pages == nullptr)
3347  {
3348  if (window->window_quitting)
3349  {
3351  GList *plugins = gnc_plugin_manager_get_plugins (manager);
3352 
3353  /* remove only the preference callbacks from the window plugins */
3354  window->just_plugin_prefs = TRUE;
3355  g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
3356  window->just_plugin_prefs = FALSE;
3357  g_list_free (plugins);
3358 
3359  /* remove the preference callbacks from the main window */
3360  gnc_main_window_remove_prefs (window);
3361  }
3362  if (window && (gnc_list_length_cmp (active_windows, 1) > 0))
3363  gtk_widget_destroy (GTK_WIDGET(window));
3364  }
3365 }
3366 
3367 
3368 /* Retrieve a pointer to the page that is currently at the front of
3369  * the specified window. Any plugin that needs to manipulate its
3370  * menus based upon the currently selected menu page should connect
3371  * to the "page_changed" signal on a window. The callback function
3372  * from that signal can then call this function to obtain a pointer
3373  * to the current page.
3374  */
3375 GncPluginPage *
3377 {
3378  GncMainWindowPrivate *priv;
3379 
3380  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3381  return priv->current_page;
3382 }
3383 
3384 
3385 /* Manually add a set of actions to the specified window. Plugins
3386  * whose user interface is not hard coded (e.g. the menu-additions
3387  * plugin) must create their actions at run time, then use this
3388  * function to install them into the window.
3389  */
3390 void
3392  const gchar *group_name,
3393  GSimpleActionGroup *group)
3394 {
3395  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3396  g_return_if_fail (group_name != nullptr);
3397  g_return_if_fail (G_IS_SIMPLE_ACTION_GROUP(group));
3398 
3399  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
3400  G_ACTION_GROUP(group));
3401 }
3402 
3403 
3404 static void
3405 update_menu_model (GncMainWindow *window, const gchar *ui_filename,
3406  const gchar **ui_updates)
3407 {
3408  GncMainWindowPrivate *priv;
3409  GError *error = nullptr;
3410  gchar *res_name;
3411  GtkBuilder *builder = gtk_builder_new ();
3412  GMenuModel *menu_model_part;
3413  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
3414 
3415  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3416  g_return_if_fail (ui_filename != nullptr);
3417  g_return_if_fail (ui_updates != nullptr);
3418 
3419  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3420 
3421  gtk_builder_set_translation_domain (builder, PROJECT_NAME);
3422  res_name = g_strconcat (GNUCASH_RESOURCE_PREFIX "/", ui_filename, NULL);
3423 
3424  gtk_builder_add_from_resource (builder, res_name, &error);
3425  g_free (res_name);
3426 
3427  if (error)
3428  {
3429  g_critical ("Failed to load, Error %s", error->message);
3430  g_error_free (error);
3431  return;
3432  }
3433 
3434  for (gint i = 0; ui_updates[i]; i++)
3435  {
3436  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
3437 
3438  gsm->search_action_label = nullptr;
3439  gsm->search_action_name = ui_updates[i];
3440 
3441  if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
3442  g_menu_insert_section (G_MENU(gsm->model), gsm->index, NULL, G_MENU_MODEL(menu_model_part));
3443  else
3444  PERR("Could not find '%s' in menu model", ui_updates[i]);
3445  }
3446  g_free (gsm);
3447  g_object_unref (builder);
3448 }
3449 
3450 
3451 /* Add a set of actions to the specified window. This function
3452  * should not need to be called directly by plugin implementors.
3453  * Correctly assigning values to the GncPluginClass fields during
3454  * plugin initialization will cause this routine to be automatically
3455  * called.
3456  */
3457 void
3459  const gchar *group_name,
3460  GActionEntry *actions,
3461  guint n_actions,
3462  const gchar **ui_updates,
3463  const gchar *ui_filename,
3464  gpointer user_data)
3465 {
3466  GncMainWindowPrivate *priv;
3468  GSimpleActionGroup *simple_action_group;
3469 
3470  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3471  g_return_if_fail (group_name != nullptr);
3472  g_return_if_fail (actions != nullptr);
3473  g_return_if_fail (n_actions > 0);
3474 
3475  data = g_new0 (GncMainWindowActionData, 1);
3476  data->window = window;
3477  data->data = user_data;
3478 
3479  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3480 
3481  simple_action_group = g_simple_action_group_new ();
3482 
3483  g_action_map_add_action_entries (G_ACTION_MAP(simple_action_group),
3484  actions,
3485  n_actions,
3486  data);
3487 
3488  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
3489  G_ACTION_GROUP(simple_action_group));
3490 
3491  if (ui_filename)
3492  update_menu_model (window, ui_filename, ui_updates);
3493 }
3494 
3495 
3496 /* Remove a set of actions from the specified window. This function
3497  * should not need to be called directly by plugin implementors. It
3498  * will automatically be called when a plugin is removed from a
3499  * window.
3500  */
3501 void
3503  const gchar *group_name)
3504 {
3505  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3506  g_return_if_fail (group_name != nullptr);
3507 
3508  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name, nullptr);
3509 }
3510 
3511 GAction *
3513 {
3514  GncMainWindowPrivate *priv;
3515  GAction *action = nullptr;
3516 
3517  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3518  g_return_val_if_fail (action_name != nullptr, nullptr);
3519 
3520  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3521 
3522  action = g_action_map_lookup_action (G_ACTION_MAP(window),
3523  action_name);
3524 
3525  return action;
3526 }
3527 
3528 GAction *
3530  const gchar *group_name,
3531  const gchar *action_name)
3532 {
3533  GAction *action = nullptr;
3534 
3535  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3536  g_return_val_if_fail (group_name != nullptr, nullptr);
3537  g_return_val_if_fail (action_name != nullptr, nullptr);
3538 
3539  auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
3540  if (action_group)
3541  action = g_action_map_lookup_action (G_ACTION_MAP(window), action_name);
3542 
3543  return action;
3544 }
3545 
3546 
3547 /* Retrieve a specific set of user interface actions from a window.
3548  * This function can be used to get an group of action to be
3549  * manipulated when the front page of a window has changed.
3550  */
3551 GSimpleActionGroup *
3553  const gchar *group_name)
3554 {
3555  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3556  g_return_val_if_fail (group_name != nullptr, nullptr);
3557 
3558  auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
3559  return (GSimpleActionGroup*)action_group;
3560 }
3561 
3562 GtkWidget *
3564 {
3565  GncMainWindowPrivate *priv;
3566 
3567  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3568  g_return_val_if_fail (action_name != nullptr, nullptr);
3569 
3570  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3571 
3572  return gnc_find_toolbar_item (priv->toolbar, action_name);
3573 }
3574 
3575 GtkWidget *
3577 {
3578  GncMainWindowPrivate *priv;
3579  GtkWidget *menu_item;
3580 
3581  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3582  g_return_val_if_fail (action_name != nullptr, nullptr);
3583 
3584  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3585 
3586  menu_item = GTK_WIDGET(g_hash_table_lookup (priv->display_item_hash, action_name));
3587 
3588  if (!menu_item)
3589  {
3591 
3592  g_hash_table_insert (priv->display_item_hash, g_strdup (action_name), menu_item);
3593  }
3594  return menu_item;
3595 }
3596 
3597 
3598 void
3600 {
3601  GncMainWindowPrivate *priv;
3602 
3603  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3604 
3605  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3606 
3608 }
3609 
3610 
3611 gboolean
3613  const gchar *action_name,
3614  const gchar *label,
3615  const gchar *tooltip)
3616 {
3617  GncMainWindowPrivate *priv;
3618  gboolean found = false;
3619 
3620  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), false);
3621  g_return_val_if_fail (action_name != nullptr, false);
3622  g_return_val_if_fail (label != nullptr, false);
3623 
3624  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3625 
3627  _(label), _(tooltip));
3628 
3629  // add tooltip redirect call backs
3631  priv->menubar_model,
3632  priv->statusbar);
3633 
3634  return found;
3635 }
3636 
3637 void
3639  const gchar **action_names,
3640  gboolean vis)
3641 {
3642  GncMainWindowPrivate *priv;
3643 
3644  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3645 
3646  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3647 
3648  for (gint i = 0; action_names[i]; i++)
3649  {
3650  GtkWidget *tool_item = gnc_find_toolbar_item (priv->toolbar, action_names[i]);
3651  GtkWidget *menu_item = gnc_main_window_menu_find_menu_item (window, action_names[i]);
3652 
3653  if (menu_item)
3654  {
3655  PINFO("Found menu_item %p with action name '%s', seting vis to '%s'",
3656  menu_item, action_names[i], vis ? "true" : "false");
3657  gtk_widget_set_visible (menu_item, vis);
3658  }
3659  else
3660  PINFO("Did not find menu_item with action name '%s' to set vis '%s'",
3661  action_names[i], vis ? "true" : "false");
3662 
3663  if (tool_item)
3664  {
3665  PINFO("Found tool_item %p with action name '%s', seting vis to '%s'",
3666  tool_item, action_names[i], vis ? "true" : "false");
3667  gtk_widget_set_visible (tool_item, vis);
3668  }
3669  else
3670  PINFO("Did not find tool_item with action name '%s' to set vis '%s'",
3671  action_names[i], vis ? "true" : "false");
3672  }
3673 }
3674 
3675 
3676 void
3678  GncToolBarShortNames *toolbar_labels)
3679 {
3680  GncMainWindowPrivate *priv;
3681 
3682  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3683  g_return_if_fail (toolbar_labels != nullptr);
3684 
3685  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3686 
3687  gnc_plugin_init_short_names (priv->toolbar, toolbar_labels);
3688 }
3689 
3690 
3691 static void
3692 gnc_main_window_update_toolbar (GncMainWindow *window, GncPluginPage *page,
3693  const gchar *toolbar_qualifier)
3694 {
3695  GncMainWindowPrivate *priv;
3696  GtkBuilder *builder;
3697  GAction *action;
3698 
3699  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3700  g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
3701 
3702  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3703 
3704  builder = gnc_plugin_page_get_builder (page);
3705 
3706  if (builder)
3707  {
3708  gchar *toolbar_name;
3709  gtk_container_remove (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
3710 
3711  if (toolbar_qualifier)
3712  toolbar_name = g_strconcat ("mainwin-toolbar-", toolbar_qualifier, nullptr);
3713  else
3714  toolbar_name = g_strdup ("mainwin-toolbar");
3715 
3716  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, toolbar_name);
3717 
3718  if (!priv->toolbar)
3719  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
3720 
3721  g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
3722  gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
3723  g_free (toolbar_name);
3724  }
3725 
3726  action = gnc_main_window_find_action (window, "ViewToolbarAction");
3727 
3728  // set visibility of toolbar
3729  if (action)
3730  {
3731  GVariant *state = g_action_get_state (G_ACTION(action));
3732  gtk_widget_set_visible (priv->toolbar, g_variant_get_boolean (state));
3733  g_variant_unref (state);
3734  }
3735  // add tooltip redirect call backs
3737 }
3738 
3739 
3740 void
3742  GncPluginPage *page,
3743  const gchar **ui_updates)
3744 {
3745  GncMainWindowPrivate *priv;
3746  const gchar *plugin_page_actions_group_name;
3747  GtkBuilder *builder;
3748  const gchar *menu_qualifier;
3749 
3750  GMenuModel *menu_model_part;
3751  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
3752 
3753  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3754  g_return_if_fail (page != nullptr);
3755  g_return_if_fail (ui_updates != nullptr);
3756 
3757  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3758 
3759  builder = gnc_plugin_page_get_builder (page);
3760 
3761  if (!builder)
3762  return;
3763 
3764  menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
3765 
3766  plugin_page_actions_group_name = gnc_plugin_page_get_simple_action_group_name (page);
3767 
3768  if (!plugin_page_actions_group_name)
3769  return;
3770 
3771  gtk_widget_insert_action_group (GTK_WIDGET(window), gnc_plugin_page_get_simple_action_group_name (page),
3772  G_ACTION_GROUP(gnc_plugin_page_get_action_group (page)));
3773 
3774  if ((g_strcmp0 (priv->previous_plugin_page_name,
3775  plugin_page_actions_group_name) == 0) &&
3776  (g_strcmp0 (priv->previous_menu_qualifier,
3777  menu_qualifier) == 0))
3778  return;
3779 
3780  priv->previous_plugin_page_name = plugin_page_actions_group_name;
3781  priv->previous_menu_qualifier = menu_qualifier;
3782 
3783  gnc_main_window_update_toolbar (window, page, menu_qualifier);
3784 
3785  // reset hash table and remove added menu items
3786  g_hash_table_remove_all (priv->display_item_hash);
3788  GNC_MENU_ATTRIBUTE_TEMPORARY);
3789 
3790  for (gint i = 0; ui_updates[i]; i++)
3791  {
3792  gchar *menu_name;
3793 
3794  if (menu_qualifier)
3795  menu_name = g_strconcat (ui_updates[i], "-", menu_qualifier, nullptr);
3796  else
3797  menu_name = g_strdup (ui_updates[i]);
3798 
3799  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, menu_name);
3800 
3801  if (!menu_model_part)
3802  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
3803 
3804  gsm->search_action_label = nullptr;
3805  gsm->search_action_name = ui_updates[i];
3806 
3807  if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
3808  g_menu_insert_section (G_MENU(gsm->model), gsm->index,
3809  nullptr, G_MENU_MODEL(menu_model_part));
3810  else
3811  PERR("Could not find '%s' in menu model", ui_updates[i]);
3812 
3813  g_free (menu_name);
3814  }
3815 
3816  // add tooltip redirect call backs
3818 
3819  // need to add the accelerator keys
3821 
3822  // need to signal menu has been changed
3823  g_signal_emit_by_name (window, "menu_changed", page);
3824 
3825  g_free (gsm);
3826 }
3827 
3828 
3829 static void
3830 gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
3831 {
3832  GncMainWindow *window;
3833  GtkPositionType position = GTK_POS_TOP;
3834  gint item = 0;
3835  GncMainWindowPrivate *priv;
3836  GAction *action;
3837 
3838  g_return_if_fail (GNC_IS_MAIN_WINDOW(user_data));
3839 
3840  window = GNC_MAIN_WINDOW(user_data);
3841 
3842  ENTER ("window %p", window);
3843 
3844  /* Ignore notification of the preference that is being set to false when
3845  * the choice of tab position changes. */
3846  if (pref && !gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, pref))
3847  return;
3848 
3849  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
3850  {
3851  position = GTK_POS_BOTTOM;
3852  item = 1;
3853  }
3854  else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
3855  {
3856  position = GTK_POS_LEFT;
3857  item = 2;
3858  }
3859  else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
3860  {
3861  position = GTK_POS_RIGHT;
3862  item = 3;
3863  }
3864 
3865  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3866  gtk_notebook_set_tab_pos (GTK_NOTEBOOK(priv->notebook), position);
3867 
3868  action = g_action_map_lookup_action (G_ACTION_MAP(window),
3869  "ViewTabPositionAction");
3870 
3871  g_signal_handlers_block_by_func (G_OBJECT(action),
3872  (gpointer)gnc_main_window_cmd_view_tab_position,
3873  window);
3874  g_action_change_state (G_ACTION(action), g_variant_new_int32 (item));
3875  g_signal_handlers_unblock_by_func (G_OBJECT(action),
3876  (gpointer)gnc_main_window_cmd_view_tab_position,
3877  window);
3878 
3879  gnc_main_window_update_tab_width (nullptr, (char*)GNC_PREF_TAB_WIDTH, nullptr);
3880 
3881  LEAVE ("");
3882 }
3883 
3884 /*
3885  * Based on code from Epiphany (src/ephy-window.c)
3886  */
3887 static void
3888 gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
3889 {
3890  GncMainWindowPrivate *priv;
3891  GncPluginPage *page;
3892  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
3893  GAction *action;
3894  gboolean can_copy = false, can_cut = false, can_paste = false;
3895 
3896  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3897  page = priv->current_page;
3898 
3899  if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
3900  {
3901  (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
3902  return;
3903  }
3904 
3905  if (GTK_IS_EDITABLE (widget))
3906  {
3907  gboolean has_selection;
3908 
3909  has_selection = gtk_editable_get_selection_bounds
3910  (GTK_EDITABLE (widget), nullptr, nullptr);
3911 
3912  can_copy = has_selection;
3913  can_cut = has_selection;
3914  can_paste = TRUE;
3915  }
3916  else if (GTK_IS_TEXT_VIEW (widget))
3917  {
3918  gboolean has_selection;
3919  GtkTextBuffer *text_buffer;
3920 
3921  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
3922  has_selection = gtk_text_buffer_get_selection_bounds
3923  (text_buffer, nullptr, nullptr);
3924 
3925  can_copy = has_selection;
3926  can_cut = has_selection;
3927  can_paste = TRUE;
3928  }
3929  else
3930  {
3931 #ifdef ORIGINAL_EPIPHANY_CODE
3932  /* For now we assume all actions are possible */
3933  can_copy = can_cut = can_paste = true;
3934 #else
3935  /* If its not a GtkEditable, we don't know what to do
3936  * with it. */
3937  can_copy = can_cut = can_paste = false;
3938 #endif
3939  }
3940  action = gnc_main_window_find_action (window, "EditCopyAction");
3941  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_copy);
3942 
3943  action = gnc_main_window_find_action (window, "EditCutAction");
3944  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_cut);
3945 
3946  action = gnc_main_window_find_action (window, "EditPasteAction");
3947  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_paste);
3948 }
3949 
3950 static void
3951 gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
3952 {
3953  GAction *action;
3954 
3955  action = gnc_main_window_find_action (window, "EditCopyAction");
3956  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
3957 
3958  action = gnc_main_window_find_action (window, "EditCutAction");
3959  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
3960 
3961  action = gnc_main_window_find_action (window, "EditPasteAction");
3962  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
3963 
3964 }
3965 
3966 static void
3967 gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
3968  GncMainWindow *window)
3969 {
3970  gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
3971 }
3972 
3973 static void
3974 gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
3975  GncMainWindow *window)
3976 {
3977  gnc_main_window_enable_edit_actions_sensitivity (window);
3978 }
3979 
3980 static void
3981 gnc_main_window_init_menu_updaters (GncMainWindow *window)
3982 {
3983  GtkWidget *edit_menu_item, *edit_menu;
3984 
3985  edit_menu_item = gnc_main_window_menu_find_menu_item (window, "EditAction");
3986 
3987  edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM(edit_menu_item));
3988 
3989  g_signal_connect (edit_menu, "show",
3990  G_CALLBACK(gnc_main_window_edit_menu_show_cb), window);
3991  g_signal_connect (edit_menu, "hide",
3992  G_CALLBACK(gnc_main_window_edit_menu_hide_cb), window);
3993 }
3994 
3995 /* This is used to prevent the tab having focus */
3996 static gboolean
3997 gnc_main_window_page_focus_in (GtkWidget *widget, GdkEvent *event,
3998  gpointer user_data)
3999 {
4000  auto window{static_cast<GncMainWindow *>(user_data)};
4002 
4003  g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4004  return FALSE;
4005 }
4006 
4007 static GAction *
4008 gnc_main_window_get_redirect (GncMainWindow *window, const gchar *action_name)
4009 {
4010  GncMainWindowPrivate *priv;
4011  GAction *action = nullptr;
4012  const gchar *group_name;
4013 
4014  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
4015  g_return_val_if_fail (action_name != nullptr, nullptr);
4016 
4017  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4018 
4020 
4021  PINFO("action anme is '%s', group_name is '%s'", action_name, group_name);
4022 
4023  if (group_name)
4024  {
4025  action = gnc_main_window_find_action_in_group (window, group_name, action_name);
4026 
4027  if (!action)
4029  }
4030 
4031  PINFO("Redirect action is %p for action anme '%s' and group_name '%s'",
4032  action, action_name, group_name);
4033  return action;
4034 }
4035 
4036 static void
4037 main_window_realize_cb (GtkWidget *widget, gpointer user_data)
4038 {
4039  GncMainWindow *window = (GncMainWindow*)user_data;
4040  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4041 
4042  gnc_add_accelerator_keys_for_menu (GTK_WIDGET(priv->menubar), priv->accel_group);
4043 
4044  /* need to signal menu has been changed, this will call the
4045  business function 'bind_extra_toolbuttons_visibility' */
4046  g_signal_emit_by_name (window, "menu_changed", nullptr);
4047 }
4048 
4049 static void
4050 gnc_main_window_setup_window (GncMainWindow *window)
4051 {
4052  GncMainWindowPrivate *priv;
4053  GtkWidget *main_vbox;
4054  GtkBuilder *builder;
4055  GncPluginManager *manager;
4056  GList *plugins;
4057  GError *error = nullptr;
4058  GAction *action;
4059 
4060  ENTER(" ");
4061 
4062  /* Catch window manager delete signal */
4063  g_signal_connect (G_OBJECT (window), "delete-event",
4064  G_CALLBACK (gnc_main_window_delete_event), window);
4065 
4066  /* Create widgets and add them to the window */
4067  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
4068  gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
4069  gtk_widget_show (main_vbox);
4070  gtk_container_add (GTK_CONTAINER (window), main_vbox);
4071 
4072  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4073  priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
4074  gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
4075  gtk_widget_show (priv->menu_dock);
4076  gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
4077  FALSE, TRUE, 0);
4078 
4079  priv->notebook = gtk_notebook_new ();
4080  g_object_set(G_OBJECT(priv->notebook),
4081  "scrollable", TRUE,
4082  "enable-popup", TRUE,
4083  (char *)nullptr);
4084  gtk_widget_show (priv->notebook);
4085  g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
4086  G_CALLBACK (gnc_main_window_switch_page), window);
4087  g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
4088  G_CALLBACK (gnc_main_window_page_reordered), window);
4089  g_signal_connect (G_OBJECT (priv->notebook), "focus-in-event",
4090  G_CALLBACK (gnc_main_window_page_focus_in), window);
4091  gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
4092  TRUE, TRUE, 0);
4093 
4094  priv->statusbar = gtk_statusbar_new ();
4095  gtk_widget_show (priv->statusbar);
4096  gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
4097  FALSE, TRUE, 0);
4098 
4099  priv->progressbar = gtk_progress_bar_new ();
4100  gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(priv->progressbar), TRUE);
4101  gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
4102  gtk_widget_show (priv->progressbar);
4103  gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
4104  FALSE, TRUE, 0);
4105  gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
4106  0.01);
4107 
4108  builder = gtk_builder_new ();
4109  gtk_builder_set_translation_domain (builder, PROJECT_NAME);
4110  gtk_builder_add_from_resource (builder, GNUCASH_RESOURCE_PREFIX "/gnc-main-window.ui", &error);
4111 
4112  if (error)
4113  {
4114  g_critical ("Failed to load, Error %s", error->message);
4115  g_error_free (error);
4116  return;
4117  }
4118 
4119  g_action_map_add_action_entries (G_ACTION_MAP(window),
4120  gnc_menu_actions,
4121  gnc_menu_n_actions,
4122  window);
4123 
4124  priv->menubar_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-menu");
4125  priv->menubar = gtk_menu_bar_new_from_model (priv->menubar_model);
4126  gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->menubar);
4127  gtk_widget_show (GTK_WIDGET(priv->menubar));
4128 
4129  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
4130  g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
4131  gtk_container_add (GTK_CONTAINER(priv->menu_dock), GTK_WIDGET(priv->toolbar));
4132  gtk_widget_show (GTK_WIDGET(priv->toolbar));
4133 
4134  g_object_unref (builder);
4135 
4136  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4137  initially_insensitive_actions,
4138  FALSE);
4139  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4140  always_insensitive_actions,
4141  FALSE);
4142 
4143  gnc_main_window_set_vis_of_items_by_action (window, always_hidden_actions,
4144  false);
4145 
4146  gtk_widget_insert_action_group (GTK_WIDGET(window), "mainwin",
4147  G_ACTION_GROUP(window));
4148 
4149  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4150  GNC_PREF_TAB_POSITION_TOP,
4151  (gpointer)gnc_main_window_update_tab_position,
4152  window);
4153  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4154  GNC_PREF_TAB_POSITION_BOTTOM,
4155  (gpointer)gnc_main_window_update_tab_position,
4156  window);
4157  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4158  GNC_PREF_TAB_POSITION_LEFT,
4159  (gpointer)gnc_main_window_update_tab_position,
4160  window);
4161  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4162  GNC_PREF_TAB_POSITION_RIGHT,
4163  (gpointer)gnc_main_window_update_tab_position,
4164  window);
4165  gnc_main_window_update_tab_position (nullptr, nullptr, window);
4166 
4167  gnc_main_window_init_menu_updaters (window);
4168 
4169  /* Disable the Transaction menu */
4170  action = gnc_main_window_find_action (window, "TransactionAction");
4171  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4172  /* Disable the Schedule menu */
4173  action = gnc_main_window_find_action (window, "ScheduledAction");
4174  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4175 
4176  /* Now update the "eXtensions" menu */
4177  if (!gnc_prefs_is_extra_enabled())
4178  {
4179  action = gnc_main_window_find_action (window, "ExtensionsAction");
4180  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4181  }
4182 
4183  /* GncPluginManager stuff */
4184  manager = gnc_plugin_manager_get ();
4185  plugins = gnc_plugin_manager_get_plugins (manager);
4186  g_list_foreach (plugins, gnc_main_window_add_plugin, window);
4187  g_list_free (plugins);
4188 
4189  g_signal_connect (G_OBJECT (manager), "plugin-added",
4190  G_CALLBACK (gnc_main_window_plugin_added), window);
4191  g_signal_connect (G_OBJECT (manager), "plugin-removed",
4192  G_CALLBACK (gnc_main_window_plugin_removed), window);
4193 
4194  // need to add the accelerator keys this way, mainly for --nofile
4195  g_signal_connect (G_OBJECT(window), "realize",
4196  G_CALLBACK(main_window_realize_cb), window);
4197 
4198  LEAVE(" ");
4199 }
4200 
4201 #ifdef MAC_INTEGRATION
4202 /* Event handlers for the shutdown process. Gnc_quartz_shutdown is
4203  * connected to NSApplicationWillTerminate, the last chance to do
4204  * anything before quitting. The problem is that it's launched from a
4205  * CFRunLoop, not a g_main_loop, and if we call anything that would
4206  * affect the main_loop we get an assert that we're in a subidiary
4207  * loop.
4208  */
4209 static void
4210 gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
4211 {
4212  /* Do Nothing. It's too late. */
4213 }
4214 /* Should quit responds to NSApplicationBlockTermination; returning TRUE means
4215  * "don't terminate", FALSE means "do terminate". gnc_main_window_quit() queues
4216  * a timer that starts an orderly shutdown in 250ms and if we tell macOS it's OK
4217  * to quit GnuCash gets terminated instead of doing its orderly shutdown,
4218  * leaving the book locked.
4219  */
4220 static gboolean
4221 gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
4222 {
4224  gnc_main_window_quit (window);
4225  return TRUE;
4226 }
4227 /* Enable GtkMenuItem accelerators */
4228 static gboolean
4229 can_activate_cb(GtkWidget *widget, guint signal_id, gpointer data)
4230 {
4231  //return gtk_widget_is_sensitive (widget);
4232  return TRUE;
4233 }
4234 
4235 static void
4236 gnc_quartz_set_menu (GncMainWindow* window)
4237 {
4238  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4239  auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
4240  GtkWidget *item = nullptr;
4241  GClosure *quit_closure;
4242 
4243  gtk_widget_hide (priv->menubar);
4244  gtk_widget_set_no_show_all (priv->menubar, true);
4245 
4246  gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL(priv->menubar));
4247 
4248  // File Quit
4249  item = gnc_main_window_menu_find_menu_item (window, "FileQuitAction");
4250  if (item)
4251  gtk_widget_hide (GTK_WIDGET(item));
4252 
4253  quit_closure = g_cclosure_new (G_CALLBACK (gnc_quartz_should_quit),
4254  window, NULL);
4255  gtk_accel_group_connect (priv->accel_group, 'q', GDK_META_MASK,
4256  GTK_ACCEL_MASK, quit_closure);
4257 
4258 
4259  // Help About
4260  item = gnc_main_window_menu_find_menu_item (window, "HelpAboutAction");
4261  if (item)
4262  {
4263  gtk_widget_hide (item);
4264  gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 0);
4265  }
4266 
4267  // Edit Preferences
4268  item = gnc_main_window_menu_find_menu_item (window, "EditPreferencesAction");
4269  if (item)
4270  {
4271  gtk_widget_hide (GTK_WIDGET(item));
4272  gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 2);
4273  }
4274 
4275  // Help Menu
4276  item = gnc_main_window_menu_find_menu_item (window, "HelpAction");
4277  if (item)
4278  gtkosx_application_set_help_menu (theApp, GTK_MENU_ITEM(item));
4279  // Windows Menu
4280  item = gnc_main_window_menu_find_menu_item (window, "WindowsAction");
4281  if (item)
4282  gtkosx_application_set_window_menu (theApp, GTK_MENU_ITEM(item));
4283 
4284  g_signal_connect (theApp, "NSApplicationBlockTermination",
4285  G_CALLBACK(gnc_quartz_should_quit), window);
4286 
4287  g_signal_connect (priv->menubar, "can-activate-accel",
4288  G_CALLBACK (can_activate_cb), nullptr);
4289 
4290  gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
4291  g_object_unref (theApp);
4292 }
4293 #endif //MAC_INTEGRATION
4294 
4295 /* Callbacks */
4296 
4309 static gboolean
4310 gnc_main_window_show_summarybar (GncMainWindow *window, GAction *action)
4311 {
4312  GVariant *state;
4313  gboolean visible;
4314 
4315  if (action == nullptr)
4316  action = g_action_map_lookup_action (G_ACTION_MAP(window),
4317  "ViewSummaryAction");
4318  if (action == nullptr)
4319  return TRUE;
4320 
4321  state = g_action_get_state (G_ACTION(action));
4322 
4323  visible = g_variant_get_boolean (state);
4324 
4325  g_variant_unref (state);
4326 
4327  return visible;
4328 }
4329 
4339 static void
4340 gnc_main_window_switch_page (GtkNotebook *notebook,
4341  gpointer *notebook_page,
4342  gint pos,
4343  GncMainWindow *window)
4344 {
4345  GncMainWindowPrivate *priv;
4346  GtkWidget *child;
4347  GncPluginPage *page;
4348  gboolean visible;
4349 
4350  ENTER("Notebook %p, page, %p, index %d, window %p",
4351  notebook, notebook_page, pos, window);
4352  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4353 
4354  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4355  if (priv->current_page != nullptr)
4356  {
4357  page = priv->current_page;
4358  gnc_plugin_page_unselected (page);
4359  }
4360 
4361  child = gtk_notebook_get_nth_page (notebook, pos);
4362  if (child)
4363  {
4364  page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
4365  }
4366  else
4367  {
4368  page = nullptr;
4369  }
4370 
4371  priv->current_page = page;
4372 
4373  if (page != nullptr)
4374  {
4375  /* Update the user interface (e.g. menus and toolbars */
4377  visible = gnc_main_window_show_summarybar (window, nullptr);
4379 
4380  /* Allow page specific actions */
4381  gnc_plugin_page_selected (page);
4382  gnc_window_update_status (GNC_WINDOW(window), page);
4383 
4384  /* Update the page reference info */
4385  priv->usage_order = g_list_remove (priv->usage_order, page);
4386  priv->usage_order = g_list_prepend (priv->usage_order, page);
4387  }
4388 
4389  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4390  multiple_page_actions,
4391  g_list_length (priv->installed_pages) > 1);
4392 
4393  gnc_main_window_update_title(window);
4394 #ifndef MAC_INTEGRATION
4395  gnc_main_window_update_menu_item(window);
4396 #endif
4397  g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4398  LEAVE(" ");
4399 }
4400 
4407 static void
4408 gnc_main_window_page_reordered (GtkNotebook *notebook,
4409  GtkWidget *child,
4410  guint pos,
4411  GncMainWindow *window)
4412 {
4413  GncMainWindowPrivate *priv;
4414  GncPluginPage *page;
4415  GList *old_link;
4416 
4417  ENTER("Notebook %p, child %p, index %d, window %p",
4418  notebook, child, pos, window);
4419  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4420 
4421  if (!child) return;
4422 
4423  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4424 
4425  page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
4426  if (!page) return;
4427 
4428  old_link = g_list_find (priv->installed_pages, page);
4429  if (!old_link) return;
4430 
4431  priv->installed_pages = g_list_delete_link (priv->installed_pages,
4432  old_link);
4433  priv->installed_pages = g_list_insert (priv->installed_pages,
4434  page, pos);
4435 
4436  LEAVE(" ");
4437 }
4438 
4439 static void
4440 gnc_main_window_plugin_added (GncPlugin *manager,
4441  GncPlugin *plugin,
4442  GncMainWindow *window)
4443 {
4444  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4445  g_return_if_fail (GNC_IS_PLUGIN (plugin));
4446 
4447  gnc_plugin_add_to_window (plugin, window, window_type);
4448 }
4449 
4450 static void
4451 gnc_main_window_plugin_removed (GncPlugin *manager,
4452  GncPlugin *plugin,
4453  GncMainWindow *window)
4454 {
4455  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4456  g_return_if_fail (GNC_IS_PLUGIN (plugin));
4457 
4458  gnc_plugin_remove_from_window (plugin, window, window_type);
4459 }
4460 
4461 
4462 /* Command callbacks */
4463 static void
4464 gnc_main_window_cmd_redirect (GSimpleAction *simple,
4465  GVariant *parameter,
4466  gpointer user_data)
4467 {
4468  GncMainWindow *window = (GncMainWindow*)user_data;
4469  GAction *redirect_action;
4470 
4471  PINFO("Redirect action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4472 
4473  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4474 
4475  if (redirect_action)
4476  {
4477  PINFO("Found action %p", redirect_action);
4478  g_action_activate (redirect_action, nullptr);
4479  return;
4480  }
4481 }
4482 
4483 static void
4484 gnc_main_window_cmd_page_setup (GSimpleAction *simple,
4485  GVariant *parameter,
4486  gpointer user_data)
4487 {
4488  GncMainWindow *window = (GncMainWindow*)user_data;
4489  GtkWindow *gtk_window;
4490 
4491  g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4492 
4493  gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
4494  gnc_ui_page_setup(gtk_window);
4495 }
4496 
4497 gboolean
4499 {
4500  QofBook *book = gnc_get_current_book ();
4501  gboolean use_split_action_for_num_before =
4503  gint use_read_only_threshold_before =
4505  gboolean use_split_action_for_num_after;
4506  gint use_read_only_threshold_after;
4507  gboolean return_val = FALSE;
4508  GList *results = nullptr, *iter;
4509 
4510  if (!options) return return_val;
4511 
4512  results = gnc_option_db_commit (options);
4513  for (iter = results; iter; iter = iter->next)
4514  {
4515  GtkWidget *dialog = gtk_message_dialog_new(gnc_ui_get_main_window (nullptr),
4516  (GtkDialogFlags)0,
4517  GTK_MESSAGE_ERROR,
4518  GTK_BUTTONS_OK,
4519  "%s",
4520  (char*)iter->data);
4521  gtk_dialog_run(GTK_DIALOG(dialog));
4522  gtk_widget_destroy(dialog);
4523  g_free (iter->data);
4524  }
4525  g_list_free (results);
4526  qof_book_begin_edit (book);
4527  qof_book_save_options (book, gnc_option_db_save, options, TRUE);
4528  use_split_action_for_num_after =
4530 
4531  // mark cached value as invalid so we get new value
4532  book->cached_num_days_autoreadonly_isvalid = FALSE;
4533  use_read_only_threshold_after = qof_book_get_num_days_autoreadonly (book);
4534 
4535  if (use_split_action_for_num_before != use_split_action_for_num_after)
4536  {
4538  use_split_action_for_num_after);
4539  return_val = TRUE;
4540  }
4541  if (use_read_only_threshold_before != use_read_only_threshold_after)
4542  return_val = TRUE;
4543 
4544  qof_book_commit_edit (book);
4545  return return_val;
4546 }
4547 
4548 static void
4549 gnc_book_options_dialog_apply_cb(GncOptionsDialog * optionwin,
4550  gpointer user_data)
4551 {
4552  auto options{static_cast<GncOptionDB *>(user_data)};
4553 
4554  if (!options) return;
4555 
4557  gnc_gui_refresh_all ();
4558 }
4559 
4560 static void
4561 gnc_book_options_dialog_close_cb(GncOptionsDialog * optionwin,
4562  gpointer user_data)
4563 {
4564  auto options{static_cast<GncOptionDB *>(user_data)};
4565 
4566  delete optionwin;
4567  gnc_option_db_destroy(options);
4568 }
4569 
4573 void
4575 {
4576  gnc_suspend_gui_refresh ();
4577  if (num_action)
4578  {
4579  /* Set a feature flag in the book for use of the split action field as number.
4580  * This will prevent older GnuCash versions that don't support this feature
4581  * from opening this file. */
4582  gnc_features_set_used (gnc_get_current_book(),
4583  GNC_FEATURE_NUM_FIELD_SOURCE);
4584  }
4585  gnc_book_option_num_field_source_change (num_action);
4586  gnc_resume_gui_refresh ();
4587 }
4588 
4589 static gboolean
4590 show_handler (const char *class_name, gint component_id,
4591  gpointer user_data, gpointer iter_data)
4592 {
4593  auto optwin{static_cast<GncOptionsDialog*>(user_data)};
4594 
4595  if (!optwin)
4596  return(FALSE);
4597 
4598  auto widget = optwin->get_widget();
4599  gtk_window_present(GTK_WINDOW(widget));
4600  return(TRUE);
4601 }
4602 
4603 GtkWidget *
4604 gnc_book_options_dialog_cb (gboolean modal, gchar *title, GtkWindow* parent)
4605 {
4606  auto book = gnc_get_current_book ();
4607 
4608  auto options = gnc_option_db_new();
4609  gnc_option_db_book_options(options);
4610  qof_book_load_options (book, gnc_option_db_load, options);
4611  gnc_option_db_clean (options);
4612 
4613  /* Only allow one Book Options dialog if called from file->properties
4614  menu */
4615  if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
4616  show_handler, nullptr))
4617  {
4618  return nullptr;
4619  }
4620  auto optionwin = new GncOptionsDialog (modal,
4621  (title ? title : _( "Book Options")),
4622  DIALOG_BOOK_OPTIONS_CM_CLASS, parent);
4623  optionwin->build_contents(options);
4624  optionwin->set_book_help_cb();
4625  optionwin->set_apply_cb(gnc_book_options_dialog_apply_cb,
4626  (gpointer)options);
4627  optionwin->set_close_cb ( gnc_book_options_dialog_close_cb,
4628  (gpointer)options);
4629  if (modal)
4631  return optionwin->get_widget();
4632 }
4633 
4634 static void
4635 gnc_main_window_cmd_file_properties (GSimpleAction *simple,
4636  GVariant *parameter,
4637  gpointer user_data)
4638 {
4639  GncMainWindow *window = (GncMainWindow*)user_data;
4640  gnc_book_options_dialog_cb (FALSE, nullptr, GTK_WINDOW (window));
4641 }
4642 
4643 static void
4644 gnc_main_window_cmd_file_close (GSimpleAction *simple,
4645  GVariant *parameter,
4646  gpointer user_data)
4647 {
4648  GncMainWindow *window = (GncMainWindow*)user_data;
4649  GncMainWindowPrivate *priv;
4650  GncPluginPage *page;
4651 
4652  g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4653 
4654  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4655  page = priv->current_page;
4657 }
4658 
4659 static void
4660 gnc_main_window_cmd_file_quit (GSimpleAction *simple,
4661  GVariant *parameter,
4662  gpointer user_data)
4663 {
4664  GncMainWindow *window = (GncMainWindow*)user_data;
4666  return;
4667 
4668  gnc_main_window_quit(window);
4669 }
4670 
4671 static void
4672 gnc_main_window_cmd_edit_cut (GSimpleAction *simple,
4673  GVariant *parameter,
4674  gpointer user_data)
4675 {
4676  GncMainWindow *window = (GncMainWindow*)user_data;
4677  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4678  GAction *redirect_action;
4679 
4680  PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4681 
4682  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4683 
4684  if (redirect_action)
4685  {
4686  PINFO("Found action %p", redirect_action);
4687  g_action_activate (redirect_action, nullptr);
4688  return;
4689  }
4690 
4691  if (GTK_IS_EDITABLE(widget))
4692  {
4693  gtk_editable_cut_clipboard (GTK_EDITABLE(widget));
4694  }
4695  else if (GTK_IS_TEXT_VIEW(widget))
4696  {
4697  GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4698  GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4699  GDK_SELECTION_CLIPBOARD);
4700  gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4701 
4702  if (clipboard)
4703  gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
4704  }
4705 }
4706 
4707 static void
4708 gnc_main_window_cmd_edit_copy (GSimpleAction *simple,
4709  GVariant *parameter,
4710  gpointer user_data)
4711 {
4712  GncMainWindow *window = (GncMainWindow*)user_data;
4713  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4714  GAction *redirect_action;
4715 
4716  PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4717 
4718  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4719 
4720  if (redirect_action)
4721  {
4722  PINFO("Found action %p", redirect_action);
4723  g_action_activate (redirect_action, nullptr);
4724  return;
4725  }
4726 
4727  if (GTK_IS_EDITABLE(widget))
4728  {
4729  gtk_editable_copy_clipboard (GTK_EDITABLE(widget));
4730  }
4731  else if (GTK_IS_TEXT_VIEW(widget))
4732  {
4733  GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4734  GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4735  GDK_SELECTION_CLIPBOARD);
4736  if (clipboard)
4737  gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
4738  }
4739 }
4740 
4741 static void
4742 gnc_main_window_cmd_edit_paste (GSimpleAction *simple,
4743  GVariant *parameter,
4744  gpointer user_data)
4745 {
4746  GncMainWindow *window = (GncMainWindow*)user_data;
4747  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4748  GAction *redirect_action;
4749 
4750  PINFO("Paste action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4751 
4752  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4753 
4754  if (redirect_action)
4755  {
4756  PINFO("Found action %p", redirect_action);
4757  g_action_activate (redirect_action, nullptr);
4758  return;
4759  }
4760 
4761  if (GTK_IS_EDITABLE(widget))
4762  {
4763  gtk_editable_paste_clipboard (GTK_EDITABLE(widget));
4764  }
4765  else if (GTK_IS_TEXT_VIEW(widget))
4766  {
4767  auto text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4768  auto clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
4769  GDK_SELECTION_CLIPBOARD);
4770  if (clipboard)
4771  {
4772  auto editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4773  gtk_text_buffer_paste_clipboard (text_buffer, clipboard, nullptr,
4774  editable);
4775  }
4776  }
4777 }
4778 
4779 static void
4780 gnc_main_window_cmd_edit_preferences (GSimpleAction *simple,
4781  GVariant *parameter,
4782  gpointer user_data)
4783 {
4784  GncMainWindow *window = (GncMainWindow*)user_data;
4785  gnc_preferences_dialog (GTK_WINDOW(window));
4786 }
4787 
4788 static void
4789 gnc_main_window_cmd_view_refresh (GSimpleAction *simple,
4790  GVariant *parameter,
4791  gpointer user_data)
4792 {
4793 }
4794 
4795 static void
4796 gnc_main_window_cmd_actions_reset_warnings (GSimpleAction *simple,
4797  GVariant *parameter,
4798  gpointer user_data)
4799 {
4800  GncMainWindow *window = (GncMainWindow*)user_data;
4801  gnc_reset_warnings_dialog(GTK_WINDOW(window));
4802 }
4803 
4804 static void
4805 gnc_main_window_cmd_actions_rename_page (GSimpleAction *simple,
4806  GVariant *parameter,
4807  gpointer user_data)
4808 {
4809  GncMainWindow *window = (GncMainWindow*)user_data;
4810  GncMainWindowPrivate *priv;
4811  GncPluginPage *page;
4812  GtkWidget *label, *entry;
4813 
4814  ENTER(" ");
4815  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4816  page = priv->current_page;
4817  if (!page)
4818  {
4819  LEAVE("No current page");
4820  return;
4821  }
4822 
4823  if (!main_window_find_tab_items(window, page, &label, &entry))
4824  {
4825  LEAVE("can't find required widgets");
4826  return;
4827  }
4828 
4829  gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
4830  gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
4831  gtk_widget_hide(label);
4832  gtk_widget_show(entry);
4833  gtk_widget_grab_focus(entry);
4834  LEAVE("opened for editing");
4835 }
4836 
4837 static void
4838 gnc_main_window_cmd_view_toolbar (GSimpleAction *simple,
4839  GVariant *parameter,
4840  gpointer user_data)
4841 {
4842  GncMainWindow *window = (GncMainWindow*)user_data;
4843  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4844  GVariant *state = g_action_get_state (G_ACTION(simple));
4845 
4846  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4847 
4848  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
4849 
4850  if (!g_variant_get_boolean (state))
4851  gtk_widget_show (priv->toolbar);
4852  else
4853  gtk_widget_hide (priv->toolbar);
4854 
4855  g_variant_unref (state);
4856 }
4857 
4858 static void
4859 gnc_main_window_cmd_view_summary (GSimpleAction *simple,
4860  GVariant *parameter,
4861  gpointer user_data)
4862 {
4863  GncMainWindow *window = (GncMainWindow*)user_data;
4864  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4865  GList *item;
4866  gboolean visible;
4867 
4868  visible = gnc_main_window_show_summarybar (window, G_ACTION(simple));
4869 
4870  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!visible));
4871 
4872  for (item = priv->installed_pages; item; item = g_list_next (item))
4873  {
4874  gnc_plugin_page_show_summarybar (static_cast<GncPluginPage*>(item->data),
4875  !visible);
4876  }
4877 }
4878 
4879 static void
4880 gnc_main_window_cmd_view_statusbar (GSimpleAction *simple,
4881  GVariant *parameter,
4882  gpointer user_data)
4883 {
4884  GncMainWindow *window = (GncMainWindow*)user_data;
4885  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4886  GVariant *state = g_action_get_state (G_ACTION(simple));
4887 
4888  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4889 
4890  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
4891 
4892  if (!g_variant_get_boolean (state))
4893  gtk_widget_show (priv->statusbar);
4894  else
4895  gtk_widget_hide (priv->statusbar);
4896 
4897  g_variant_unref (state);
4898 }
4899 
4900 static void
4901 gnc_main_window_cmd_window_new (GSimpleAction *simple,
4902  GVariant *paramter,
4903  gpointer user_data)
4904 {
4905  GncMainWindow *new_window;
4906 
4907  /* Create the new window */
4908  ENTER(" ");
4909  new_window = gnc_main_window_new ();
4910  gtk_widget_show(GTK_WIDGET(new_window));
4911  LEAVE(" ");
4912 }
4913 
4914 static void
4915 gnc_main_window_cmd_window_move_page (GSimpleAction *simple,
4916  GVariant *paramter,
4917  gpointer user_data)
4918 {
4919  GncMainWindow *window = (GncMainWindow*)user_data;
4920  GncMainWindowPrivate *priv;
4921  GncMainWindow *new_window;
4922  GncPluginPage *page;
4923  GtkNotebook *notebook;
4924  GtkWidget *tab_widget, *menu_widget;
4925 
4926  ENTER("action %p, window %p", simple, window);
4927 
4928  /* Setup */
4929  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4930  page = priv->current_page;
4931  if (!page)
4932  {
4933  LEAVE("invalid page");
4934  return;
4935  }
4936  if (!page->notebook_page)
4937  {
4938  LEAVE("invalid notebook_page");
4939  return;
4940  }
4941 
4942 #ifndef MAC_INTEGRATION
4943  if (gnc_list_length_cmp (active_windows, gnc_main_window_max_number) == 0)
4944  gnc_info_dialog (GTK_WINDOW(window), "%s",
4945  _("The maximum number of window menu entries reached, no more entries will be added."));
4946 #endif /* !MAC_INTEGRATION */
4947 
4948  notebook = GTK_NOTEBOOK (priv->notebook);
4949  tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
4950  menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
4951 
4952  // Remove the page_changed signal callback
4953  gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
4954 
4955  /* Ref the page components, then remove it from its old window */
4956  g_object_ref(page);
4957  g_object_ref(tab_widget);
4958  g_object_ref(menu_widget);
4959  g_object_ref(page->notebook_page);
4960  gnc_main_window_disconnect(window, page);
4961 
4962  /* Create the new window */
4963  new_window = gnc_main_window_new ();
4964  gtk_widget_show(GTK_WIDGET(new_window));
4965 
4966  /* Now add the page to the new window */
4967  gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
4968 
4969  /* Unref the page components now that we're done */
4970  g_object_unref(page->notebook_page);
4971  g_object_unref(menu_widget);
4972  g_object_unref(tab_widget);
4973  g_object_unref(page);
4974 
4975  /* just a little debugging. :-) */
4976  DEBUG("Moved page %p from window %p to new window %p",
4977  page, window, new_window);
4978  DEBUG("Old window current is %p, new window current is %p",
4979  priv->current_page, priv->current_page);
4980 
4981  LEAVE("page moved");
4982 }
4983 
4984 static void
4985 gnc_main_window_cmd_view_tab_position (GSimpleAction *simple,
4986  GVariant *parameter,
4987  gpointer user_data)
4988 {
4989  gint item = g_variant_get_int32 (parameter);
4990 
4991  g_action_change_state (G_ACTION(simple), parameter);
4992 
4993  if (item < 0 || item > 3)
4994  return;
4995 
4996  if (item != 0 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP))
4997  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, FALSE);
4998 
4999  if (item != 1 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
5000  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, FALSE);
5001 
5002  if (item != 2 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
5003  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, FALSE);
5004 
5005  if (item != 3 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
5006  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, FALSE);
5007 
5008  switch (item)
5009  {
5010  case 0:
5011  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, TRUE);
5012  break;
5013 
5014  case 1:
5015  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, TRUE);
5016  break;
5017 
5018  case 2:
5019  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, TRUE);
5020  break;
5021 
5022  case 3:
5023  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, TRUE);
5024  break;
5025  }
5026 
5027 }
5028 
5029 #ifndef MAC_INTEGRATION
5030 static void
5031 gnc_main_window_cmd_window_raise (GSimpleAction *simple,
5032  GVariant *parameter,
5033  gpointer user_data)
5034 {
5035  GncMainWindow *window = (GncMainWindow*)user_data;
5036  GncMainWindow *new_window;
5037  gint item;
5038 
5039  g_return_if_fail (G_IS_SIMPLE_ACTION(simple));
5040  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
5041 
5042  item = g_variant_get_int32 (parameter);
5043 
5044  ENTER("action %p, window %p, item %d", simple, window, item);
5045 
5046  g_action_change_state (G_ACTION(simple), parameter);
5047 
5048  new_window = static_cast<GncMainWindow*>(g_list_nth_data (active_windows, item));
5049  gtk_window_present (GTK_WINDOW(new_window));
5050 
5051  /* revert the change in the radio group
5052  * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
5053  g_idle_add ((GSourceFunc)gnc_main_window_update_radio_button, window);
5054  LEAVE(" ");
5055 }
5056 #endif /* !MAC_INTEGRATION */
5057 
5058 static void
5059 gnc_main_window_cmd_help_tutorial (GSimpleAction *simple,
5060  GVariant *paramter,
5061  gpointer user_data)
5062 {
5063  GncMainWindow *window = (GncMainWindow*)user_data;
5064  gnc_gnome_help (GTK_WINDOW(window), DF_GUIDE, NULL);
5065 }
5066 
5067 static void
5068 gnc_main_window_cmd_help_contents (GSimpleAction *simple,
5069  GVariant *paramter,
5070  gpointer user_data)
5071 {
5072  GncMainWindow *window = (GncMainWindow*)user_data;
5073  gnc_gnome_help (GTK_WINDOW(window), DF_MANUAL, NULL);
5074 }
5075 
5085 static gchar *
5086 get_file (const gchar *partial)
5087 {
5088  gchar *filename, *text = nullptr;
5089  gsize length;
5090 
5091  filename = gnc_filepath_locate_doc_file(partial);
5092  if (filename && g_file_get_contents(filename, &text, &length, nullptr))
5093  {
5094  if (length)
5095  {
5096  g_free(filename);
5097  return text;
5098  }
5099  g_free(text);
5100  }
5101  g_free (filename);
5102  return nullptr;
5103 }
5104 
5105 
5115 static gchar **
5116 get_file_strsplit (const gchar *partial)
5117 {
5118  gchar *text, **lines;
5119 
5120  text = get_file(partial);
5121  if (!text)
5122  return nullptr;
5123 
5124  lines = g_strsplit_set(text, "\r\n", -1);
5125  g_free(text);
5126  return lines;
5127 }
5134 static gboolean
5135 url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
5136 {
5137  gnc_launch_doclink (GTK_WINDOW(dialog), uri);
5138  return TRUE;
5139 }
5140 
5141 static gboolean
5142 link_button_cb (GtkLinkButton *button, gpointer user_data)
5143 {
5144  const gchar *uri = gtk_link_button_get_uri (button);
5145  gnc_launch_doclink (GTK_WINDOW(user_data), uri);
5146  return TRUE;
5147 }
5148 
5149 static void
5150 add_about_paths (GtkDialog *dialog)
5151 {
5152  GtkWidget *page_vbox = gnc_get_dialog_widget_from_id (dialog, "page_vbox");
5153  GtkWidget *grid;
5154  GList *paths;
5155  gint i = 0;
5156 
5157  if (!page_vbox)
5158  {
5159  PWARN("Unable to find AboutDialog 'page_vbox' Widget");
5160  return;
5161  }
5162 
5163  grid = gtk_grid_new ();
5164  paths = gnc_list_all_paths ();
5165 
5166  for (GList *path_node = paths; path_node; path_node = g_list_next (path_node))
5167  {
5168  EnvPaths *ep = (EnvPaths*)path_node->data;
5169 
5170  gchar *env_name = g_strconcat (ep->env_name, ":", NULL);
5171  GtkWidget *label = gtk_label_new (env_name);
5172  const gchar *uri = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, ep->env_path);
5173  gchar *display_uri = gnc_doclink_get_unescaped_just_uri (uri);
5174  GtkWidget *widget = gtk_link_button_new_with_label (uri, display_uri);
5175 
5176  gtk_grid_attach (GTK_GRID(grid), label, 0, i, 1, 1);
5177  gtk_widget_set_halign (label, GTK_ALIGN_END);
5178  gtk_grid_attach (GTK_GRID(grid), widget, 1, i, 1, 1);
5179  gtk_widget_set_halign (widget, GTK_ALIGN_START);
5180  gtk_widget_set_margin_top (widget, 0);
5181  gtk_widget_set_margin_bottom (widget, 0);
5182 
5183  if (ep->modifiable)
5184  {
5185  GtkWidget *mod_lab = gtk_label_new (_("(user modifiable)"));
5186  gtk_grid_attach (GTK_GRID(grid), mod_lab, 2, i, 1, 1);
5187  gtk_widget_show (mod_lab);
5188  }
5189  g_signal_connect (widget, "activate-link",
5190  G_CALLBACK(link_button_cb), dialog);
5191  i++;
5192 
5193  g_free (display_uri);
5194  g_free (env_name);
5195  }
5196  gtk_container_add_with_properties (GTK_CONTAINER(page_vbox), grid,
5197  "position", 1, NULL);
5198  gtk_widget_show_all (grid);
5199  g_list_free_full (paths, g_free);
5200 }
5201 
5204 static void
5205 gnc_main_window_cmd_help_about (GSimpleAction *simple,
5206  GVariant *paramter,
5207  gpointer user_data)
5208 {
5209  GncMainWindow *window = (GncMainWindow*)user_data;
5210  /* Translators: %s will be replaced with the current year */
5211  gchar *copyright = g_strdup_printf(_("Copyright © 1997-%s The GnuCash contributors."),
5212  GNC_VCS_REV_YEAR);
5213  gchar **authors = get_file_strsplit("AUTHORS");
5214  gchar **documenters = get_file_strsplit("DOCUMENTERS");
5215  gchar *license = get_file("LICENSE");
5216  GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
5217  GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
5218  GNC_ICON_APP,
5219  128,
5220  GTK_ICON_LOOKUP_USE_BUILTIN,
5221  nullptr);
5222  gchar *version = g_strdup_printf ("%s: %s\n%s: %s\nFinance::Quote: %s",
5223  _("Version"), gnc_version(),
5224  _("Build ID"), gnc_build_id(),
5227  : "-");
5228  GtkDialog *dialog = GTK_DIALOG (gtk_about_dialog_new ());
5229  g_object_set (G_OBJECT (dialog),
5230  "authors", authors,
5231  "documenters", documenters,
5232  "comments", _("Accounting for personal and small business finance."),
5233  "copyright", copyright,
5234  "license", license,
5235  "logo", logo,
5236  "name", "GnuCash",
5237  /* Translators: the following string will be shown in Help->About->Credits
5238  Enter your name or that of your team and an email contact for feedback.
5239  The string can have multiple rows, so you can also add a list of
5240  contributors. */
5241  "translator-credits", _("translator-credits"),
5242  "version", version,
5243  "website", PACKAGE_URL,
5244  "website-label", _("Visit the GnuCash website."),
5245  nullptr);
5246 
5247  g_free(version);
5248  g_free(copyright);
5249  if (license)
5250  g_free(license);
5251  if (documenters)
5252  g_strfreev(documenters);
5253  if (authors)
5254  g_strfreev(authors);
5255  g_object_unref (logo);
5256  g_signal_connect (dialog, "activate-link",
5257  G_CALLBACK (url_signal_cb), nullptr);
5258 
5259  // Add environment paths
5260  add_about_paths (dialog);
5261 
5262  /* Set dialog to resize. */
5263  gtk_window_set_resizable(GTK_WINDOW (dialog), TRUE);
5264 
5265  gtk_window_set_transient_for (GTK_WINDOW (dialog),
5266  GTK_WINDOW (window));
5267  gtk_dialog_run (dialog);
5268  gtk_widget_destroy (GTK_WIDGET (dialog));
5269 }
5270 
5271 
5272 /************************************************************
5273  * *
5274  ************************************************************/
5275 
5276 void
5278 {
5279  GList *window_iter;
5280 #ifdef MAC_INTEGRATION
5281  auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
5282 #endif
5283  for (window_iter = active_windows; window_iter != nullptr; window_iter = window_iter->next)
5284  {
5285  gtk_widget_show(GTK_WIDGET(window_iter->data));
5286  }
5287 #ifdef MAC_INTEGRATION
5288  g_signal_connect(theApp, "NSApplicationWillTerminate",
5289  G_CALLBACK(gnc_quartz_shutdown), nullptr);
5290  gtkosx_application_ready(theApp);
5291  g_object_unref (theApp);
5292 #endif
5293 }
5294 
5295 GtkWindow *
5296 gnc_ui_get_gtk_window (GtkWidget *widget)
5297 {
5298  GtkWidget *toplevel;
5299 
5300  if (!widget)
5301  return nullptr;
5302 
5303  toplevel = gtk_widget_get_toplevel (widget);
5304  if (toplevel && GTK_IS_WINDOW (toplevel))
5305  return GTK_WINDOW (toplevel);
5306  else
5307  return nullptr;
5308 }
5309 
5310 GtkWindow *
5311 gnc_ui_get_main_window (GtkWidget *widget)
5312 {
5313  GList *window;
5314 
5315  GtkWindow *toplevel = gnc_ui_get_gtk_window (widget);
5316  while (toplevel && !GNC_IS_MAIN_WINDOW (toplevel))
5317  toplevel = gtk_window_get_transient_for(toplevel);
5318 
5319  if (toplevel)
5320  return toplevel;
5321 
5322  for (window = active_windows; window; window = window->next)
5323  if (gtk_window_is_active (GTK_WINDOW (window->data)))
5324  return static_cast<GtkWindow*>(window->data);
5325 
5326  for (window = active_windows; window; window = window->next)
5327  if (gtk_widget_get_mapped (GTK_WIDGET(window->data)))
5328  return static_cast<GtkWindow*>(window->data);
5329 
5330  return nullptr;
5331 }
5332 
5333 
5339 static GtkWindow *
5340 gnc_main_window_get_gtk_window (GncWindow *window)
5341 {
5342  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
5343  return GTK_WINDOW(window);
5344 }
5345 
5346 
5352 static GtkWidget *
5353 gnc_main_window_get_statusbar (GncWindow *window_in)
5354 {
5355  GncMainWindowPrivate *priv;
5356  GncMainWindow *window;
5357 
5358  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
5359 
5360  window = GNC_MAIN_WINDOW(window_in);
5361  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5362  return priv->statusbar;
5363 }
5364 
5365 
5371 static GtkWidget *
5372 gnc_main_window_get_progressbar (GncWindow *window_in)
5373 {
5374  GncMainWindowPrivate *priv;
5375  GncMainWindow *window;
5376 
5377  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
5378 
5379  window = GNC_MAIN_WINDOW(window_in);
5380  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5381  return priv->progressbar;
5382 }
5383 
5384 
5390 static GtkWidget *
5391 gnc_main_window_get_menubar (GncWindow *window)
5392 {
5393  GncMainWindowPrivate *priv;
5394 
5395  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5396 
5397  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5398 
5399  return priv->menubar;
5400 }
5401 
5407 static GtkWidget *
5408 gnc_main_window_get_toolbar (GncWindow *window)
5409 {
5410  GncMainWindowPrivate *priv;
5411 
5412  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5413 
5414  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5415 
5416  return priv->toolbar;
5417 }
5418 
5424 static GMenuModel *
5425 gnc_main_window_get_menubar_model (GncWindow *window)
5426 {
5427  GncMainWindowPrivate *priv;
5428 
5429  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5430 
5431  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5432 
5433  return priv->menubar_model;
5434 }
5435 
5436 
5441 static void
5442 gnc_window_main_window_init (GncWindowIface *iface)
5443 {
5444  iface->get_gtk_window = gnc_main_window_get_gtk_window;
5445  iface->get_statusbar = gnc_main_window_get_statusbar;
5446  iface->get_progressbar = gnc_main_window_get_progressbar;
5447  iface->get_menubar = gnc_main_window_get_menubar;
5448  iface->get_toolbar = gnc_main_window_get_toolbar;
5449  iface->get_menubar_model = gnc_main_window_get_menubar_model;
5450 }
5451 
5452 
5453 /* Set the window where all progressbar updates should occur. This
5454  * is a wrapper around the gnc_window_set_progressbar_window()
5455  * function.
5456  */
5457 void
5459 {
5460  GncWindow *gncwin;
5461  gncwin = GNC_WINDOW(window);
5462  gnc_window_set_progressbar_window(gncwin);
5463 }
5464 
5465 
5478 static void
5479 do_popup_menu (GncPluginPage *page, GdkEventButton *event)
5480 {
5481  GtkBuilder *builder;
5482  GMenuModel *menu_model;
5483  GtkWidget *menu;
5484  const gchar *menu_qualifier;
5485  gchar *popup_menu_name;
5486 
5487  g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
5488 
5489  ENTER("page %p, event %p", page, event);
5490 
5491  builder = gnc_plugin_page_get_builder (page);
5492 
5493  menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
5494 
5495  if (builder == nullptr)
5496  {
5497  LEAVE("no builder");
5498  return;
5499  }
5500 
5501  if (menu_qualifier)
5502  popup_menu_name = g_strconcat ("mainwin-popup-", menu_qualifier, NULL);
5503  else
5504  popup_menu_name = g_strdup ("mainwin-popup");
5505 
5506  menu_model = (GMenuModel *)gtk_builder_get_object (builder, popup_menu_name);
5507 
5508  if (!menu_model)
5509  menu_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-popup");
5510 
5511  menu = gtk_menu_new_from_model (menu_model);
5512 
5513  if (!menu)
5514  {
5515  LEAVE("no menu");
5516  return;
5517  }
5518  gtk_menu_attach_to_widget (GTK_MENU(menu), GTK_WIDGET(page->window), nullptr);
5519  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
5520 
5521  g_free (popup_menu_name);
5522 
5523  LEAVE(" ");
5524 }
5525 
5526 
5540 gboolean
5542  GncPluginPage *page)
5543 {
5544  ENTER("widget %p, page %p", widget, page);
5545  do_popup_menu(page, nullptr);
5546  LEAVE(" ");
5547  return TRUE;
5548 }
5549 
5550 
5551 /* Callback function invoked when the user clicks in the content of
5552  * any Gnucash window. If this was a "right-click" then Gnucash will
5553  * popup the contextual menu.
5554  */
5555 gboolean
5556 gnc_main_window_button_press_cb (GtkWidget *whatever,
5557  GdkEventButton *event,
5558  GncPluginPage *page)
5559 {
5560  g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
5561 
5562  ENTER("widget %p, event %p, page %p", whatever, event, page);
5563  /* Ignore double-clicks and triple-clicks */
5564  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
5565  {
5566  do_popup_menu(page, event);
5567  LEAVE("menu shown");
5568  return TRUE;
5569  }
5570 
5571  LEAVE("other click");
5572  return FALSE;
5573 }
5574 
5575 void
5577  gboolean sensitive)
5578 {
5579  for (auto tmp = active_windows; tmp; tmp = g_list_next(tmp))
5580  {
5581  auto action{gnc_main_window_find_action (static_cast<GncMainWindow*>(tmp->data), action_name)};
5582  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
5583  }
5584 }
5585 
5586 GMenuModel *
5588 {
5589  GncMainWindowPrivate *priv;
5590 
5591  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5592 
5593  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5594 
5595  return priv->menubar_model;
5596 }
5597 
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.
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.
void gnc_menubar_model_remove_items_with_attrib(GMenuModel *menu_model, const gchar *attrib)
Remove GMenuModel entries based on having an attribute value equal to attrib, it does not matter what...
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 gnc_main_window_update_menu_and_toolbar(GncMainWindow *window, GncPluginPage *page, const gchar **ui_updates)
Update the main window menu with the placeholders listed in ui_updates and load the page specific too...
void qof_book_load_options(QofBook *book, GncOptionLoad load_cb, GncOptionDB *odb)
Load a GncOptionsDB from KVP data.
Definition: qofbook.cpp:1275
void gnc_plugin_page_destroy_widget(GncPluginPage *plugin_page)
Destroy the display widget that corresponds to this plugin.
GtkAccelGroup * accel_group
The accelerator group for the window.
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:429
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.
gint index
Index number in active windows list.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
void gnc_main_window_set_vis_of_items_by_action(GncMainWindow *window, const gchar **action_names, gboolean vis)
Show or hide menu and toolbar items based on a NULL terminated list of action names.
void gnc_gobject_tracking_remember(GObject *object, GObjectClass *klass)
Tell gnucash to remember this object in the database.
gboolean gnc_menubar_model_update_item(GMenuModel *menu_model, const gchar *action_name, const gchar *label, const gchar *tooltip)
Update the GMenuModel item based on the action name by copying existing item, removing it and inserti...
functions to query various version related strings that were set at build time.
gtk helper routines.
void gnc_plugin_page_merge_actions(GncPluginPage *page)
Add the actions for a content page to the specified window.
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:57
void gnc_main_window_menu_add_accelerator_keys(GncMainWindow *window)
Scan the main window menu and add accelerator keys to main window accelerator group.
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:423
GMenuModel * gnc_main_window_get_menu_model(GncMainWindow *window)
Return the GMenuModel for the main window menu bar.
gboolean gnc_menubar_model_find_item(GMenuModel *menu_model, GncMenuModelSearch *gsm)
Find a GtkMenu item from the action name.
This data structure allows the passing of the tab width and whether the tab layout is on the left or ...
void gnc_add_accelerator_keys_for_menu(GtkWidget *menu, GtkAccelGroup *accel_group)
Add accelerator keys for menu item widgets.
void gnc_main_window_init_short_names(GncMainWindow *window, GncToolBarShortNames *toolbar_labels)
Update the labels of the toolbar items with short names.
#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:965
GMenuModel * menubar_model
The menubar_model.
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.
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...
GSimpleActionGroup * gnc_plugin_page_get_action_group(GncPluginPage *page)
Retrieve the GSimpleActionGroup object associated with this page.
GtkWidget * toolbar
The toolbar.
void gnc_plugin_add_toolbar_tooltip_callbacks(GtkWidget *toolbar, GtkWidget *statusbar)
This function adds the tooltip callbacks to make the tooltips appear in the status bar...
Definition: gnc-plugin.c:296
GKeyFile helper routines.
GtkBuilder * gnc_plugin_page_get_builder(GncPluginPage *page)
Retrieve the GtkBuilder object associated with this page.
gboolean restoring_pages
Set when restoring plugin pages.
gint pos[2]
Array for window position.
Plugin management functions for the GnuCash UI.
GtkWidget * gnc_main_window_menu_find_menu_item(GncMainWindow *window, const gchar *action_name)
Find the menu item with the given action name for the window specified.
void gnc_plugin_add_to_window(GncPlugin *plugin, GncMainWindow *window, GQuark type)
Add the specified plugin from the specified window.
Definition: gnc-plugin.c:129
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.
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.
void gnc_main_window_merge_actions(GncMainWindow *window, const gchar *group_name, GActionEntry *actions, guint n_actions, const gchar **ui_updates, const gchar *ui_filename, gpointer user_data)
Add a set of actions to the specified window.
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_foreach_page(GncMainWindowPageFunc fn, gpointer user_data)
Iterator function to walk all pages in all windows, calling the specified function for each page...
gboolean gnc_main_window_update_menu_for_action(GncMainWindow *window, const gchar *action_name, const gchar *label, const gchar *tooltip)
Find the GMenuModel item given the action name for the window specified.
void gnc_main_window_unmerge_actions(GncMainWindow *window, const gchar *group_name)
Remove a set of actions from the specified window.
const gchar * gnc_plugin_page_get_menu_qualifier(GncPluginPage *page)
Retrieve the menu qualifier for this page.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#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.
void gnc_plugin_add_menu_tooltip_callbacks(GtkWidget *menubar, GMenuModel *menubar_model, GtkWidget *statusbar)
This function adds the tooltip callbacks to make the tooltips appear in the status bar...
Definition: gnc-plugin.c:264
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:1281
gboolean visible
Whether or not the GAction 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.
GAction * gnc_main_window_find_action_in_group(GncMainWindow *window, const gchar *group_name, const gchar *action_name)
Find the GAction in a specific action group for window.
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:574
#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:386
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...
GAction * gnc_main_window_find_action(GncMainWindow *window, const gchar *action_name)
Find the GAction in the main window.
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 GAction 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:376
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.
GtkWidget * gnc_menubar_model_find_menu_item(GMenuModel *menu_model, GtkWidget *menu, const gchar *action_name)
Find a GtkMenu item from the action name.
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
const gchar * gnc_plugin_page_get_simple_action_group_name(GncPluginPage *page)
Retrieve the simple action group name associated with this plugin page.
GncPluginManager * gnc_plugin_manager_get(void)
Retrieve a pointer to the plugin manager.
void gnc_plugin_set_actions_enabled(GActionMap *action_map, const gchar **action_names, gboolean enable)
This function sets the sensitivity of a GAction in a specific group.
Definition: gnc-plugin.c:246
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:171
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.
GtkWidget * gnc_find_toolbar_item(GtkWidget *toolbar, const gchar *action_name)
Search the toolbar for the tool item based on the action name.
Dialog for handling user preferences.
GtkWidget * menubar
The menubar.
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:378
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.
void gnc_plugin_init_short_names(GtkWidget *toolbar, GncToolBarShortNames *toolbar_labels)
Add "short" labels to existing actions.
Definition: gnc-plugin.c:225
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 GAction.
This data structure is used to describe the requested state of a GAction, and is used to pass data am...
GList * gnc_list_all_paths(void)
Returns a GList* of the environment variables used by GnuCash.
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:499
A structure for defining alternate action names for use in the toolbar.
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.
GList * gnc_option_db_commit(GncOptionDB *odb)
Write all changed ui_item values to their options.
GNC_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_APPLICATION_WINDOW, G_IMPLEMENT_INTERFACE(GNC_TYPE_WINDOW, gnc_window_main_window_init)) static guint main_window_signals[LAST_SIGNAL]
A holding place for all the signals generated by the main window code.
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.
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:270
Utility functions for convert uri in separate components and back.
GncPluginPage * current_page
The currently selected page.
GSimpleActionGroup * gnc_main_window_get_action_group(GncMainWindow *window, const gchar *group_name)
Retrieve a specific set of user interface actions from a window.
gint64 time64
Many systems, including Microsoft Windows and BSD-derived Unixes like Darwin, are retaining the int-3...
Definition: gnc-date.h:87
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.
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_main_window_manual_merge_actions(GncMainWindow *window, const gchar *group_name, GSimpleActionGroup *group)
Manually add a set of actions to 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.
GAction * gnc_plugin_page_get_action(GncPluginPage *page, const gchar *name)
Retrieve a GAction object associated with this page.
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.
GtkWidget * gnc_main_window_toolbar_find_tool_item(GncMainWindow *window, const gchar *action_name)
Find the toolbar item with the given action name for the window specified.
GtkWidget * gnc_plugin_page_create_widget(GncPluginPage *plugin_page)
Create the display widget that corresponds to this plugin.
constexpr auto gnc_main_window_max_number
Max number of windows allowed.
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.