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