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  if (!page_added) // if page not added, increase offset to compensate
796  {
797  offset ++;
798  added_page_offsets = g_slist_append (added_page_offsets,
799  GINT_TO_POINTER(-1));
800  }
801  else
802  added_page_offsets = g_slist_append (added_page_offsets,
803  GINT_TO_POINTER(offset));
804 
805  /* give the page a chance to display */
806  while (gtk_events_pending ())
807  gtk_main_iteration ();
808  }
809  priv->restoring_pages = FALSE;
810  /* Restore page ordering within the notebook. Use +1 notation so the
811  * numbers in the page order match the page sections, at least for
812  * the one window case. */
813  order = g_key_file_get_integer_list (data->key_file, window_group,
814  WINDOW_PAGEORDER, &length, &error);
815  if (error)
816  {
817  g_warning("error reading group %s key %s: %s",
818  window_group, WINDOW_PAGEORDER, error->message);
819  g_error_free(error);
820  error = nullptr;
821  }
822  else if (length != page_count)
823  {
824  g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %" G_GSIZE_FORMAT,
825  window_group, WINDOW_PAGEORDER, length, page_count);
826  }
827  else
828  {
829  /* Dump any list that might exist */
830  g_list_free(priv->usage_order);
831  priv->usage_order = nullptr;
832 
833  gint default_page_position = -1;
834 
835  /* Now rebuild the list from the key file, skipping pages not added */
836  for (i = 0; i < length; i++)
837  {
838  gint zero_based_page_number = order[i] - 1;
839 
840  gint offset = GPOINTER_TO_INT(g_slist_nth_data (added_page_offsets,
841  zero_based_page_number));
842 
843  if (offset == -1)
844  continue;
845 
846  gpointer page = g_list_nth_data (priv->installed_pages,
847  zero_based_page_number - offset);
848 
849  if (default_page_position == -1)
850  default_page_position = zero_based_page_number - offset;
851 
852  if (page)
853  priv->usage_order = g_list_append (priv->usage_order, page);
854  }
855  gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
856  default_page_position);
857 
858  g_signal_emit_by_name (window, "page_changed",
859  g_list_nth_data (priv->usage_order, 0));
860  }
861  if (order)
862  {
863  g_free(order);
864  }
865 
866  LEAVE("window %p", window);
867 cleanup:
868  g_slist_free (added_page_offsets);
869  if (error)
870  g_error_free(error);
871  g_free(window_group);
872  if (window)
873  gtk_widget_show (GTK_WIDGET(window));
874 }
875 
876 void
877 gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
878 {
879  gint i, window_count;
880  GError *error = nullptr;
882 
883  /* We use the same struct for reading and for writing, so we cast
884  away the const. */
885  data.key_file = (GKeyFile *) keyfile;
886  window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
887  WINDOW_COUNT, &error);
888  if (error)
889  {
890  g_warning("error reading group %s key %s: %s",
891  STATE_FILE_TOP, WINDOW_COUNT, error->message);
892  g_error_free(error);
893  LEAVE("can't read count");
894  return;
895  }
896 
897  /* Restore all state information on the open windows. Window
898  numbers in state file are 1-based. GList indices are 0-based. */
899  gnc_set_busy_cursor (nullptr, TRUE);
900  for (i = 0; i < window_count; i++)
901  {
902  data.window_num = i;
903  auto window{static_cast<GncMainWindow*>(g_list_nth_data(active_windows,
904  i))};
905  gnc_main_window_restore_window(window, &data);
906  }
907  gnc_unset_busy_cursor (nullptr);
908 
909  statusbar_notification_lastmodified();
910 }
911 
912 void
914 {
915  GAction *action;
916 
917  /* The default state should be to have an Account Tree page open
918  * in the window. */
919  DEBUG("no saved state file");
920  if (!window)
921  window = static_cast<GncMainWindow*>(g_list_nth_data(active_windows, 0));
922  gtk_widget_show (GTK_WIDGET(window));
923  action = gnc_main_window_find_action_in_group (window,
924  "gnc-plugin-account-tree-actions",
925  "ViewAccountTreeAction");
926  g_action_activate (action, nullptr);
927 }
928 
938 static void
939 gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
940 {
941  gchar *page_group;
942  const gchar *plugin_name, *page_name;
943 
944  ENTER("page %p, data %p (key file %p, window %d, page %d)",
945  page, data, data->key_file, data->window_num, data->page_num);
946  plugin_name = gnc_plugin_page_get_plugin_name(page);
947  page_name = gnc_plugin_page_get_page_name(page);
948  if (!plugin_name || !page_name)
949  {
950  LEAVE("not saving invalid page");
951  return;
952  }
953  page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
954  g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
955  g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
956 
957  gnc_plugin_page_save_page(page, data->key_file, page_group);
958  g_free(page_group);
959  LEAVE(" ");
960 }
961 
962 
971 static void
972 gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
973 {
974  GncMainWindowPrivate *priv;
975  GAction *action;
976  gint i, num_pages, coords[4], *order;
977  gboolean maximized, minimized, visible = true;
978  gchar *window_group;
979 
980  /* Setup */
981  ENTER("window %p, data %p (key file %p, window %d)",
982  window, data, data->key_file, data->window_num);
983  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
984 
985  /* Check for bogus window structures. */
986  num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
987  if (0 == num_pages)
988  {
989  LEAVE("empty window %p", window);
990  return;
991  }
992 
993  /* Save this window's notebook info */
994  window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
995  g_key_file_set_integer(data->key_file, window_group,
996  WINDOW_PAGECOUNT, num_pages);
997  g_key_file_set_integer(data->key_file, window_group,
998  WINDOW_FIRSTPAGE, data->page_num);
999 
1000  /* Save page ordering within the notebook. Use +1 notation so the
1001  * numbers in the page order match the page sections, at least for
1002  * the one window case. */
1003  order = static_cast<int*>(g_malloc(sizeof(gint) * num_pages));
1004  for (i = 0; i < num_pages; i++)
1005  {
1006  gpointer page = g_list_nth_data(priv->usage_order, i);
1007  order[i] = g_list_index(priv->installed_pages, page) + 1;
1008  }
1009  g_key_file_set_integer_list(data->key_file, window_group,
1010  WINDOW_PAGEORDER, order, num_pages);
1011  g_free(order);
1012 
1013  /* Save the window coordinates, etc. */
1014  gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
1015  gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
1016  maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1017  & GDK_WINDOW_STATE_MAXIMIZED) != 0;
1018  minimized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1019  & GDK_WINDOW_STATE_ICONIFIED) != 0;
1020 
1021  if (minimized)
1022  {
1023  gint *pos = priv->pos;
1024  g_key_file_set_integer_list(data->key_file, window_group,
1025  WINDOW_POSITION, &pos[0], 2);
1026  DEBUG("window minimized (%p) position (%d,%d)", window, pos[0], pos[1]);
1027  }
1028  else
1029  g_key_file_set_integer_list(data->key_file, window_group,
1030  WINDOW_POSITION, &coords[0], 2);
1031  g_key_file_set_integer_list(data->key_file, window_group,
1032  WINDOW_GEOMETRY, &coords[2], 2);
1033  g_key_file_set_boolean(data->key_file, window_group,
1034  WINDOW_MAXIMIZED, maximized);
1035  DEBUG("window (%p) position (%d,%d), size %dx%d, %s", window, coords[0], coords[1],
1036  coords[2], coords[3],
1037  maximized ? "maximized" : "not maximized");
1038 
1039  /* Common view menu items */
1040  action = gnc_main_window_find_action (window, "ViewToolbarAction");
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  TOOLBAR_VISIBLE, visible);
1049  action = gnc_main_window_find_action (window, "ViewSummaryAction");
1050  if (action)
1051  {
1052  GVariant *state = g_action_get_state (G_ACTION(action));
1053  visible = g_variant_get_boolean (state);
1054  g_variant_unref (state);
1055  }
1056  g_key_file_set_boolean (data->key_file, window_group,
1057  SUMMARYBAR_VISIBLE, visible);
1058  action = gnc_main_window_find_action (window, "ViewStatusbarAction");
1059  if (action)
1060  {
1061  GVariant *state = g_action_get_state (G_ACTION(action));
1062  visible = g_variant_get_boolean (state);
1063  g_variant_unref (state);
1064  }
1065  g_key_file_set_boolean (data->key_file, window_group,
1066  STATUSBAR_VISIBLE, visible);
1067 
1068  /* Save individual pages in this window */
1069  g_list_foreach (priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
1070 
1071  g_free(window_group);
1072  LEAVE("window %p", window);
1073 }
1074 
1075 void
1077 {
1078  GncMainWindowSaveData data;
1079 
1080  /* Set up the iterator data structures */
1081  data.key_file = keyfile;
1082  data.window_num = 1;
1083  data.page_num = 1;
1084 
1085  g_key_file_set_integer(data.key_file,
1086  STATE_FILE_TOP, WINDOW_COUNT,
1087  g_list_length(active_windows));
1088  /* Dump all state information on the open windows */
1089  g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
1090 }
1091 
1092 
1093 gboolean
1094 gnc_main_window_finish_pending (GncMainWindow *window)
1095 {
1096  GncMainWindowPrivate *priv;
1097  GList *item;
1098 
1099  g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
1100 
1101  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1102  for (item = priv->installed_pages; item; item = g_list_next(item))
1103  {
1104  if (!gnc_plugin_page_finish_pending(static_cast<GncPluginPage*>(item->data)))
1105  {
1106  return FALSE;
1107  }
1108  }
1109  return TRUE;
1110 }
1111 
1112 
1113 gboolean
1115 {
1116  const GList *windows, *item;
1117 
1118  windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
1119  for (item = windows; item; item = g_list_next(item))
1120  {
1121  if (!gnc_main_window_finish_pending(static_cast<GncMainWindow*>(item->data)))
1122  {
1123  return FALSE;
1124  }
1125  }
1126  if (gnc_gui_refresh_suspended ())
1127  {
1128  gnc_warning_dialog (nullptr, "%s", "An operation is still running, wait for it to complete before quitting.");
1129  return FALSE;
1130  }
1131  return TRUE;
1132 }
1133 
1134 
1145 static gboolean
1146 gnc_main_window_page_exists (GncPluginPage *page)
1147 {
1148  GncMainWindowPrivate *priv;
1149  GList *walker;
1150 
1151  for (walker = active_windows; walker; walker = g_list_next(walker))
1152  {
1153  auto window{static_cast<GncMainWindow*>(walker->data)};
1154  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1155  if (g_list_find(priv->installed_pages, page))
1156  {
1157  return TRUE;
1158  }
1159  }
1160  return FALSE;
1161 }
1162 
1163 static gboolean auto_save_countdown (GtkWidget *dialog)
1164 {
1165  GtkWidget *label;
1166  gchar *timeoutstr = nullptr;
1167 
1168  /* Stop count down if user closed the dialog since the last time we were called */
1169  if (!GTK_IS_DIALOG (dialog))
1170  return FALSE; /* remove timer */
1171 
1172  /* Stop count down if count down text can't be updated */
1173  label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
1174  if (!GTK_IS_LABEL (label))
1175  return FALSE; /* remove timer */
1176 
1177  /* Protect against rolling over to MAXUINT */
1178  if (secs_to_save)
1179  --secs_to_save;
1180  DEBUG ("Counting down: %d seconds", secs_to_save);
1181 
1182  timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1183  gtk_label_set_text (GTK_LABEL (label), timeoutstr);
1184  g_free (timeoutstr);
1185 
1186  /* Count down reached 0. Save and close dialog */
1187  if (!secs_to_save)
1188  {
1189  gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1190  return FALSE; /* remove timer */
1191  }
1192 
1193  /* Run another cycle */
1194  return TRUE;
1195 }
1196 
1197 
1207 static gboolean
1208 gnc_main_window_prompt_for_save (GtkWidget *window)
1209 {
1210  QofSession *session;
1211  QofBook *book;
1212  GtkWidget *dialog, *msg_area, *label;
1213  gint response;
1214  const gchar *filename, *tmp;
1215  const gchar *title = _("Save changes to file %s before closing?");
1216  /* This should be the same message as in gnc-file.c */
1217  const gchar *message_hours =
1218  _("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
1219  const gchar *message_days =
1220  _("If you don't save, changes from the past %d days and %d hours will be discarded.");
1221  time64 oldest_change;
1222  gint minutes, hours, days;
1223  guint timer_source = 0;
1224  if (!gnc_current_session_exist())
1225  return FALSE;
1226  session = gnc_get_current_session();
1227  book = qof_session_get_book(session);
1228  if (!qof_book_session_not_saved(book))
1229  return FALSE;
1230  filename = qof_session_get_url(session);
1231  if (!strlen (filename))
1232  filename = _("<unknown>");
1233  if ((tmp = strrchr(filename, '/')) != nullptr)
1234  filename = tmp + 1;
1235 
1236  /* Remove any pending auto-save timeouts */
1237  gnc_autosave_remove_timer(book);
1238 
1239  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1240  GTK_DIALOG_MODAL,
1241  GTK_MESSAGE_WARNING,
1242  GTK_BUTTONS_NONE,
1243  title,
1244  filename);
1245  oldest_change = qof_book_get_session_dirty_time(book);
1246  minutes = (gnc_time (nullptr) - oldest_change) / 60 + 1;
1247  hours = minutes / 60;
1248  minutes = minutes % 60;
1249  days = hours / 24;
1250  hours = hours % 24;
1251  if (days > 0)
1252  {
1253  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1254  message_days, days, hours);
1255  }
1256  else if (hours > 0)
1257  {
1258  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1259  message_hours, hours, minutes);
1260  }
1261  else
1262  {
1263  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1264  ngettext("If you don't save, changes from the past %d minute will be discarded.",
1265  "If you don't save, changes from the past %d minutes will be discarded.",
1266  minutes), minutes);
1267  }
1268  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
1269  _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
1270  _("_Cancel"), GTK_RESPONSE_CANCEL,
1271  _("_Save"), GTK_RESPONSE_APPLY,
1272  nullptr);
1273  gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1274 
1275  /* If requested by the user, add a timeout to the question to save automatically
1276  * if the user doesn't answer after a chosen number of seconds.
1277  */
1278  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
1279  {
1280  gchar *timeoutstr = nullptr;
1281 
1282  secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
1283  timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1284  label = GTK_WIDGET(gtk_label_new (timeoutstr));
1285  g_free (timeoutstr);
1286  gtk_widget_show (label);
1287 
1288  msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
1289  gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
1290  g_object_set (G_OBJECT (label), "xalign", 0.0, nullptr);
1291 
1292  g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
1293  timer_source = g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
1294  }
1295 
1296  response = gtk_dialog_run (GTK_DIALOG (dialog));
1297  if (timer_source)
1298  g_source_remove (timer_source);
1299  gtk_widget_destroy(dialog);
1300 
1301  switch (response)
1302  {
1303  case GTK_RESPONSE_APPLY:
1304  gnc_file_save (GTK_WINDOW (window));
1305  return FALSE;
1306 
1307  case GTK_RESPONSE_CLOSE:
1309  return FALSE;
1310 
1311  default:
1312  return TRUE;
1313  }
1314 }
1315 
1316 
1317 static void
1318 gnc_main_window_add_plugin (gpointer plugin,
1319  gpointer window)
1320 {
1321  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1322  g_return_if_fail (GNC_IS_PLUGIN (plugin));
1323 
1324  ENTER(" ");
1325  gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
1326  GNC_MAIN_WINDOW (window),
1327  window_type);
1328  LEAVE(" ");
1329 }
1330 
1331 static void
1332 gnc_main_window_remove_plugin (gpointer plugin,
1333  gpointer window)
1334 {
1335  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1336  g_return_if_fail (GNC_IS_PLUGIN (plugin));
1337 
1338  ENTER(" ");
1339  gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
1340  GNC_MAIN_WINDOW (window),
1341  window_type);
1342  LEAVE(" ");
1343 }
1344 
1345 
1346 static gboolean
1347 gnc_main_window_timed_quit (gpointer dummy)
1348 {
1349  if (gnc_file_save_in_progress())
1350  return TRUE;
1351 
1352  gnc_shutdown (0);
1353  return FALSE;
1354 }
1355 
1356 static gboolean
1357 gnc_main_window_quit(GncMainWindow *window)
1358 {
1359  QofSession *session;
1360  gboolean needs_save, do_shutdown = TRUE;
1361  if (gnc_current_session_exist())
1362  {
1363  session = gnc_get_current_session();
1364  needs_save =
1366  !gnc_file_save_in_progress();
1367  do_shutdown = !needs_save ||
1368  (needs_save &&
1369  !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
1370  }
1371  if (do_shutdown)
1372  {
1373  GList *w, *next;
1374 
1375  /* This is not a typical list iteration. There is a possibility
1376  * that the window may be removed from the active_windows list so
1377  * we have to cache the 'next' pointer before executing any code
1378  * in the loop. */
1379  for (w = active_windows; w; w = next)
1380  {
1381  GncMainWindowPrivate *priv;
1382  GncMainWindow *window = static_cast<GncMainWindow*>(w->data);
1383 
1384  next = g_list_next (w);
1385 
1386  window->window_quitting = TRUE; //set window_quitting on all windows
1387 
1388  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1389 
1390  // if there are no pages destroy window
1391  if (priv->installed_pages == NULL)
1392  gtk_widget_destroy (GTK_WIDGET(window));
1393  }
1394  /* remove the preference callbacks from the main window */
1395  gnc_main_window_remove_prefs (window);
1396  g_timeout_add(250, gnc_main_window_timed_quit, nullptr);
1397  return TRUE;
1398  }
1399  return FALSE;
1400 }
1401 
1402 gboolean
1403 gnc_main_window_is_quitting (GncMainWindow *window)
1404 {
1405  g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), FALSE);
1406  return window->window_quitting;
1407 }
1408 
1409 static gboolean
1410 gnc_main_window_delete_event (GtkWidget *window,
1411  GdkEvent *event,
1412  gpointer user_data)
1413 {
1414  static gboolean already_dead = FALSE;
1415 
1416  if (already_dead)
1417  return TRUE;
1418 
1419  if (gnc_list_length_cmp (active_windows, 1) > 0)
1420  {
1421  gint response;
1422  GtkWidget *dialog;
1423  gchar *message = _("This window is closing and will not be restored.");
1424 
1425  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
1426  GTK_DIALOG_DESTROY_WITH_PARENT,
1427  GTK_MESSAGE_QUESTION,
1428  GTK_BUTTONS_NONE,
1429  "%s", _("Close Window?"));
1430  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
1431  "%s", message);
1432 
1433  gtk_dialog_add_buttons (GTK_DIALOG(dialog),
1434  _("_Cancel"), GTK_RESPONSE_CANCEL,
1435  _("_OK"), GTK_RESPONSE_YES,
1436  (gchar *)NULL);
1437  gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1438  response = gnc_dialog_run (GTK_DIALOG(dialog), GNC_PREF_WARN_CLOSING_WINDOW_QUESTION);
1439  gtk_widget_destroy (dialog);
1440 
1441  if (response == GTK_RESPONSE_CANCEL)
1442  return TRUE;
1443  }
1444 
1445  if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
1446  {
1447  /* Don't close the window. */
1448  return TRUE;
1449  }
1450 
1451  if (gnc_list_length_cmp (active_windows, 1) > 0)
1452  return FALSE;
1453 
1454  already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
1455  return TRUE;
1456 }
1457 
1458 
1478 static void
1479 gnc_main_window_event_handler (QofInstance *entity, QofEventId event_type,
1480  gpointer user_data, gpointer event_data)
1481 {
1482  GncMainWindow *window;
1483  GncMainWindowPrivate *priv;
1484  GncPluginPage *page;
1485  GList *item, *next;
1486 
1487  /* hard failures */
1488  g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
1489 
1490  /* soft failures */
1491  if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
1492  return;
1493  if (event_type != QOF_EVENT_DESTROY)
1494  return;
1495 
1496  ENTER("entity %p, event %d, window %p, event data %p",
1497  entity, event_type, user_data, event_data);
1498  window = GNC_MAIN_WINDOW(user_data);
1499  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1500 
1501  /* This is not a typical list iteration. We're removing while
1502  * we iterate, so we have to cache the 'next' pointer before
1503  * executing any code in the loop. */
1504  for (item = priv->installed_pages; item; item = next)
1505  {
1506  next = g_list_next(item);
1507  page = GNC_PLUGIN_PAGE(item->data);
1508  if (gnc_plugin_page_has_book (page, (QofBook *)entity))
1510  }
1511 
1512  if (GTK_IS_WIDGET(window) && window->window_quitting)
1513  gtk_widget_destroy (GTK_WIDGET(window));
1514 
1515  LEAVE(" ");
1516 }
1517 
1518 
1535 static gchar *
1536 gnc_main_window_generate_title (GncMainWindow *window)
1537 {
1538  GncMainWindowPrivate *priv;
1539  GncPluginPage *page;
1540  QofBook *book;
1541  gboolean immutable;
1542  gchar *filename = nullptr;
1543  const gchar *uri = nullptr;
1544  const gchar *dirty = "";
1545  const gchar *readonly_text = nullptr;
1546  gchar *readonly;
1547  gchar *title;
1548 
1549  if (gnc_current_session_exist())
1550  {
1551  uri = qof_session_get_url (gnc_get_current_session ());
1552  book = gnc_get_current_book();
1553  if (qof_book_session_not_saved (book))
1554  dirty = "*";
1555  if (qof_book_is_readonly(book))
1556  {
1557  /* Translators: This string is shown in the window title if this
1558  document is, well, read-only. */
1559  readonly_text = _("(read-only)");
1560  }
1561  }
1562  readonly = (readonly_text != nullptr)
1563  ? g_strdup_printf(" %s", readonly_text)
1564  : g_strdup("");
1565 
1566  if (!uri || g_strcmp0 (uri, "") == 0)
1567  filename = g_strdup(_("Unsaved Book"));
1568  else
1569  {
1570  if (gnc_uri_targets_local_fs (uri))
1571  {
1572  /* The filename is a true file.
1573  The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
1574  gchar *path = gnc_uri_get_path ( uri );
1575  filename = g_path_get_basename ( path );
1576  g_free ( path );
1577  }
1578  else
1579  {
1580  /* The filename is composed of database connection parameters.
1581  For this we will show access_method://username@database[:port] */
1582  filename = gnc_uri_normalize_uri (uri, FALSE);
1583  }
1584  }
1585 
1586  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1587  page = priv->current_page;
1588  if (page)
1589  {
1590  /* The Gnome HIG 2.0 recommends the application name not be used. (p16)
1591  but several developers prefer to use it anyway. */
1592  title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
1594  }
1595  else
1596  {
1597  title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
1598  }
1599  /* Update the menus based upon whether this is an "immutable" page. */
1600  immutable = page &&
1601  g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
1602  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
1603  immutable_page_actions,
1604  !immutable);
1605  /* Trigger sensitivity updtates of other actions such as Save/Revert */
1606  g_signal_emit_by_name (window, "page_changed", page);
1607  g_free( filename );
1608  g_free(readonly);
1609 
1610  return title;
1611 }
1612 
1613 
1623 static void
1624 gnc_main_window_update_title (GncMainWindow *window)
1625 {
1626  gchar *title;
1627 
1628  title = gnc_main_window_generate_title(window);
1629  gtk_window_set_title(GTK_WINDOW(window), title);
1630  g_free(title);
1631 }
1632 
1633 static void
1634 gnc_main_window_update_all_titles (void)
1635 {
1636  g_list_foreach(active_windows,
1637  (GFunc)gnc_main_window_update_title,
1638  nullptr);
1639 }
1640 
1641 /* Callback function invoked when the user clicks on a GtkNotebook tab.
1642  *
1643  * This function is needed to make it possible to close a tab
1644  * when it's clicked using the middle mouse button;
1645  * there does not seem to be a way to do this with GtkNotebook natively.
1646  *
1647  * @param widget The event box in the tab, which was clicked.
1648  *
1649  * @param event The event parameter describing where on the screen
1650  * the mouse was pointing when clicked, type of click, modifiers,
1651  * etc.
1652  *
1653  * @param page This is the GncPluginPage corresponding to the tab.
1654  *
1655  * @return Returns TRUE if this was a middle-click, meaning Gnucash
1656  * handled the click.
1657  */
1658 static gboolean
1659 gnc_tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, GncPluginPage *page) {
1660  if (event->type == GDK_BUTTON_PRESS && event->button == 2)
1661  {
1663  return TRUE;
1664  }
1665  return FALSE;
1666 }
1667 
1668 static void
1669 gnc_main_window_book_dirty_cb (QofBook *book,
1670  gboolean dirty,
1671  gpointer user_data)
1672 {
1673  gnc_main_window_update_all_titles();
1674 
1675  /* Auto-save feature */
1676  gnc_autosave_dirty_handler(book, dirty);
1677 }
1678 
1679 static void
1680 gnc_main_window_attach_to_book (QofSession *session)
1681 {
1682  QofBook *book;
1683 
1684  g_return_if_fail(session);
1685 
1686  book = qof_session_get_book(session);
1687  qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, nullptr);
1688  gnc_main_window_update_all_titles();
1689 #ifndef MAC_INTEGRATION
1690  gnc_main_window_update_all_menu_items();
1691 #endif
1692 }
1693 
1694 static guint gnc_statusbar_notification_messageid = 0;
1695 //#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
1696 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1697 /* Removes the statusbar notification again that has been pushed to the
1698  * statusbar by generate_statusbar_lastmodified_message. */
1699 static gboolean statusbar_notification_off(gpointer user_data_unused)
1700 {
1701  GncMainWindow *mainwindow = GNC_MAIN_WINDOW (gnc_ui_get_main_window (nullptr));
1702  //g_warning("statusbar_notification_off\n");
1703  if (gnc_statusbar_notification_messageid == 0)
1704  return FALSE;
1705 
1706  if (mainwindow)
1707  {
1708  GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1709  gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
1710  gnc_statusbar_notification_messageid = 0;
1711  }
1712  else
1713  {
1714  g_warning("oops, no GncMainWindow obtained\n");
1715  }
1716  return FALSE; // should not be called again
1717 }
1718 #endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
1719 
1720 /* Creates a statusbar message stating the last modification time of the opened
1721  * data file. */
1722 static gchar *generate_statusbar_lastmodified_message()
1723 {
1724  gchar *message = nullptr;
1725  const gchar *uri = nullptr;
1726 
1727  if (gnc_current_session_exist())
1728  {
1729  uri = qof_session_get_url (gnc_get_current_session ());
1730  }
1731 
1732  if (!(uri && strlen (uri)))
1733  return nullptr;
1734  else
1735  {
1736  if (gnc_uri_targets_local_fs (uri))
1737  {
1738  /* The filename is a true file. */
1739  gchar *filepath = gnc_uri_get_path ( uri );
1740  gchar *filename = g_path_get_basename ( filepath );
1741  GFile *file = g_file_new_for_uri (uri);
1742  GFileInfo *info = g_file_query_info (file,
1743  G_FILE_ATTRIBUTE_TIME_MODIFIED,
1744  G_FILE_QUERY_INFO_NONE,
1745  NULL, NULL);
1746 
1747  if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
1748  {
1749  // Access the mtime information through stat(2)
1750  struct stat statbuf;
1751  int r = stat(filepath, &statbuf);
1752  if (r == 0)
1753  {
1754  /* Translators: This is the date and time that is shown in
1755  the status bar after opening a file: The date and time of
1756  last modification. The string is a format string using
1757  boost::date_time's format flags, see the boost docs for an
1758  explanation of the modifiers. */
1759  char *time_string = gnc_print_time64(statbuf.st_mtime,
1760  _("Last modified on %a, %b %d, %Y at %I:%M %p"));
1761  //g_warning("got time %ld, str=%s\n", mtime, time_string);
1762  /* Translators: This message appears in the status bar after opening the file. */
1763  message = g_strdup_printf(_("File %s opened. %s"),
1764  filename, time_string);
1765  free(time_string);
1766  }
1767  else
1768  {
1769  g_warning("Unable to read mtime for file %s\n", filepath);
1770  // message is still nullptr
1771  }
1772  }
1773  g_free(filename);
1774  g_free(filepath);
1775  g_object_unref (info);
1776  g_object_unref (file);
1777  }
1778  // If the URI is not a file but a database, we can maybe also show
1779  // something useful, but I have no idea how to obtain this information.
1780  }
1781  return message;
1782 }
1783 
1784 static void
1785 statusbar_notification_lastmodified()
1786 {
1787  // First look up the first GncMainWindow to set the statusbar there
1788  GList *iter;
1789  GtkWidget *widget = nullptr;
1790  for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
1791  iter = g_list_next(iter))
1792  {
1793  widget = static_cast<GtkWidget*>(iter->data);
1794  }
1795  if (widget && GNC_IS_MAIN_WINDOW(widget))
1796  {
1797  // Ok, we found a mainwindow where we can set a statusbar message
1798  GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
1799  GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1800 
1801  gchar *msg = generate_statusbar_lastmodified_message();
1802  if (msg)
1803  {
1804  gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
1805  }
1806  g_free(msg);
1807 
1808 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1809  // Also register a timeout callback to remove that statusbar
1810  // notification again after 10 seconds
1811  g_timeout_add(10 * 1000, statusbar_notification_off, nullptr); // maybe not needed anyway?
1812 #endif
1813  }
1814  else
1815  {
1816  g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
1817  }
1818 }
1819 
1820 
1824 {
1826  gchar *action_name;
1827 
1829  gchar *label;
1830 
1832  gboolean visible;
1833 
1835  gint index;
1836 };
1837 
1838 #ifndef MAC_INTEGRATION
1839 
1852 static void
1853 gnc_main_window_update_one_menu_action (GncMainWindow *window,
1854  struct menu_update *data)
1855 {
1856  GncMainWindowPrivate *priv;
1857  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
1858  GMenuItem *item;
1859  gint pos;
1860 
1861  ENTER("window %p, action %s, label %s, index %d, visible %d", window,
1862  data->action_name, data->label, data->index, data->visible);
1863 
1864  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1865 
1866  gsm->search_action_label = nullptr;
1867  gsm->search_action_name = "WindowsPlaceholder1"; // placeholder
1868  gsm->search_action_target = nullptr;
1869 
1870  if (!gnc_menubar_model_find_item (priv->menubar_model, gsm))
1871  {
1872  LEAVE("Could not find placeholder 'WindowsPlaceholder1' for windows entries");
1873  g_free (gsm);
1874  return;
1875  }
1876 
1877  pos = gsm->index + data->index + 1;
1878 
1879  if (!data->visible)
1880  {
1881  if (pos < g_menu_model_get_n_items (gsm->model))
1882  g_menu_remove (G_MENU(gsm->model), pos);
1883 
1884  g_free (gsm);
1885  LEAVE(" ");
1886  return;
1887  }
1888 
1889  item = g_menu_item_new (data->label, "mainwin.WindowAction");
1890  g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_TARGET, "i", data->index);
1891 
1892  if (pos < g_menu_model_get_n_items (gsm->model))
1893  g_menu_remove (G_MENU(gsm->model), pos);
1894  g_menu_insert_item (G_MENU(gsm->model), pos, item);
1895  g_object_unref (item);
1896 
1897  g_free (gsm);
1898  LEAVE(" ");
1899 }
1900 
1913 static void
1914 gnc_main_window_update_radio_button (GncMainWindow *window)
1915 {
1916  GAction *action;
1917  gsize index;
1918 
1919  ENTER("window %p", window);
1920 
1921  /* Show the new entry in all windows. */
1922  index = g_list_index (active_windows, window);
1923 
1924  if (index >= gnc_main_window_max_number)
1925  {
1926  LEAVE("window %" G_GSIZE_FORMAT ", only %d actions", index, gnc_main_window_max_number);
1927  return;
1928  }
1929 
1930  action = g_action_map_lookup_action (G_ACTION_MAP(window),
1931  "WindowAction");
1932 
1933  /* Block the signal so as not to affect window ordering (top to
1934  * bottom) on the screen */
1935  g_signal_handlers_block_by_func (G_OBJECT(action),
1936  (gpointer)gnc_main_window_cmd_window_raise,
1937  window);
1938 
1939  DEBUG("blocked signal on action %p, window %p", action, window);
1940  g_action_change_state (G_ACTION(action), g_variant_new_int32 (index));
1941 
1942  g_signal_handlers_unblock_by_func (G_OBJECT(action),
1943  (gpointer)gnc_main_window_cmd_window_raise,
1944  window);
1945  LEAVE(" ");
1946 }
1947 
1960 static void
1961 gnc_main_window_update_menu_item (GncMainWindow *window)
1962 {
1963  struct menu_update data;
1964  gchar **strings, *title, *expanded;
1965  gsize index;
1966 
1967  ENTER("window %p", window);
1968 
1969  index = g_list_index (active_windows, window);
1970 
1972  {
1973  LEAVE("skip window %" G_GSIZE_FORMAT " (only %d entries)", index, gnc_main_window_max_number);
1974  return;
1975  }
1976 
1977  /* Figure out the label name. Add the accelerator if possible. */
1978  title = gnc_main_window_generate_title (window);
1979  strings = g_strsplit (title, "_", 0);
1980  g_free (title);
1981  expanded = g_strjoinv ("__", strings);
1983  {
1984  data.label = g_strdup_printf ("_%" G_GSIZE_FORMAT " %s", (index + 1) % 10, expanded);
1985  g_free (expanded);
1986  }
1987  else
1988  {
1989  data.label = expanded;
1990  }
1991  g_strfreev (strings);
1992 
1993  data.visible = TRUE;
1994  data.action_name = g_strdup_printf ("Window%" G_GSIZE_FORMAT "Action", index);
1995  data.index = index;
1996 
1997  g_list_foreach (active_windows,
1998  (GFunc)gnc_main_window_update_one_menu_action,
1999  &data);
2000 
2001  g_free (data.action_name);
2002  g_free (data.label);
2003 
2004  LEAVE(" ");
2005 }
2006 #endif /* !MAC_INTEGRATION */
2007 
2016 #ifndef MAC_INTEGRATION
2017 static void
2018 gnc_main_window_update_all_menu_items (void)
2019 {
2020  struct menu_update data;
2021 
2022  ENTER("");
2023  /* First update the entries for all existing windows */
2024  g_list_foreach (active_windows,
2025  (GFunc)gnc_main_window_update_menu_item,
2026  nullptr);
2027 
2028  g_list_foreach (active_windows,
2029  (GFunc)gnc_main_window_update_radio_button,
2030  nullptr);
2031 
2032  /* Now hide any entries that aren't being used. */
2033  data.visible = FALSE;
2034  // need i to descend from gnc_main_window_max_number
2035  for (gsize i = gnc_main_window_max_number - 1; i > 0 && i >= g_list_length (active_windows); i--)
2036  {
2037  data.index = i;
2038  data.action_name = g_strdup_printf ("Window%dAction", data.index);
2039  data.label = g_strdup_printf ("mywin%" G_GSIZE_FORMAT, i % 10);
2040 
2041  g_list_foreach (active_windows,
2042  (GFunc)gnc_main_window_update_one_menu_action,
2043  &data);
2044 
2045  g_free (data.action_name);
2046  g_free (data.label);
2047  }
2048  LEAVE(" ");
2049 }
2050 #endif /* !MAC_INTEGRATION */
2051 
2063 static void
2064 gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
2065  gpointer user_data)
2066 {
2067  auto new_value{static_cast<gboolean*>(user_data)};
2068  ENTER("page %p, visible %d", page, *new_value);
2069  auto close_button{static_cast<GtkWidget*>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON))};
2070  if (!close_button)
2071  {
2072  LEAVE("no close button");
2073  return;
2074  }
2075 
2076  if (*new_value)
2077  gtk_widget_show (close_button);
2078  else
2079  gtk_widget_hide (close_button);
2080  LEAVE(" ");
2081 }
2082 
2083 
2096 static void
2097 gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
2098 {
2099  gboolean new_value;
2100 
2101  ENTER(" ");
2102  new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
2104  gnc_main_window_update_tab_close_one_page,
2105  &new_value);
2106  LEAVE(" ");
2107 }
2108 
2109 
2118 static void
2119 gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
2120  gpointer user_data)
2121 {
2122  const gchar *color_string;
2123 
2124  ENTER("page %p", page);
2125  color_string = gnc_plugin_page_get_page_color(page);
2126  main_window_update_page_color (page, color_string);
2127  LEAVE(" ");
2128 }
2129 
2130 
2141 static void
2142 gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
2143 {
2144  ENTER(" ");
2145  g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
2146  auto window{static_cast<GncMainWindow*>(user_data)};
2147  auto priv{GNC_MAIN_WINDOW_GET_PRIVATE(window)};
2148  if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
2149  priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2150  gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
2151  LEAVE(" ");
2152 }
2153 
2154 
2158 typedef struct
2159 {
2160  gint tab_width;
2161  gboolean tabs_left_right;
2162 } TabWidth;
2163 
2164 static TabWidth *
2165 populate_tab_width_struct (void)
2166 {
2167  TabWidth *tw;
2168 
2169  tw = g_new0 (TabWidth, 1);
2170  tw->tab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2171  tw->tabs_left_right = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT) ||
2172  gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT);
2173 
2174  return tw;
2175 }
2176 
2198 static void
2199 gnc_main_window_set_tab_ellipsize (GtkWidget *label, gint tab_width, gboolean tab_left_right)
2200 {
2201  const gchar *lab_text = gtk_label_get_text (GTK_LABEL(label));
2202 
2203  if (tab_width != 0)
2204  {
2205  gint text_length = g_utf8_strlen (lab_text, -1);
2206  if (text_length < tab_width)
2207  {
2208  if (tab_left_right) // tabs position is left or right
2209  gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2210  else // tabs position is top or bottom
2211  gtk_label_set_width_chars (GTK_LABEL(label), text_length);
2212 
2213  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2214  }
2215  else
2216  {
2217  gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2218  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
2219  }
2220  }
2221  else
2222  {
2223  gtk_label_set_width_chars (GTK_LABEL(label), 15);
2224  gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2225  }
2226 }
2227 
2228 
2239 static void
2240 gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
2241  gpointer user_data)
2242 {
2243  auto tw{static_cast<TabWidth*>(user_data)};
2244 
2245  ENTER("page %p, tab width %d, tabs on left or right %d",
2246  page, tw->tab_width, tw->tabs_left_right);
2247 
2248  auto label{static_cast<GtkWidget *>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL))};
2249  if (!label)
2250  {
2251  LEAVE("no label");
2252  return;
2253  }
2254  gnc_main_window_set_tab_ellipsize (label, tw->tab_width, tw->tabs_left_right);
2255  LEAVE(" ");
2256 }
2257 
2258 
2271 static void
2272 gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
2273 {
2274  TabWidth *tw;
2275 
2276  ENTER(" ");
2277 
2278  tw = populate_tab_width_struct ();
2279 
2280  gnc_main_window_foreach_page (gnc_main_window_update_tab_width_one_page, tw);
2281  g_free (tw);
2282 
2283  LEAVE(" ");
2284 }
2285 
2286 
2287 /************************************************************
2288  * Tab Label Implementation *
2289  ************************************************************/
2290 static gboolean
2291 main_window_find_tab_items (GncMainWindow *window,
2292  GncPluginPage *page,
2293  GtkWidget **label_p,
2294  GtkWidget **entry_p)
2295 {
2296  GncMainWindowPrivate *priv;
2297  GtkWidget *tab_hbox, *widget, *tab_widget;
2298  GList *children, *tmp;
2299 
2300  ENTER("window %p, page %p, label_p %p, entry_p %p",
2301  window, page, label_p, entry_p);
2302  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2303  *label_p = *entry_p = nullptr;
2304 
2305  if (!page->notebook_page)
2306  {
2307  LEAVE("invalid notebook_page");
2308  return FALSE;
2309  }
2310 
2311  tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2312  page->notebook_page);
2313 
2314  // Walk through children to find the box containing label+entry
2315  tab_hbox = tab_widget;
2316  while (tab_hbox) {
2317  if (g_strcmp0(gtk_widget_get_name(tab_hbox), "tab-content") == 0) {
2318  break;
2319  }
2320  GList* _children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
2321  tab_hbox = _children ? GTK_WIDGET(_children->data) : nullptr;
2322  g_list_free(_children);
2323  }
2324 
2325  if (!GTK_IS_BOX(tab_hbox))
2326  {
2327  PWARN ("Unknown widget for tab label %p", tab_widget);
2328  return FALSE;
2329  }
2330 
2331  children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
2332  for (tmp = children; tmp; tmp = g_list_next(tmp))
2333  {
2334  widget = static_cast<GtkWidget*>(tmp->data);
2335  if (GTK_IS_LABEL(widget))
2336  {
2337  *label_p = widget;
2338  }
2339  else if (GTK_IS_ENTRY(widget))
2340  {
2341  *entry_p = widget;
2342  }
2343  }
2344  g_list_free(children);
2345 
2346  LEAVE("label %p, entry %p", *label_p, *entry_p);
2347  return (*label_p && *entry_p);
2348 }
2349 
2350 static gboolean
2351 main_window_find_tab_widget (GncMainWindow *window,
2352  GncPluginPage *page,
2353  GtkWidget **widget_p)
2354 {
2355  GncMainWindowPrivate *priv;
2356 
2357  ENTER("window %p, page %p, widget %p",
2358  window, page, widget_p);
2359  *widget_p = nullptr;
2360 
2361  if (!page->notebook_page)
2362  {
2363  LEAVE("invalid notebook_page");
2364  return FALSE;
2365  }
2366 
2367  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2368  *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2369  page->notebook_page);
2370 
2371  LEAVE("widget %p", *widget_p);
2372  return TRUE;
2373 }
2374 
2375 void
2377  const gchar *long_name_in)
2378 {
2379  GtkWidget *tab_widget;
2380 
2381  ENTER(" ");
2382 
2383  if ((long_name_in == nullptr) || (*long_name_in == '\0'))
2384  {
2385  LEAVE("no string");
2386  return;
2387  }
2388  gchar *long_name = g_strstrip (g_strdup (long_name_in));
2389  const gchar *old_long_name = gnc_plugin_page_get_page_long_name (page);
2390 
2391  /* Optimization, if the long_name hasn't changed, don't update X. */
2392  if (*long_name == '\0' || strcmp (long_name, old_long_name) == 0)
2393  {
2394  g_free (long_name);
2395  LEAVE("empty string or name unchanged");
2396  return;
2397  }
2398 
2399  gnc_plugin_page_set_page_long_name (page, long_name);
2400 
2401  GncMainWindow *window = GNC_MAIN_WINDOW(page->window);
2402  if (!window)
2403  {
2404  g_free (long_name);
2405  LEAVE("no window widget available");
2406  return;
2407  }
2408 
2409  /* Update the notebook tab tooltip */
2410  if (main_window_find_tab_widget (window, page, &tab_widget))
2411  gtk_widget_set_tooltip_text (tab_widget, long_name);
2412 
2413  g_free (long_name);
2414  LEAVE("");
2415 }
2416 
2417 void
2419  const gchar *name_in)
2420 {
2421  GncMainWindow *window;
2422  GncMainWindowPrivate *priv;
2423  GtkWidget *label, *entry;
2424  gchar *name;
2425  TabWidth *tw;
2426 
2427  ENTER(" ");
2428 
2429  if ((name_in == nullptr) || (*name_in == '\0'))
2430  {
2431  LEAVE("no string");
2432  return;
2433  }
2434  name = g_strstrip(g_strdup(name_in));
2435 
2436  /* Optimization, if the name hasn't changed, don't update X. */
2437  if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
2438  {
2439  g_free(name);
2440  LEAVE("empty string or name unchanged");
2441  return;
2442  }
2443 
2444  /* Update the plugin */
2445  gnc_plugin_page_set_page_name(page, name);
2446 
2447  /* Update the notebook tab */
2448  window = GNC_MAIN_WINDOW(page->window);
2449  if (!window)
2450  {
2451  g_free(name);
2452  LEAVE("no window widget available");
2453  return;
2454  }
2455 
2456  if (main_window_find_tab_items(window, page, &label, &entry))
2457  gtk_label_set_text(GTK_LABEL(label), name);
2458 
2459  /* Adjust the label width for new text */
2460  tw = populate_tab_width_struct ();
2461  gnc_main_window_update_tab_width_one_page (page, tw);
2462  g_free (tw);
2463 
2464  /* Update the notebook menu */
2465  if (page->notebook_page)
2466  {
2467  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2468  label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
2469  page->notebook_page);
2470  gtk_label_set_text(GTK_LABEL(label), name);
2471  }
2472 
2473  /* Force an update of the window title */
2474  gnc_main_window_update_title(window);
2475  g_free(name);
2476  LEAVE("done");
2477 }
2478 
2479 
2480 void
2482  const gchar *color_in)
2483 {
2484  GncMainWindow *window;
2485  GncMainWindowPrivate *priv;
2486  GtkWidget *tab_widget;
2487  GdkRGBA tab_color;
2488  gchar *color_string = nullptr;
2489  gboolean want_color = FALSE;
2490 
2491  ENTER(" ");
2492  if (color_in)
2493  color_string = g_strstrip(g_strdup(color_in));
2494 
2495  if (color_string && *color_string != '\0')
2496  want_color = TRUE;
2497 
2498  /* Update the plugin */
2499  window = GNC_MAIN_WINDOW(page->window);
2500  if (want_color)
2501  gnc_plugin_page_set_page_color(page, color_string);
2502  else
2503  gnc_plugin_page_set_page_color(page, nullptr);
2504 
2505  /* Update the notebook tab */
2506  main_window_find_tab_widget (window, page, &tab_widget);
2507  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2508 
2509  if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
2510  {
2511  GtkCssProvider *provider = gtk_css_provider_new();
2512  GtkStyleContext *stylectxt;
2513  gchar *col_str, *widget_css;
2514 
2515  if (!GTK_IS_EVENT_BOX (tab_widget))
2516  {
2517  GtkWidget *event_box = gtk_event_box_new ();
2518  g_object_ref (tab_widget);
2519  gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2520  page->notebook_page, event_box);
2521  gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
2522  g_object_unref (tab_widget);
2523  tab_widget = event_box;
2524  }
2525 
2526  stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
2527  col_str = gdk_rgba_to_string (&tab_color);
2528  widget_css = g_strconcat ("*{\n background-color:", col_str, ";\n}\n", nullptr);
2529 
2530  gtk_css_provider_load_from_data (provider, widget_css, -1, nullptr);
2531  gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
2532  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2533  g_object_unref (provider);
2534  g_free (col_str);
2535  g_free (widget_css);
2536  }
2537  else
2538  {
2539  if (GTK_IS_EVENT_BOX (tab_widget))
2540  {
2541  GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2542  g_object_ref (tab_hbox);
2543  gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
2544  gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2545  page->notebook_page, tab_hbox);
2546  g_object_unref (tab_hbox);
2547  }
2548  }
2549  g_free(color_string);
2550  LEAVE("done");
2551 }
2552 
2553 
2554 void
2556  gboolean read_only)
2557 {
2558  GncMainWindow *window;
2559  GtkWidget *tab_widget;
2560  GtkWidget *image = NULL;
2561  GList *children;
2562  gchar *image_name = NULL;
2563  const gchar *icon_name;
2564 
2565  ENTER(" ");
2566 
2567  g_return_if_fail (page && page->window);
2568 
2569  if (!GNC_IS_MAIN_WINDOW (page->window))
2570  return;
2571 
2572  window = GNC_MAIN_WINDOW(page->window);
2573 
2574  /* Get the notebook tab widget */
2575  main_window_find_tab_widget (window, page, &tab_widget);
2576 
2577  if (!tab_widget)
2578  {
2579  LEAVE("no tab widget");
2580  return;
2581  }
2582 
2583  if (GTK_IS_EVENT_BOX(tab_widget))
2584  tab_widget = gtk_bin_get_child (GTK_BIN(tab_widget));
2585 
2586  children = gtk_container_get_children (GTK_CONTAINER(tab_widget));
2587  /* For each, walk the list of container children to get image widget */
2588  for (GList *child = children; child; child = g_list_next (child))
2589  {
2590  GtkWidget *widget = static_cast<GtkWidget*>(child->data);
2591  if (GTK_IS_IMAGE(widget))
2592  image = widget;
2593  }
2594  g_list_free (children);
2595 
2596  if (!image)
2597  {
2598  LEAVE("no image to replace");
2599  return;
2600  }
2601 
2602  g_object_get (image, "icon-name", &image_name, NULL);
2603 
2604  if (read_only)
2605  icon_name = "changes-prevent-symbolic";
2606  else
2607  icon_name = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
2608 
2609  if (g_strcmp0 (icon_name, image_name) == 0)
2610  {
2611  LEAVE("page icon the same, no need to replace");
2612  g_free (image_name);
2613  return;
2614  }
2615  gtk_container_remove (GTK_CONTAINER(tab_widget), image);
2616  image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
2617  gtk_widget_show (image);
2618 
2619  gtk_container_add (GTK_CONTAINER(tab_widget), image);
2620  gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
2621  gtk_box_reorder_child (GTK_BOX(tab_widget), image, 0);
2622 
2623  g_free (image_name);
2624  LEAVE("done");
2625 }
2626 
2627 
2628 static void
2629 gnc_main_window_tab_entry_activate (GtkWidget *entry,
2630  GncPluginPage *page)
2631 {
2632  GtkWidget *label, *entry2;
2633 
2634  g_return_if_fail(GTK_IS_ENTRY(entry));
2635  g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
2636 
2637  ENTER("");
2638  if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2639  page, &label, &entry2))
2640  {
2641  LEAVE("can't find required widgets");
2642  return;
2643  }
2644 
2645  main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
2646 
2647  gtk_widget_hide(entry);
2648  gtk_widget_show(label);
2649  LEAVE("");
2650 }
2651 
2652 
2653 static gboolean
2654 gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
2655  GncPluginPage *page)
2656 {
2657  ENTER("");
2658  gnc_main_window_tab_entry_activate(entry, page);
2659  LEAVE("");
2660  return FALSE;
2661 }
2662 
2663 static gboolean
2664 gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
2665  GdkEvent *event,
2666  GncPluginPage *page)
2667 {
2668  ENTER("");
2669  gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
2670  LEAVE("");
2671  return FALSE;
2672 }
2673 
2674 static gboolean
2675 gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
2676  GdkEventKey *event,
2677  GncPluginPage *page)
2678 {
2679  if (event->keyval == GDK_KEY_Escape)
2680  {
2681  GtkWidget *label, *entry2;
2682 
2683  g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
2684  g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
2685 
2686  ENTER("");
2687  if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2688  page, &label, &entry2))
2689  {
2690  LEAVE("can't find required widgets");
2691  return FALSE;
2692  }
2693 
2694  gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
2695  gtk_widget_hide(entry);
2696  gtk_widget_show(label);
2697  LEAVE("");
2698  }
2699  return FALSE;
2700 }
2701 
2702 /************************************************************
2703  * Widget Implementation *
2704  ************************************************************/
2705 
2706 
2707 
2715 static void
2716 gnc_main_window_class_init (GncMainWindowClass *klass)
2717 {
2718  GObjectClass *object_class = G_OBJECT_CLASS (klass);
2719  GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
2720 
2721  window_type = g_quark_from_static_string ("gnc-main-window");
2722 
2723  object_class->constructed = gnc_main_window_constructed;
2724  object_class->finalize = gnc_main_window_finalize;
2725 
2726  /* GtkWidget signals */
2727  gtkwidget_class->destroy = gnc_main_window_destroy;
2728 
2740  main_window_signals[PAGE_ADDED] =
2741  g_signal_new ("page_added",
2742  G_OBJECT_CLASS_TYPE (object_class),
2743  G_SIGNAL_RUN_FIRST,
2744  G_STRUCT_OFFSET (GncMainWindowClass, page_added),
2745  nullptr, nullptr,
2746  g_cclosure_marshal_VOID__OBJECT,
2747  G_TYPE_NONE, 1,
2748  G_TYPE_OBJECT);
2749 
2760  main_window_signals[PAGE_CHANGED] =
2761  g_signal_new ("page_changed",
2762  G_OBJECT_CLASS_TYPE (object_class),
2763  G_SIGNAL_RUN_FIRST,
2764  G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
2765  nullptr, nullptr,
2766  g_cclosure_marshal_VOID__OBJECT,
2767  G_TYPE_NONE, 1,
2768  G_TYPE_OBJECT);
2769 
2778  main_window_signals[MENU_CHANGED] =
2779  g_signal_new ("menu_changed",
2780  G_OBJECT_CLASS_TYPE (object_class),
2781  G_SIGNAL_RUN_FIRST,
2782  G_STRUCT_OFFSET (GncMainWindowClass, menu_changed),
2783  nullptr, nullptr,
2784  g_cclosure_marshal_VOID__OBJECT,
2785  G_TYPE_NONE, 1,
2786  G_TYPE_OBJECT);
2787 
2788  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2789  GNC_PREF_SHOW_CLOSE_BUTTON,
2790  (gpointer)gnc_main_window_update_tab_close,
2791  nullptr);
2792  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2793  GNC_PREF_TAB_WIDTH,
2794  (gpointer)gnc_main_window_update_tab_width,
2795  nullptr);
2796 
2797  gnc_hook_add_dangler(HOOK_BOOK_SAVED,
2798  (GFunc)gnc_main_window_update_all_titles, nullptr, nullptr);
2799  gnc_hook_add_dangler(HOOK_BOOK_OPENED,
2800  (GFunc)gnc_main_window_attach_to_book, nullptr, nullptr);
2801 
2802 }
2803 
2804 
2810 static void
2811 gnc_main_window_init (GncMainWindow *window)
2812 {
2813  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2814 
2815  // Set the name for this dialog so it can be easily manipulated with css
2816  gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-main-window");
2817 
2818  priv->event_handler_id =
2819  qof_event_register_handler(gnc_main_window_event_handler, window);
2820 
2821  priv->restoring_pages = FALSE;
2822 
2823  priv->display_item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nullptr);
2824 
2825  priv->previous_plugin_page_name = nullptr;
2826  priv->previous_menu_qualifier = nullptr;
2827 
2828  priv->accel_group = gtk_accel_group_new ();
2829  gtk_window_add_accel_group (GTK_WINDOW(window), priv->accel_group);
2830 
2831  /* Get the show_color_tabs value preference */
2832  priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2833 
2834  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2835  GNC_PREF_TAB_COLOR,
2836  (gpointer)gnc_main_window_update_tab_color,
2837  window);
2838 
2839  gnc_main_window_setup_window (window);
2840 }
2841 
2848 static void
2849 gnc_main_window_constructed (GObject *obj)
2850 {
2852 
2853  G_OBJECT_CLASS (gnc_main_window_parent_class)->constructed (obj);
2854 }
2855 
2866 static void
2867 gnc_main_window_finalize (GObject *object)
2868 {
2869  g_return_if_fail (object != nullptr);
2870  g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
2871 
2872  if (active_windows == nullptr)
2873  {
2874  /* Oops. User killed last window and we didn't catch it. */
2875  g_idle_add((GSourceFunc)gnc_shutdown, 0);
2876  }
2877 
2879  G_OBJECT_CLASS (gnc_main_window_parent_class)->finalize (object);
2880 }
2881 
2882 
2883 static void
2884 gnc_main_window_remove_prefs (GncMainWindow *window)
2885 {
2886  // remove the registered preference callbacks setup in this file.
2887  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2888  GNC_PREF_TAB_COLOR,
2889  (gpointer)gnc_main_window_update_tab_color,
2890  window);
2891 
2892  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2893  GNC_PREF_SHOW_CLOSE_BUTTON,
2894  (gpointer)gnc_main_window_update_tab_close,
2895  nullptr);
2896 
2897  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2898  GNC_PREF_TAB_WIDTH,
2899  (gpointer)gnc_main_window_update_tab_width,
2900  nullptr);
2901 
2902  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2903  GNC_PREF_TAB_POSITION_TOP,
2904  (gpointer)gnc_main_window_update_tab_position,
2905  window);
2906 
2907  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2908  GNC_PREF_TAB_POSITION_BOTTOM,
2909  (gpointer)gnc_main_window_update_tab_position,
2910  window);
2911 
2912  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2913  GNC_PREF_TAB_POSITION_LEFT,
2914  (gpointer)gnc_main_window_update_tab_position,
2915  window);
2916 
2917  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2918  GNC_PREF_TAB_POSITION_RIGHT,
2919  (gpointer)gnc_main_window_update_tab_position,
2920  window);
2921 
2922  // remove the registered negative color preference callback.
2923  if (gnc_prefs_get_reg_negative_color_pref_id() > 0 && window->window_quitting)
2924  {
2925  gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL,
2927  gnc_prefs_set_reg_negative_color_pref_id (0);
2928  }
2929 
2930  // remove the registered auto_raise_lists preference callback.
2931  if (gnc_prefs_get_reg_auto_raise_lists_id() > 0 && window->window_quitting)
2932  {
2933  gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL_REGISTER,
2935  gnc_prefs_set_reg_auto_raise_lists_id (0);
2936  }
2937 }
2938 
2939 
2940 static void
2941 gnc_main_window_destroy (GtkWidget *widget)
2942 {
2943  GncMainWindow *window;
2944  GncMainWindowPrivate *priv;
2945  GncPluginManager *manager;
2946  GList *plugins;
2947 
2948  g_return_if_fail (widget != nullptr);
2949  g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
2950 
2951  window = GNC_MAIN_WINDOW (widget);
2952 #ifdef MAC_INTEGRATION
2953  auto entry = g_list_find (active_windows, window);
2954  if (entry && (entry->next || entry->prev))
2955  gnc_quartz_set_menu (GNC_MAIN_WINDOW (entry->next ? entry->next->data : entry->prev->data));
2956 #endif
2957  active_windows = g_list_remove (active_windows, window);
2958 
2959  /* Do these things once */
2960  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2961  if (priv->event_handler_id > 0)
2962  {
2963 
2964  /* Close any pages in this window */
2965  while (priv->current_page)
2967 
2968  if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
2969  gnc_window_set_progressbar_window(nullptr);
2970 #ifndef MAC_INTEGRATION
2971  /* Update the "Windows" menu in all other windows */
2972  gnc_main_window_update_all_menu_items();
2973 #endif
2974  /* remove the preference callbacks from the main window */
2975  gnc_main_window_remove_prefs (window);
2976 
2978  priv->event_handler_id = 0;
2979 
2980  g_hash_table_destroy (priv->display_item_hash);
2981 
2982  /* GncPluginManager stuff */
2983  manager = gnc_plugin_manager_get ();
2984  plugins = gnc_plugin_manager_get_plugins (manager);
2985  g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
2986  g_list_free (plugins);
2987  }
2988 
2989  GTK_WIDGET_CLASS (gnc_main_window_parent_class)->destroy (widget);
2990 }
2991 
2992 
2993 static gboolean
2994 gnc_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2995 {
2996  GncMainWindowPrivate *priv;
2997  GdkModifierType modifiers;
2998 
2999  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(widget), FALSE);
3000 
3001  priv = GNC_MAIN_WINDOW_GET_PRIVATE(widget);
3002 
3003  modifiers = gtk_accelerator_get_default_mod_mask ();
3004 
3005  if ((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) // Ctrl+Alt+
3006  {
3007  const gchar *account_key = C_ ("lower case key for short cut to 'Accounts'", "a");
3008  guint account_keyval = gdk_keyval_from_name (account_key);
3009 
3010  if ((account_keyval == event->keyval) || (account_keyval == gdk_keyval_to_lower (event->keyval)))
3011  {
3012  gint page = 0;
3013 
3014  for (GList *item = priv->installed_pages; item; item = g_list_next (item))
3015  {
3016  const gchar *pname = gnc_plugin_page_get_plugin_name (GNC_PLUGIN_PAGE(item->data));
3017 
3018  if (g_strcmp0 (pname, "GncPluginPageAccountTree") == 0)
3019  {
3020  gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook), page);
3021  return TRUE;
3022  }
3023  page++;
3024  }
3025  }
3026  else if ((GDK_KEY_Menu == event->keyval) || (GDK_KEY_space == event->keyval))
3027  {
3028  GList *menu = gtk_menu_get_for_attach_widget (GTK_WIDGET(priv->notebook));
3029 
3030  if (menu)
3031  {
3032  gtk_menu_popup_at_widget (GTK_MENU(menu->data),
3033  GTK_WIDGET(priv->notebook),
3034  GDK_GRAVITY_SOUTH,
3035  GDK_GRAVITY_SOUTH,
3036  NULL);
3037  return TRUE;
3038  }
3039  }
3040  }
3041  return FALSE;
3042 }
3043 
3044 
3045 /* Create a new gnc main window plugin.
3046  */
3047 GncMainWindow *
3049 {
3050  auto window{static_cast<GncMainWindow*>(g_object_new (GNC_TYPE_MAIN_WINDOW, nullptr))};
3051  gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
3052 
3053  auto old_window = gnc_ui_get_main_window (nullptr);
3054  if (old_window)
3055  {
3056  gint width, height;
3057  gtk_window_get_size (old_window, &width, &height);
3058  gtk_window_resize (GTK_WINDOW (window), width, height);
3059  if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
3060  & GDK_WINDOW_STATE_MAXIMIZED) != 0)
3061  {
3062  gtk_window_maximize (GTK_WINDOW (window));
3063  }
3064  }
3065  active_windows = g_list_append (active_windows, window);
3066  gnc_main_window_update_title(window);
3067  window->window_quitting = FALSE;
3068  window->just_plugin_prefs = FALSE;
3069 #ifdef MAC_INTEGRATION
3070  gnc_quartz_set_menu(window);
3071 #else
3072  gnc_main_window_update_all_menu_items();
3073 #endif
3074  gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
3075 
3076  // set up a callback for notebook navigation
3077  g_signal_connect (G_OBJECT(window), "key-press-event",
3078  G_CALLBACK(gnc_main_window_key_press_event),
3079  NULL);
3080 
3081  return window;
3082 }
3083 
3084 /************************************************************
3085  * Utility Functions *
3086  ************************************************************/
3087 
3088 static void
3089 gnc_main_window_engine_commit_error_callback( gpointer data,
3090  QofBackendError errcode )
3091 {
3092  GncMainWindow* window = GNC_MAIN_WINDOW(data);
3093  GtkWidget* dialog;
3094  const gchar *reason = _("Unable to save to database.");
3095  if ( errcode == ERR_BACKEND_READONLY )
3096  reason = _("Unable to save to database: Book is marked read-only.");
3097  dialog = gtk_message_dialog_new( GTK_WINDOW(window),
3098  GTK_DIALOG_DESTROY_WITH_PARENT,
3099  GTK_MESSAGE_ERROR,
3100  GTK_BUTTONS_CLOSE,
3101  "%s",
3102  reason );
3103  gtk_dialog_run(GTK_DIALOG (dialog));
3104  gtk_widget_destroy(dialog);
3105 
3106 }
3107 
3125 static void
3126 gnc_main_window_connect (GncMainWindow *window,
3127  GncPluginPage *page,
3128  GtkWidget *tab_hbox,
3129  GtkWidget *menu_label)
3130 {
3131  GncMainWindowPrivate *priv;
3132  GtkNotebook *notebook;
3133  gint current_position = -1;
3134 
3135  page->window = GTK_WIDGET(window);
3136  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3137  notebook = GTK_NOTEBOOK (priv->notebook);
3138 
3139  if (!priv->restoring_pages
3140  && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_OPEN_ADJACENT))
3141  current_position = g_list_index (priv->installed_pages, priv->current_page) + 1;
3142 
3143  priv->installed_pages = g_list_insert (priv->installed_pages, page, current_position);
3144  priv->usage_order = g_list_prepend (priv->usage_order, page);
3145  gtk_notebook_insert_page_menu (notebook, page->notebook_page,
3146  tab_hbox, menu_label, current_position);
3147  gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
3148  gnc_plugin_page_inserted (page);
3149  if (!priv->restoring_pages)
3150  gtk_notebook_set_current_page (notebook, current_position);
3151 
3152  if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
3153  (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
3154  g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
3155 
3156  g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
3157  G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3158  g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
3159  G_CALLBACK(gnc_main_window_button_press_cb), page);
3160 }
3161 
3162 
3176 static void
3177 gnc_main_window_disconnect (GncMainWindow *window,
3178  GncPluginPage *page)
3179 {
3180  GncMainWindowPrivate *priv;
3181  GtkNotebook *notebook;
3182  GncPluginPage *new_page;
3183  gint page_num;
3184 
3185  /* Disconnect the callbacks */
3186  g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3187  (gpointer)gnc_main_window_popup_menu_cb, page);
3188  g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3189  (gpointer)gnc_main_window_button_press_cb, page);
3190 
3191  // Remove the page_changed signal callback
3192  gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
3193 
3194  /* Disconnect the page and summarybar from the window */
3195  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3196  if (priv->current_page == page)
3197  {
3198  gnc_plugin_page_unselected (page);
3199  priv->current_page = nullptr;
3200  }
3201 
3202  /* Remove it from the list of pages in the window */
3203  priv->installed_pages = g_list_remove (priv->installed_pages, page);
3204  priv->usage_order = g_list_remove (priv->usage_order, page);
3205 
3206  /* Switch to the last recently used page */
3207  notebook = GTK_NOTEBOOK (priv->notebook);
3208  if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
3209  {
3210  new_page = static_cast<GncPluginPage*>(g_list_nth_data (priv->usage_order, 0));
3211  if (new_page)
3212  {
3213  page_num = gtk_notebook_page_num(notebook, new_page->notebook_page);
3214  gtk_notebook_set_current_page(notebook, page_num);
3215  /* This may have caused WebKit to schedule a timer interrupt which it
3216  sometimes forgets to cancel before deleting the object. See
3217  <https://bugs.webkit.org/show_bug.cgi?id=119003>. Get around this
3218  by flushing all events to get rid of the timer interrupt. */
3219  while (gtk_events_pending())
3220  gtk_main_iteration();
3221  }
3222  }
3223 
3224  /* Remove the page from the notebook */
3225  page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3226  gtk_notebook_remove_page (notebook, page_num);
3227 
3228  if ( gtk_notebook_get_current_page(notebook) == -1)
3229  {
3230  /* Need to synthesize a page changed signal when the last
3231  * page is removed. The notebook doesn't generate a signal
3232  * for this, therefore the switch_page code in this file
3233  * never gets called to generate this signal. */
3234  gnc_main_window_switch_page(notebook, nullptr, -1, window);
3235  //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, nullptr);
3236  }
3237 
3238  gnc_plugin_page_removed (page);
3239 
3240  gnc_window_set_status (GNC_WINDOW(window), page, nullptr);
3241 }
3242 
3243 
3244 /************************************************************
3245  * *
3246  ************************************************************/
3247 
3248 
3249 void
3251 {
3252  GncMainWindow *window;
3253  GncMainWindowPrivate *priv;
3254  GtkNotebook *notebook;
3255  gint page_num;
3256 
3257  window = GNC_MAIN_WINDOW (page->window);
3258  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3259  notebook = GTK_NOTEBOOK (priv->notebook);
3260  page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3261  gtk_notebook_set_current_page (notebook, page_num);
3262  gtk_window_present(GTK_WINDOW(window));
3263 }
3264 
3265 
3266 /* Display a data plugin page in a window. If the page already
3267  * exists in any window, then that window will be brought to the
3268  * front and the notebook switch to display the specified page. If
3269  * the page is new then it will be added to the specified window. If
3270  * the window is nullptr, the new page will be added to the first
3271  * window.
3272  */
3273 void
3274 gnc_main_window_open_page (GncMainWindow *window,
3275  GncPluginPage *page)
3276 {
3277  GncMainWindowPrivate *priv;
3278  GtkWidget *tab_container, *tab_clickable_area;
3279  GtkWidget *label, *entry;
3280  const gchar *icon, *text, *color_string, *lab_text;
3281  GtkWidget *image;
3282  GList *tmp;
3283  TabWidth *tw;
3284 
3285  ENTER("window %p, page %p", window, page);
3286  if (window)
3287  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3288  g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
3289  g_return_if_fail (gnc_plugin_page_has_books(page));
3290 
3291  if (gnc_main_window_page_exists(page))
3292  {
3294  return;
3295  }
3296 
3297  /* Does the page want to be in a new window? */
3299  {
3300  /* See if there's a blank window. If so, use that. */
3301  for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
3302  {
3303  window = GNC_MAIN_WINDOW(tmp->data);
3304  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3305  if (priv->installed_pages == nullptr)
3306  {
3307  break;
3308  }
3309  }
3310  if (tmp == nullptr)
3311  window = gnc_main_window_new ();
3312  gtk_widget_show(GTK_WIDGET(window));
3313  }
3314  else if ((window == nullptr) && active_windows)
3315  {
3316  window = static_cast<GncMainWindow*>(active_windows->data);
3317  }
3318 
3319  page->window = GTK_WIDGET(window);
3321  g_object_set_data (G_OBJECT (page->notebook_page),
3322  PLUGIN_PAGE_LABEL, page);
3323 
3324  /*
3325  * The page tab.
3326  * Component structure:
3327  *
3328  * tab_container (GtkBox)
3329  * ├── tab_clickable_area (GtkEventBox)
3330  * │ └── tab_content (GtkBox)
3331  * │ ├── image (GtkImage, optional)
3332  * │ ├── label (GtkLabel)
3333  * │ └── entry (GtkEntry, hidden)
3334  * └── close_button (GtkButton, if not immutable)
3335  */
3336  icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
3337  lab_text = gnc_plugin_page_get_page_name(page);
3338  label = gtk_label_new (lab_text);
3339  g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
3340 
3341  tw = populate_tab_width_struct ();
3342  gnc_main_window_update_tab_width_one_page (page, tw);
3343  g_free (tw);
3344 
3345  gtk_widget_show (label);
3346 
3347  tab_container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
3348 
3350  if (text)
3351  {
3352  gtk_widget_set_tooltip_text(tab_container, text);
3353  }
3354 
3355  if (g_strcmp0 (gnc_plugin_page_get_plugin_name (page), "GncPluginPageAccountTree") == 0)
3356  gtk_widget_set_name (GTK_WIDGET(tab_container), "gnc-id-account-page-tab-box");
3357 
3358  gtk_box_set_homogeneous (GTK_BOX (tab_container), FALSE);
3359  gtk_widget_show (tab_container);
3360 
3361  // Create a custom clickable area for the tab to support middle-clicking
3362  tab_clickable_area = gtk_event_box_new();
3363  gtk_widget_show(tab_clickable_area);
3364  gtk_box_pack_start (GTK_BOX (tab_container), tab_clickable_area, TRUE, TRUE, 0);
3365 
3366  // Create a box for the tab's content
3367  // Give it a name so we can find it later (see main_window_find_tab_items)
3368  GtkWidget *tab_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
3369  gtk_widget_set_name(tab_content, "tab-content");
3370  gtk_container_add(GTK_CONTAINER(tab_clickable_area), tab_content);
3371  gtk_widget_show(tab_content);
3372 
3373  if (icon != nullptr)
3374  {
3375  image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
3376  gtk_widget_show (image);
3377  gtk_box_pack_start (GTK_BOX (tab_content), image, FALSE, FALSE, 0);
3378  gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
3379  gtk_box_pack_start (GTK_BOX (tab_content), label, TRUE, TRUE, 0);
3380  }
3381  else
3382  gtk_box_pack_start (GTK_BOX (tab_content), label, TRUE, TRUE, 0);
3383 
3384  entry = gtk_entry_new();
3385  gtk_widget_hide (entry);
3386  gtk_box_pack_start (GTK_BOX (tab_content), entry, TRUE, TRUE, 0);
3387  g_signal_connect(G_OBJECT(entry), "activate",
3388  G_CALLBACK(gnc_main_window_tab_entry_activate), page);
3389  g_signal_connect(G_OBJECT(entry), "focus-out-event",
3390  G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
3391  page);
3392  g_signal_connect(G_OBJECT(entry), "key-press-event",
3393  G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
3394  page);
3395  g_signal_connect(G_OBJECT(entry), "editing-done",
3396  G_CALLBACK(gnc_main_window_tab_entry_editing_done),
3397  page);
3398 
3399  /* Add close button - Not for immutable pages */
3400  if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
3401  {
3402  GtkWidget *close_image, *close_button;
3403  GtkRequisition requisition;
3404 
3405  close_button = gtk_button_new();
3406  gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
3407  close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
3408  gtk_widget_show(close_image);
3409  gtk_widget_get_preferred_size (close_image, &requisition, nullptr);
3410  gtk_widget_set_size_request(close_button, requisition.width + 4,
3411  requisition.height + 2);
3412  gtk_container_add(GTK_CONTAINER(close_button), close_image);
3413  if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
3414  gtk_widget_show (close_button);
3415  else
3416  gtk_widget_hide (close_button);
3417 
3418  // Custom handler to close on middle-clicks
3419  g_signal_connect(G_OBJECT(tab_clickable_area), "button-press-event",
3420  G_CALLBACK(gnc_tab_clicked_cb), page);
3421 
3422  g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
3423  G_CALLBACK(gnc_main_window_close_page), page);
3424 
3425  gtk_box_pack_start (GTK_BOX (tab_container), close_button, FALSE, FALSE, 0);
3426  gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
3427  g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
3428  }
3429 
3430  /*
3431  * The popup menu
3432  */
3433  label = gtk_label_new (gnc_plugin_page_get_page_name(page));
3434 
3435  /*
3436  * Now install it all in the window.
3437  */
3438  gnc_main_window_connect(window, page, tab_container, label);
3439 
3440  color_string = gnc_plugin_page_get_page_color(page);
3441  main_window_update_page_color (page, color_string);
3442  LEAVE("");
3443 }
3444 
3445 
3446 /* Remove a data plugin page from a window and display the previous
3447  * page. If the page removed was the last page in the window, and
3448  * there is more than one window open, then the entire window will be
3449  * destroyed.
3450  */
3451 void
3453 {
3454  GncMainWindow *window;
3455  GncMainWindowPrivate *priv;
3456 
3457  if (!page || !page->notebook_page)
3458  return;
3459 
3460  if (!gnc_plugin_page_finish_pending(page))
3461  return;
3462 
3463  if (!GNC_IS_MAIN_WINDOW (page->window))
3464  return;
3465 
3466  window = GNC_MAIN_WINDOW (page->window);
3467  if (!window)
3468  {
3469  g_warning("Page is not in a window.");
3470  return;
3471  }
3472 
3473  gnc_main_window_disconnect(window, page);
3475  g_object_unref(page);
3476 
3477  /* If this isn't the last window, go ahead and destroy the window. */
3478  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3479  if (priv->installed_pages == nullptr)
3480  {
3481  if (window->window_quitting)
3482  {
3483  GncPluginManager *manager = gnc_plugin_manager_get ();
3484  GList *plugins = gnc_plugin_manager_get_plugins (manager);
3485 
3486  /* remove only the preference callbacks from the window plugins */
3487  window->just_plugin_prefs = TRUE;
3488  g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
3489  window->just_plugin_prefs = FALSE;
3490  g_list_free (plugins);
3491 
3492  /* remove the preference callbacks from the main window */
3493  gnc_main_window_remove_prefs (window);
3494  }
3495  if (window && (gnc_list_length_cmp (active_windows, 1) > 0))
3496  gtk_widget_destroy (GTK_WIDGET(window));
3497  }
3498 }
3499 
3500 
3501 /* Retrieve a pointer to the page that is currently at the front of
3502  * the specified window. Any plugin that needs to manipulate its
3503  * menus based upon the currently selected menu page should connect
3504  * to the "page_changed" signal on a window. The callback function
3505  * from that signal can then call this function to obtain a pointer
3506  * to the current page.
3507  */
3508 GncPluginPage *
3509 gnc_main_window_get_current_page (GncMainWindow *window)
3510 {
3511  GncMainWindowPrivate *priv;
3512 
3513  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3514  return priv->current_page;
3515 }
3516 
3517 
3518 /* Manually add a set of actions to the specified window. Plugins
3519  * whose user interface is not hard coded (e.g. the menu-additions
3520  * plugin) must create their actions at run time, then use this
3521  * function to install them into the window.
3522  */
3523 void
3525  const gchar *group_name,
3526  GSimpleActionGroup *group)
3527 {
3528  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3529  g_return_if_fail (group_name != nullptr);
3530  g_return_if_fail (G_IS_SIMPLE_ACTION_GROUP(group));
3531 
3532  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
3533  G_ACTION_GROUP(group));
3534 }
3535 
3536 
3537 static void
3538 update_menu_model (GncMainWindow *window, const gchar *ui_filename,
3539  const gchar **ui_updates)
3540 {
3541  GncMainWindowPrivate *priv;
3542  GError *error = nullptr;
3543  gchar *res_name;
3544  GtkBuilder *builder = gtk_builder_new ();
3545  GMenuModel *menu_model_part;
3546  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
3547 
3548  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3549  g_return_if_fail (ui_filename != nullptr);
3550  g_return_if_fail (ui_updates != nullptr);
3551 
3552  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3553 
3554  gtk_builder_set_translation_domain (builder, PROJECT_NAME);
3555  res_name = g_strconcat (GNUCASH_RESOURCE_PREFIX "/", ui_filename, NULL);
3556 
3557  gtk_builder_add_from_resource (builder, res_name, &error);
3558  g_free (res_name);
3559 
3560  if (error)
3561  {
3562  g_critical ("Failed to load, Error %s", error->message);
3563  g_error_free (error);
3564  return;
3565  }
3566 
3567  for (gint i = 0; ui_updates[i]; i++)
3568  {
3569  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
3570 
3571  gsm->search_action_label = nullptr;
3572  gsm->search_action_name = ui_updates[i];
3573  gsm->search_action_target = nullptr;
3574 
3575  if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
3576  g_menu_insert_section (G_MENU(gsm->model), gsm->index, NULL, G_MENU_MODEL(menu_model_part));
3577  else
3578  PERR("Could not find '%s' in menu model", ui_updates[i]);
3579  }
3580  g_free (gsm);
3581  g_object_unref (builder);
3582 }
3583 
3584 
3585 /* Add a set of actions to the specified window. This function
3586  * should not need to be called directly by plugin implementors.
3587  * Correctly assigning values to the GncPluginClass fields during
3588  * plugin initialization will cause this routine to be automatically
3589  * called.
3590  */
3591 void
3592 gnc_main_window_merge_actions (GncMainWindow *window,
3593  const gchar *group_name,
3594  GActionEntry *actions,
3595  guint n_actions,
3596  const gchar **ui_updates,
3597  const gchar *ui_filename,
3598  gpointer user_data)
3599 {
3601  GSimpleActionGroup *simple_action_group;
3602 
3603  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3604  g_return_if_fail (group_name != nullptr);
3605  g_return_if_fail (actions != nullptr);
3606  g_return_if_fail (n_actions > 0);
3607 
3608  data = g_new0 (GncMainWindowActionData, 1);
3609  data->window = window;
3610  data->data = user_data;
3611 
3612  simple_action_group = g_simple_action_group_new ();
3613 
3614  g_action_map_add_action_entries (G_ACTION_MAP(simple_action_group),
3615  actions,
3616  n_actions,
3617  data);
3618 
3619  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
3620  G_ACTION_GROUP(simple_action_group));
3621 
3622  if (ui_filename)
3623  update_menu_model (window, ui_filename, ui_updates);
3624 }
3625 
3626 
3627 /* Remove a set of actions from the specified window. This function
3628  * should not need to be called directly by plugin implementors. It
3629  * will automatically be called when a plugin is removed from a
3630  * window.
3631  */
3632 void
3633 gnc_main_window_unmerge_actions (GncMainWindow *window,
3634  const gchar *group_name)
3635 {
3636  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3637  g_return_if_fail (group_name != nullptr);
3638 
3639  gtk_widget_insert_action_group (GTK_WIDGET(window), group_name, nullptr);
3640 }
3641 
3642 GAction *
3643 gnc_main_window_find_action (GncMainWindow *window, const gchar *action_name)
3644 {
3645  GAction *action = nullptr;
3646 
3647  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3648  g_return_val_if_fail (action_name != nullptr, nullptr);
3649 
3650  action = g_action_map_lookup_action (G_ACTION_MAP(window),
3651  action_name);
3652 
3653  return action;
3654 }
3655 
3656 GAction *
3658  const gchar *group_name,
3659  const gchar *action_name)
3660 {
3661  GAction *action = nullptr;
3662 
3663  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3664  g_return_val_if_fail (group_name != nullptr, nullptr);
3665  g_return_val_if_fail (action_name != nullptr, nullptr);
3666 
3667  auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
3668 
3669  if (action_group)
3670  action = g_action_map_lookup_action (G_ACTION_MAP(action_group), action_name);
3671 
3672  return action;
3673 }
3674 
3675 
3676 /* Retrieve a specific set of user interface actions from a window.
3677  * This function can be used to get an group of action to be
3678  * manipulated when the front page of a window has changed.
3679  */
3680 GSimpleActionGroup *
3681 gnc_main_window_get_action_group (GncMainWindow *window,
3682  const gchar *group_name)
3683 {
3684  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3685  g_return_val_if_fail (group_name != nullptr, nullptr);
3686 
3687  auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
3688  return (GSimpleActionGroup*)action_group;
3689 }
3690 
3691 GtkWidget *
3692 gnc_main_window_toolbar_find_tool_item (GncMainWindow *window, const gchar *action_name)
3693 {
3694  GncMainWindowPrivate *priv;
3695 
3696  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3697  g_return_val_if_fail (action_name != nullptr, nullptr);
3698 
3699  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3700 
3701  return gnc_find_toolbar_item (priv->toolbar, action_name);
3702 }
3703 
3704 GtkWidget *
3705 gnc_main_window_menu_find_menu_item (GncMainWindow *window, const gchar *action_name)
3706 {
3707  GncMainWindowPrivate *priv;
3708  GtkWidget *menu_item;
3709 
3710  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
3711  g_return_val_if_fail (action_name != nullptr, nullptr);
3712 
3713  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3714 
3715  menu_item = GTK_WIDGET(g_hash_table_lookup (priv->display_item_hash, action_name));
3716 
3717  if (!menu_item)
3718  {
3720 
3721  g_hash_table_insert (priv->display_item_hash, g_strdup (action_name), menu_item);
3722  }
3723  return menu_item;
3724 }
3725 
3726 
3727 void
3729 {
3730  GncMainWindowPrivate *priv;
3731 
3732  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3733 
3734  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3735 
3737 }
3738 
3739 
3740 gboolean
3742  const gchar *action_name,
3743  const gchar *label,
3744  const gchar *tooltip)
3745 {
3746  GncMainWindowPrivate *priv;
3747  gboolean found = false;
3748 
3749  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), false);
3750  g_return_val_if_fail (action_name != nullptr, false);
3751  g_return_val_if_fail (label != nullptr, false);
3752 
3753  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3754 
3756  nullptr, _(label), nullptr, _(tooltip));
3757 
3758  // add tooltip redirect call backs
3760  priv->menubar_model,
3761  priv->statusbar);
3762 
3763  return found;
3764 }
3765 
3766 void
3768  const gchar **action_names,
3769  gboolean vis)
3770 {
3771  GncMainWindowPrivate *priv;
3772 
3773  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3774 
3775  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3776 
3777  for (gint i = 0; action_names[i]; i++)
3778  {
3779  GtkWidget *tool_item = gnc_find_toolbar_item (priv->toolbar, action_names[i]);
3780  GtkWidget *menu_item = gnc_main_window_menu_find_menu_item (window, action_names[i]);
3781 
3782  if (menu_item)
3783  {
3784  PINFO("Found menu_item %p with action name '%s', seting vis to '%s'",
3785  menu_item, action_names[i], vis ? "true" : "false");
3786  gtk_widget_set_visible (menu_item, vis);
3787  }
3788  else
3789  PINFO("Did not find menu_item with action name '%s' to set vis '%s'",
3790  action_names[i], vis ? "true" : "false");
3791 
3792  if (tool_item)
3793  {
3794  PINFO("Found tool_item %p with action name '%s', seting vis to '%s'",
3795  tool_item, action_names[i], vis ? "true" : "false");
3796  gtk_widget_set_visible (tool_item, vis);
3797  }
3798  else
3799  PINFO("Did not find tool_item with action name '%s' to set vis '%s'",
3800  action_names[i], vis ? "true" : "false");
3801  }
3802 }
3803 
3804 
3805 void
3806 gnc_main_window_init_short_names (GncMainWindow *window,
3807  GncToolBarShortNames *toolbar_labels)
3808 {
3809  GncMainWindowPrivate *priv;
3810 
3811  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3812  g_return_if_fail (toolbar_labels != nullptr);
3813 
3814  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3815 
3816  gnc_plugin_init_short_names (priv->toolbar, toolbar_labels);
3817 }
3818 
3819 
3820 static void
3821 gnc_main_window_update_toolbar (GncMainWindow *window, GncPluginPage *page,
3822  const gchar *toolbar_qualifier)
3823 {
3824  GncMainWindowPrivate *priv;
3825  GtkBuilder *builder;
3826  GAction *action;
3827 
3828  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3829  g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
3830 
3831  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3832 
3833  builder = gnc_plugin_page_get_builder (page);
3834 
3835  if (builder)
3836  {
3837  gchar *toolbar_name;
3838  gtk_container_remove (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
3839 
3840  if (toolbar_qualifier)
3841  toolbar_name = g_strconcat ("mainwin-toolbar-", toolbar_qualifier, nullptr);
3842  else
3843  toolbar_name = g_strdup ("mainwin-toolbar");
3844 
3845  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, toolbar_name);
3846 
3847  if (!priv->toolbar)
3848  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
3849 
3850  g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
3851  gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
3852  g_free (toolbar_name);
3853  }
3854 
3855  action = gnc_main_window_find_action (window, "ViewToolbarAction");
3856 
3857  // set visibility of toolbar
3858  if (action)
3859  {
3860  GVariant *state = g_action_get_state (G_ACTION(action));
3861  gtk_widget_set_visible (priv->toolbar, g_variant_get_boolean (state));
3862  g_variant_unref (state);
3863  }
3864  // add tooltip redirect call backs
3866 }
3867 
3868 
3869 void
3871  GncPluginPage *page,
3872  const gchar **ui_updates)
3873 {
3874  GncMainWindowPrivate *priv;
3875  const gchar *plugin_page_actions_group_name;
3876  GtkBuilder *builder;
3877  const gchar *menu_qualifier;
3878 
3879  GMenuModel *menu_model_part;
3880 #ifdef MAC_INTEGRATION
3881  auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
3882 #endif
3883  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
3884  g_return_if_fail (page != nullptr);
3885  g_return_if_fail (ui_updates != nullptr);
3886 
3887  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3888 
3889  builder = gnc_plugin_page_get_builder (page);
3890 
3891  if (!builder)
3892  return;
3893 
3894  menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
3895 
3896  plugin_page_actions_group_name = gnc_plugin_page_get_simple_action_group_name (page);
3897 
3898  if (!plugin_page_actions_group_name)
3899  return;
3900 
3901  gtk_widget_insert_action_group (GTK_WIDGET(window), gnc_plugin_page_get_simple_action_group_name (page),
3902  G_ACTION_GROUP(gnc_plugin_page_get_action_group (page)));
3903 
3904  if ((g_strcmp0 (priv->previous_plugin_page_name,
3905  plugin_page_actions_group_name) == 0) &&
3906  (g_strcmp0 (priv->previous_menu_qualifier,
3907  menu_qualifier) == 0))
3908  return;
3909 
3910  priv->previous_plugin_page_name = plugin_page_actions_group_name;
3911  priv->previous_menu_qualifier = menu_qualifier;
3912 
3913  gnc_main_window_update_toolbar (window, page, menu_qualifier);
3914 
3915  // reset hash table and remove added menu items
3916  g_hash_table_remove_all (priv->display_item_hash);
3918  GNC_MENU_ATTRIBUTE_TEMPORARY);
3919 
3920  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
3921  for (gint i = 0; ui_updates[i]; i++)
3922  {
3923  gchar *menu_name;
3924 
3925  if (menu_qualifier)
3926  menu_name = g_strconcat (ui_updates[i], "-", menu_qualifier, nullptr);
3927  else
3928  menu_name = g_strdup (ui_updates[i]);
3929 
3930  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, menu_name);
3931 
3932  if (!menu_model_part)
3933  menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
3934 
3935  gsm->search_action_label = nullptr;
3936  gsm->search_action_name = ui_updates[i];
3937  gsm->search_action_target = nullptr;
3938 
3939  if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
3940  g_menu_insert_section (G_MENU(gsm->model), gsm->index,
3941  nullptr, G_MENU_MODEL(menu_model_part));
3942  else
3943  PERR("Could not find '%s' in menu model", ui_updates[i]);
3944 
3945  g_free (menu_name);
3946  }
3947 
3948  // add tooltip redirect call backs
3950 
3951  // need to add the accelerator keys
3953 #ifdef MAC_INTEGRATION
3954  gtkosx_application_sync_menubar (theApp);
3955  g_object_unref (theApp);
3956 #endif
3957  // need to signal menu has been changed
3958  g_signal_emit_by_name (window, "menu_changed", page);
3959 
3960  g_free (gsm);
3961 }
3962 
3963 
3964 static void
3965 gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
3966 {
3967  GncMainWindow *window;
3968  GtkPositionType position = GTK_POS_TOP;
3969  gint item = 0;
3970  GncMainWindowPrivate *priv;
3971  GAction *action;
3972 
3973  g_return_if_fail (GNC_IS_MAIN_WINDOW(user_data));
3974 
3975  window = GNC_MAIN_WINDOW(user_data);
3976 
3977  ENTER ("window %p", window);
3978 
3979  /* Ignore notification of the preference that is being set to false when
3980  * the choice of tab position changes. */
3981  if (pref && !gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, pref))
3982  return;
3983 
3984  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
3985  {
3986  position = GTK_POS_BOTTOM;
3987  item = 1;
3988  }
3989  else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
3990  {
3991  position = GTK_POS_LEFT;
3992  item = 2;
3993  }
3994  else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
3995  {
3996  position = GTK_POS_RIGHT;
3997  item = 3;
3998  }
3999 
4000  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4001  gtk_notebook_set_tab_pos (GTK_NOTEBOOK(priv->notebook), position);
4002 
4003  action = g_action_map_lookup_action (G_ACTION_MAP(window),
4004  "ViewTabPositionAction");
4005 
4006  g_signal_handlers_block_by_func (G_OBJECT(action),
4007  (gpointer)gnc_main_window_cmd_view_tab_position,
4008  window);
4009  g_action_change_state (G_ACTION(action), g_variant_new_int32 (item));
4010  g_signal_handlers_unblock_by_func (G_OBJECT(action),
4011  (gpointer)gnc_main_window_cmd_view_tab_position,
4012  window);
4013 
4014  gnc_main_window_update_tab_width (nullptr, (char*)GNC_PREF_TAB_WIDTH, nullptr);
4015 
4016  LEAVE ("");
4017 }
4018 
4019 /*
4020  * Based on code from Epiphany (src/ephy-window.c)
4021  */
4022 static void
4023 gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
4024 {
4025  GncMainWindowPrivate *priv;
4026  GncPluginPage *page;
4027  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
4028  GAction *action;
4029  gboolean can_copy = false, can_cut = false, can_paste = false;
4030 
4031  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4032  page = priv->current_page;
4033 
4034  if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
4035  {
4036  (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
4037  return;
4038  }
4039 
4040  if (GTK_IS_EDITABLE (widget))
4041  {
4042  gboolean has_selection;
4043 
4044  has_selection = gtk_editable_get_selection_bounds
4045  (GTK_EDITABLE (widget), nullptr, nullptr);
4046 
4047  can_copy = has_selection;
4048  can_cut = has_selection;
4049  can_paste = TRUE;
4050  }
4051  else if (GTK_IS_TEXT_VIEW (widget))
4052  {
4053  gboolean has_selection;
4054  GtkTextBuffer *text_buffer;
4055 
4056  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4057  has_selection = gtk_text_buffer_get_selection_bounds
4058  (text_buffer, nullptr, nullptr);
4059 
4060  can_copy = has_selection;
4061  can_cut = has_selection;
4062  can_paste = TRUE;
4063  }
4064  else
4065  {
4066 #ifdef ORIGINAL_EPIPHANY_CODE
4067  /* For now we assume all actions are possible */
4068  can_copy = can_cut = can_paste = true;
4069 #else
4070  /* If its not a GtkEditable, we don't know what to do
4071  * with it. */
4072  can_copy = can_cut = can_paste = false;
4073 #endif
4074  }
4075  action = gnc_main_window_find_action (window, "EditCopyAction");
4076  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_copy);
4077 
4078  action = gnc_main_window_find_action (window, "EditCutAction");
4079  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_cut);
4080 
4081  action = gnc_main_window_find_action (window, "EditPasteAction");
4082  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_paste);
4083 }
4084 
4085 static void
4086 gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
4087 {
4088  GAction *action;
4089 
4090  action = gnc_main_window_find_action (window, "EditCopyAction");
4091  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
4092 
4093  action = gnc_main_window_find_action (window, "EditCutAction");
4094  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
4095 
4096  action = gnc_main_window_find_action (window, "EditPasteAction");
4097  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
4098 
4099 }
4100 
4101 static void
4102 gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
4103  GncMainWindow *window)
4104 {
4105  gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
4106 }
4107 
4108 static void
4109 gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
4110  GncMainWindow *window)
4111 {
4112  gnc_main_window_enable_edit_actions_sensitivity (window);
4113 }
4114 
4115 static void
4116 gnc_main_window_init_menu_updaters (GncMainWindow *window)
4117 {
4118  GtkWidget *edit_menu_item, *edit_menu;
4119 
4120  edit_menu_item = gnc_main_window_menu_find_menu_item (window, "EditAction");
4121 
4122  edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM(edit_menu_item));
4123 
4124  g_signal_connect (edit_menu, "show",
4125  G_CALLBACK(gnc_main_window_edit_menu_show_cb), window);
4126  g_signal_connect (edit_menu, "hide",
4127  G_CALLBACK(gnc_main_window_edit_menu_hide_cb), window);
4128 }
4129 
4130 /* This is used to prevent the tab having focus */
4131 static gboolean
4132 gnc_main_window_page_focus_in (GtkWidget *widget, GdkEvent *event,
4133  gpointer user_data)
4134 {
4135  auto window{static_cast<GncMainWindow *>(user_data)};
4137 
4138  g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4139  return FALSE;
4140 }
4141 
4142 static GAction *
4143 gnc_main_window_get_redirect (GncMainWindow *window, const gchar *action_name)
4144 {
4145  GncMainWindowPrivate *priv;
4146  GAction *action = nullptr;
4147  const gchar *group_name;
4148 
4149  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
4150  g_return_val_if_fail (action_name != nullptr, nullptr);
4151 
4152  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4153 
4155 
4156  PINFO("action anme is '%s', group_name is '%s'", action_name, group_name);
4157 
4158  if (group_name)
4159  {
4160  action = gnc_main_window_find_action_in_group (window, group_name, action_name);
4161 
4162  if (!action)
4164  }
4165 
4166  PINFO("Redirect action is %p for action anme '%s' and group_name '%s'",
4167  action, action_name, group_name);
4168  return action;
4169 }
4170 
4171 static void
4172 main_window_realize_cb (GtkWidget *widget, gpointer user_data)
4173 {
4174  GncMainWindow *window = (GncMainWindow*)user_data;
4175  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4176 
4177  gnc_add_accelerator_keys_for_menu (GTK_WIDGET(priv->menubar), priv->menubar_model, priv->accel_group);
4178 
4179  /* need to signal menu has been changed, this will call the
4180  business function 'bind_extra_toolbuttons_visibility' */
4181  g_signal_emit_by_name (window, "menu_changed", nullptr);
4182 }
4183 
4184 static void
4185 gnc_main_window_setup_window (GncMainWindow *window)
4186 {
4187  GncMainWindowPrivate *priv;
4188  GtkWidget *main_vbox;
4189  GtkBuilder *builder;
4190  GncPluginManager *manager;
4191  GList *plugins;
4192  GError *error = nullptr;
4193  GAction *action;
4194 
4195  ENTER(" ");
4196 
4197  /* Catch window manager delete signal */
4198  g_signal_connect (G_OBJECT (window), "delete-event",
4199  G_CALLBACK (gnc_main_window_delete_event), window);
4200 
4201  /* Create widgets and add them to the window */
4202  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
4203  gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
4204  gtk_widget_show (main_vbox);
4205  gtk_container_add (GTK_CONTAINER (window), main_vbox);
4206 
4207  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4208  priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
4209  gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
4210  gtk_widget_show (priv->menu_dock);
4211  gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
4212  FALSE, TRUE, 0);
4213 
4214  priv->notebook = gtk_notebook_new ();
4215  g_object_set(G_OBJECT(priv->notebook),
4216  "scrollable", TRUE,
4217  "enable-popup", TRUE,
4218  (char *)nullptr);
4219  gtk_widget_show (priv->notebook);
4220  g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
4221  G_CALLBACK (gnc_main_window_switch_page), window);
4222  g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
4223  G_CALLBACK (gnc_main_window_page_reordered), window);
4224  g_signal_connect (G_OBJECT (priv->notebook), "focus-in-event",
4225  G_CALLBACK (gnc_main_window_page_focus_in), window);
4226  gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
4227  TRUE, TRUE, 0);
4228 
4229  priv->statusbar = gtk_statusbar_new ();
4230  gtk_widget_show (priv->statusbar);
4231  gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
4232  FALSE, TRUE, 0);
4233 
4234  priv->progressbar = gtk_progress_bar_new ();
4235  gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(priv->progressbar), TRUE);
4236  gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
4237  gtk_widget_show (priv->progressbar);
4238  gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
4239  FALSE, TRUE, 0);
4240  gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
4241  0.01);
4242 
4243  builder = gtk_builder_new ();
4244  gtk_builder_set_translation_domain (builder, PROJECT_NAME);
4245  gtk_builder_add_from_resource (builder, GNUCASH_RESOURCE_PREFIX "/gnc-main-window.ui", &error);
4246 
4247  if (error)
4248  {
4249  g_critical ("Failed to load, Error %s", error->message);
4250  g_error_free (error);
4251  return;
4252  }
4253 
4254  g_action_map_add_action_entries (G_ACTION_MAP(window),
4255  gnc_menu_actions,
4256  gnc_menu_n_actions,
4257  window);
4258 
4259  priv->menubar_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-menu");
4260  priv->menubar = gtk_menu_bar_new_from_model (priv->menubar_model);
4261  gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->menubar);
4262  gtk_widget_show (GTK_WIDGET(priv->menubar));
4263 
4264  priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
4265  g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
4266  gtk_container_add (GTK_CONTAINER(priv->menu_dock), GTK_WIDGET(priv->toolbar));
4267  gtk_widget_show (GTK_WIDGET(priv->toolbar));
4268 
4269  g_object_unref (builder);
4270 
4271  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4272  initially_insensitive_actions,
4273  FALSE);
4274  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4275  always_insensitive_actions,
4276  FALSE);
4277 
4278  gnc_main_window_set_vis_of_items_by_action (window, always_hidden_actions,
4279  false);
4280 
4281  gtk_widget_insert_action_group (GTK_WIDGET(window), "mainwin",
4282  G_ACTION_GROUP(window));
4283 
4284  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4285  GNC_PREF_TAB_POSITION_TOP,
4286  (gpointer)gnc_main_window_update_tab_position,
4287  window);
4288  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4289  GNC_PREF_TAB_POSITION_BOTTOM,
4290  (gpointer)gnc_main_window_update_tab_position,
4291  window);
4292  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4293  GNC_PREF_TAB_POSITION_LEFT,
4294  (gpointer)gnc_main_window_update_tab_position,
4295  window);
4296  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
4297  GNC_PREF_TAB_POSITION_RIGHT,
4298  (gpointer)gnc_main_window_update_tab_position,
4299  window);
4300  gnc_main_window_update_tab_position (nullptr, nullptr, window);
4301 
4302  gnc_main_window_init_menu_updaters (window);
4303 
4304  /* Disable the Transaction menu */
4305  action = gnc_main_window_find_action (window, "TransactionAction");
4306  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4307  /* Disable the Schedule menu */
4308  action = gnc_main_window_find_action (window, "ScheduledAction");
4309  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4310 
4311  /* Now update the "eXtensions" menu */
4312  if (!gnc_prefs_is_extra_enabled())
4313  {
4314  action = gnc_main_window_find_action (window, "ExtensionsAction");
4315  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
4316  }
4317 
4318  /* GncPluginManager stuff */
4319  manager = gnc_plugin_manager_get ();
4320  plugins = gnc_plugin_manager_get_plugins (manager);
4321  g_list_foreach (plugins, gnc_main_window_add_plugin, window);
4322  g_list_free (plugins);
4323 
4324  g_signal_connect (G_OBJECT (manager), "plugin-added",
4325  G_CALLBACK (gnc_main_window_plugin_added), window);
4326  g_signal_connect (G_OBJECT (manager), "plugin-removed",
4327  G_CALLBACK (gnc_main_window_plugin_removed), window);
4328 
4329  // need to add the accelerator keys this way, mainly for --nofile
4330  g_signal_connect (G_OBJECT(window), "realize",
4331  G_CALLBACK(main_window_realize_cb), window);
4332 
4333  LEAVE(" ");
4334 }
4335 
4336 #ifdef MAC_INTEGRATION
4337 /* Event handlers for the shutdown process. Gnc_quartz_shutdown is
4338  * connected to NSApplicationWillTerminate, the last chance to do
4339  * anything before quitting. The problem is that it's launched from a
4340  * CFRunLoop, not a g_main_loop, and if we call anything that would
4341  * affect the main_loop we get an assert that we're in a subidiary
4342  * loop.
4343  */
4344 static void
4345 gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
4346 {
4347  /* Do Nothing. It's too late. */
4348 }
4349 /* Should quit responds to NSApplicationBlockTermination; returning TRUE means
4350  * "don't terminate", FALSE means "do terminate". gnc_main_window_quit() queues
4351  * a timer that starts an orderly shutdown in 250ms and if we tell macOS it's OK
4352  * to quit GnuCash gets terminated instead of doing its orderly shutdown,
4353  * leaving the book locked.
4354  */
4355 static gboolean
4356 gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
4357 {
4359  gnc_main_window_quit (window);
4360  return TRUE;
4361 }
4362 /* Enable GtkMenuItem accelerators */
4363 static gboolean
4364 can_activate_cb(GtkWidget *widget, guint signal_id, gpointer data)
4365 {
4366  //return gtk_widget_is_sensitive (widget);
4367  return TRUE;
4368 }
4369 
4370 static void
4371 gnc_quartz_set_menu (GncMainWindow* window)
4372 {
4373  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4374  auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
4375  GtkWidget *item = nullptr;
4376  GClosure *quit_closure;
4377 
4378  gtk_widget_hide (priv->menubar);
4379  gtk_widget_set_no_show_all (priv->menubar, true);
4380 
4381  gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL(priv->menubar));
4382 
4383  // File Quit
4384  item = gnc_main_window_menu_find_menu_item (window, "FileQuitAction");
4385  if (item)
4386  gtk_widget_hide (GTK_WIDGET(item));
4387 
4388  quit_closure = g_cclosure_new (G_CALLBACK (gnc_quartz_should_quit),
4389  window, NULL);
4390  gtk_accel_group_connect (priv->accel_group, 'q', GDK_META_MASK,
4391  GTK_ACCEL_MASK, quit_closure);
4392 
4393 
4394  // Help About
4395  item = gnc_main_window_menu_find_menu_item (window, "HelpAboutAction");
4396  if (item)
4397  {
4398  gtk_widget_hide (item);
4399  gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 0);
4400  }
4401 
4402  // Edit Preferences
4403  item = gnc_main_window_menu_find_menu_item (window, "EditPreferencesAction");
4404  if (item)
4405  {
4406  gtk_widget_hide (GTK_WIDGET(item));
4407  gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 2);
4408  }
4409 
4410  // Help Menu
4411  item = gnc_main_window_menu_find_menu_item (window, "HelpAction");
4412  if (item)
4413  gtkosx_application_set_help_menu (theApp, GTK_MENU_ITEM(item));
4414  // Windows Menu
4415  item = gnc_main_window_menu_find_menu_item (window, "WindowsAction");
4416  if (item)
4417  gtkosx_application_set_window_menu (theApp, GTK_MENU_ITEM(item));
4418 
4419  g_signal_connect (theApp, "NSApplicationBlockTermination",
4420  G_CALLBACK(gnc_quartz_should_quit), window);
4421 
4422  g_signal_connect (priv->menubar, "can-activate-accel",
4423  G_CALLBACK (can_activate_cb), nullptr);
4424 
4425  gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
4426  g_object_unref (theApp);
4427 }
4428 #endif //MAC_INTEGRATION
4429 
4430 /* Callbacks */
4431 
4444 static gboolean
4445 gnc_main_window_show_summarybar (GncMainWindow *window, GAction *action)
4446 {
4447  GVariant *state;
4448  gboolean visible;
4449 
4450  if (action == nullptr)
4451  action = g_action_map_lookup_action (G_ACTION_MAP(window),
4452  "ViewSummaryAction");
4453  if (action == nullptr)
4454  return TRUE;
4455 
4456  state = g_action_get_state (G_ACTION(action));
4457 
4458  visible = g_variant_get_boolean (state);
4459 
4460  g_variant_unref (state);
4461 
4462  return visible;
4463 }
4464 
4474 static void
4475 gnc_main_window_switch_page (GtkNotebook *notebook,
4476  gpointer *notebook_page,
4477  gint pos,
4478  GncMainWindow *window)
4479 {
4480  GncMainWindowPrivate *priv;
4481  GtkWidget *child;
4482  GncPluginPage *page;
4483  gboolean visible;
4484 
4485  ENTER("Notebook %p, page, %p, index %d, window %p",
4486  notebook, notebook_page, pos, window);
4487  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4488 
4489  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4490  if (priv->current_page != nullptr)
4491  {
4492  page = priv->current_page;
4493  gnc_plugin_page_unselected (page);
4494  }
4495 
4496  child = gtk_notebook_get_nth_page (notebook, pos);
4497  if (child)
4498  {
4499  page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
4500  }
4501  else
4502  {
4503  page = nullptr;
4504  }
4505 
4506  priv->current_page = page;
4507 
4508  if (page != nullptr)
4509  {
4510  /* Update the user interface (e.g. menus and toolbars */
4512  visible = gnc_main_window_show_summarybar (window, nullptr);
4514 
4515  /* Allow page specific actions */
4516  gnc_plugin_page_selected (page);
4517  gnc_window_update_status (GNC_WINDOW(window), page);
4518 
4519  /* Update the page reference info */
4520  priv->usage_order = g_list_remove (priv->usage_order, page);
4521  priv->usage_order = g_list_prepend (priv->usage_order, page);
4522  }
4523 
4524  gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
4525  multiple_page_actions,
4526  g_list_length (priv->installed_pages) > 1);
4527 
4528  gnc_main_window_update_title(window);
4529 #ifndef MAC_INTEGRATION
4530  gnc_main_window_update_menu_item(window);
4531 #endif
4532  g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4533  LEAVE(" ");
4534 }
4535 
4542 static void
4543 gnc_main_window_page_reordered (GtkNotebook *notebook,
4544  GtkWidget *child,
4545  guint pos,
4546  GncMainWindow *window)
4547 {
4548  GncMainWindowPrivate *priv;
4549  GncPluginPage *page;
4550  GList *old_link;
4551 
4552  ENTER("Notebook %p, child %p, index %d, window %p",
4553  notebook, child, pos, window);
4554  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4555 
4556  if (!child) return;
4557 
4558  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4559 
4560  page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
4561  if (!page) return;
4562 
4563  old_link = g_list_find (priv->installed_pages, page);
4564  if (!old_link) return;
4565 
4566  priv->installed_pages = g_list_delete_link (priv->installed_pages,
4567  old_link);
4568  priv->installed_pages = g_list_insert (priv->installed_pages,
4569  page, pos);
4570 
4571  LEAVE(" ");
4572 }
4573 
4574 static void
4575 gnc_main_window_plugin_added (GncPlugin *manager,
4576  GncPlugin *plugin,
4577  GncMainWindow *window)
4578 {
4579  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4580  g_return_if_fail (GNC_IS_PLUGIN (plugin));
4581 
4582  gnc_plugin_add_to_window (plugin, window, window_type);
4583 }
4584 
4585 static void
4586 gnc_main_window_plugin_removed (GncPlugin *manager,
4587  GncPlugin *plugin,
4588  GncMainWindow *window)
4589 {
4590  g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4591  g_return_if_fail (GNC_IS_PLUGIN (plugin));
4592 
4593  gnc_plugin_remove_from_window (plugin, window, window_type);
4594 }
4595 
4596 
4597 /* Command callbacks */
4598 static void
4599 gnc_main_window_cmd_redirect (GSimpleAction *simple,
4600  GVariant *parameter,
4601  gpointer user_data)
4602 {
4603  GncMainWindow *window = (GncMainWindow*)user_data;
4604  GAction *redirect_action;
4605 
4606  PINFO("Redirect action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4607 
4608  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4609 
4610  if (redirect_action)
4611  {
4612  PINFO("Found action %p", redirect_action);
4613  g_action_activate (redirect_action, nullptr);
4614  return;
4615  }
4616 }
4617 
4618 static void
4619 gnc_main_window_cmd_page_setup (GSimpleAction *simple,
4620  GVariant *parameter,
4621  gpointer user_data)
4622 {
4623  GncMainWindow *window = (GncMainWindow*)user_data;
4624  GtkWindow *gtk_window;
4625 
4626  g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4627 
4628  gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
4629  gnc_ui_page_setup(gtk_window);
4630 }
4631 
4632 gboolean
4634 {
4635  QofBook *book = gnc_get_current_book ();
4636  gboolean use_split_action_for_num_before =
4638  gint use_read_only_threshold_before =
4640  gboolean use_split_action_for_num_after;
4641  gint use_read_only_threshold_after;
4642  gboolean return_val = FALSE;
4643  GList *results = nullptr, *iter;
4644 
4645  if (!options) return return_val;
4646 
4647  results = gnc_option_db_commit (options);
4648  for (iter = results; iter; iter = iter->next)
4649  {
4650  GtkWidget *dialog = gtk_message_dialog_new(gnc_ui_get_main_window (nullptr),
4651  (GtkDialogFlags)0,
4652  GTK_MESSAGE_ERROR,
4653  GTK_BUTTONS_OK,
4654  "%s",
4655  (char*)iter->data);
4656  gtk_dialog_run(GTK_DIALOG(dialog));
4657  gtk_widget_destroy(dialog);
4658  g_free (iter->data);
4659  }
4660  g_list_free (results);
4661  qof_book_begin_edit (book);
4662  qof_book_save_options (book, gnc_option_db_save, options, TRUE);
4663  use_split_action_for_num_after =
4665 
4666  // mark cached value as invalid so we get new value
4667  book->cached_num_days_autoreadonly_isvalid = FALSE;
4668  use_read_only_threshold_after = qof_book_get_num_days_autoreadonly (book);
4669 
4670  if (use_split_action_for_num_before != use_split_action_for_num_after)
4671  {
4673  use_split_action_for_num_after);
4674  return_val = TRUE;
4675  }
4676  if (use_read_only_threshold_before != use_read_only_threshold_after)
4677  return_val = TRUE;
4678 
4679  qof_book_commit_edit (book);
4680  return return_val;
4681 }
4682 
4683 static void
4684 gnc_book_options_dialog_apply_cb(GncOptionsDialog * optionwin,
4685  gpointer user_data)
4686 {
4687  auto options{static_cast<GncOptionDB *>(user_data)};
4688 
4689  if (!options) return;
4690 
4692  gnc_gui_refresh_all ();
4693 }
4694 
4695 static void
4696 gnc_book_options_dialog_close_cb(GncOptionsDialog * optionwin,
4697  gpointer user_data)
4698 {
4699  auto options{static_cast<GncOptionDB *>(user_data)};
4700 
4701  delete optionwin;
4702  gnc_option_db_destroy(options);
4703 }
4704 
4708 void
4710 {
4711  gnc_suspend_gui_refresh ();
4712  if (num_action)
4713  {
4714  /* Set a feature flag in the book for use of the split action field as number.
4715  * This will prevent older GnuCash versions that don't support this feature
4716  * from opening this file. */
4717  gnc_features_set_used (gnc_get_current_book(),
4718  GNC_FEATURE_NUM_FIELD_SOURCE);
4719  }
4720  gnc_book_option_num_field_source_change (num_action);
4721  gnc_resume_gui_refresh ();
4722 }
4723 
4724 static gboolean
4725 show_handler (const char *class_name, gint component_id,
4726  gpointer user_data, gpointer iter_data)
4727 {
4728  auto optwin{static_cast<GncOptionsDialog*>(user_data)};
4729 
4730  if (!optwin)
4731  return(FALSE);
4732 
4733  auto widget = optwin->get_widget();
4734  gtk_window_present(GTK_WINDOW(widget));
4735  return(TRUE);
4736 }
4737 
4738 GtkWidget *
4739 gnc_book_options_dialog_cb (gboolean modal, gchar *title, GtkWindow* parent)
4740 {
4741  auto book = gnc_get_current_book ();
4742 
4743  auto options = gnc_option_db_new();
4744  gnc_option_db_book_options(options);
4745  qof_book_load_options (book, gnc_option_db_load, options);
4746  gnc_option_db_clean (options);
4747 
4748  /* Only allow one Book Options dialog if called from file->properties
4749  menu */
4750  if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
4751  show_handler, nullptr))
4752  {
4753  return nullptr;
4754  }
4755  auto optionwin = new GncOptionsDialog (modal,
4756  (title ? title : _( "Book Options")),
4757  DIALOG_BOOK_OPTIONS_CM_CLASS, parent);
4758  optionwin->build_contents(options);
4759  optionwin->set_book_help_cb();
4760  optionwin->set_apply_cb(gnc_book_options_dialog_apply_cb,
4761  (gpointer)options);
4762  optionwin->set_close_cb ( gnc_book_options_dialog_close_cb,
4763  (gpointer)options);
4764  if (modal)
4766  return optionwin->get_widget();
4767 }
4768 
4769 static void
4770 gnc_main_window_cmd_file_properties (GSimpleAction *simple,
4771  GVariant *parameter,
4772  gpointer user_data)
4773 {
4774  GncMainWindow *window = (GncMainWindow*)user_data;
4775  gnc_book_options_dialog_cb (FALSE, nullptr, GTK_WINDOW (window));
4776 }
4777 
4778 static void
4779 gnc_main_window_cmd_file_close (GSimpleAction *simple,
4780  GVariant *parameter,
4781  gpointer user_data)
4782 {
4783  GncMainWindow *window = (GncMainWindow*)user_data;
4784  GncMainWindowPrivate *priv;
4785  GncPluginPage *page;
4786 
4787  g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4788 
4789  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4790  page = priv->current_page;
4792 }
4793 
4794 static void
4795 gnc_main_window_cmd_file_quit (GSimpleAction *simple,
4796  GVariant *parameter,
4797  gpointer user_data)
4798 {
4799  GncMainWindow *window = (GncMainWindow*)user_data;
4801  return;
4802 
4803  gnc_main_window_quit(window);
4804 }
4805 
4806 static void
4807 gnc_main_window_cmd_edit_cut (GSimpleAction *simple,
4808  GVariant *parameter,
4809  gpointer user_data)
4810 {
4811  GncMainWindow *window = (GncMainWindow*)user_data;
4812  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4813  GAction *redirect_action;
4814 
4815  PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4816 
4817  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4818 
4819  if (redirect_action)
4820  {
4821  PINFO("Found action %p", redirect_action);
4822  g_action_activate (redirect_action, nullptr);
4823  return;
4824  }
4825 
4826  if (GTK_IS_EDITABLE(widget))
4827  {
4828  gtk_editable_cut_clipboard (GTK_EDITABLE(widget));
4829  }
4830  else if (GTK_IS_TEXT_VIEW(widget))
4831  {
4832  GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4833  GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4834  GDK_SELECTION_CLIPBOARD);
4835  gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4836 
4837  if (clipboard)
4838  gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
4839  }
4840 }
4841 
4842 static void
4843 gnc_main_window_cmd_edit_copy (GSimpleAction *simple,
4844  GVariant *parameter,
4845  gpointer user_data)
4846 {
4847  GncMainWindow *window = (GncMainWindow*)user_data;
4848  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4849  GAction *redirect_action;
4850 
4851  PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4852 
4853  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4854 
4855  if (redirect_action)
4856  {
4857  PINFO("Found action %p", redirect_action);
4858  g_action_activate (redirect_action, nullptr);
4859  return;
4860  }
4861 
4862  if (GTK_IS_EDITABLE(widget))
4863  {
4864  gtk_editable_copy_clipboard (GTK_EDITABLE(widget));
4865  }
4866  else if (GTK_IS_TEXT_VIEW(widget))
4867  {
4868  GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4869  GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4870  GDK_SELECTION_CLIPBOARD);
4871  if (clipboard)
4872  gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
4873  }
4874 }
4875 
4876 static void
4877 gnc_main_window_cmd_edit_paste (GSimpleAction *simple,
4878  GVariant *parameter,
4879  gpointer user_data)
4880 {
4881  GncMainWindow *window = (GncMainWindow*)user_data;
4882  GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4883  GAction *redirect_action;
4884 
4885  PINFO("Paste action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
4886 
4887  redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
4888 
4889  if (redirect_action)
4890  {
4891  PINFO("Found action %p", redirect_action);
4892  g_action_activate (redirect_action, nullptr);
4893  return;
4894  }
4895 
4896  if (GTK_IS_EDITABLE(widget))
4897  {
4898  gtk_editable_paste_clipboard (GTK_EDITABLE(widget));
4899  }
4900  else if (GTK_IS_TEXT_VIEW(widget))
4901  {
4902  auto clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
4903 
4904  if (clipboard)
4905  {
4906  auto text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4907  auto editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4908  gtk_text_buffer_paste_clipboard (text_buffer, clipboard, nullptr,
4909  editable);
4910  }
4911  }
4912 }
4913 
4914 static void
4915 gnc_main_window_cmd_edit_preferences (GSimpleAction *simple,
4916  GVariant *parameter,
4917  gpointer user_data)
4918 {
4919  GncMainWindow *window = (GncMainWindow*)user_data;
4920  gnc_preferences_dialog (GTK_WINDOW(window));
4921 }
4922 
4923 static void
4924 gnc_main_window_cmd_view_refresh (GSimpleAction *simple,
4925  GVariant *parameter,
4926  gpointer user_data)
4927 {
4928 }
4929 
4930 static void
4931 gnc_main_window_cmd_actions_reset_warnings (GSimpleAction *simple,
4932  GVariant *parameter,
4933  gpointer user_data)
4934 {
4935  GncMainWindow *window = (GncMainWindow*)user_data;
4936  gnc_reset_warnings_dialog(GTK_WINDOW(window));
4937 }
4938 
4939 static void
4940 gnc_main_window_cmd_actions_rename_page (GSimpleAction *simple,
4941  GVariant *parameter,
4942  gpointer user_data)
4943 {
4944  GncMainWindow *window = (GncMainWindow*)user_data;
4945  GncMainWindowPrivate *priv;
4946  GncPluginPage *page;
4947  GtkWidget *label, *entry;
4948 
4949  ENTER(" ");
4950  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4951  page = priv->current_page;
4952  if (!page)
4953  {
4954  LEAVE("No current page");
4955  return;
4956  }
4957 
4958  if (!main_window_find_tab_items(window, page, &label, &entry))
4959  {
4960  LEAVE("can't find required widgets");
4961  return;
4962  }
4963 
4964  gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
4965  gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
4966  gtk_widget_hide(label);
4967  gtk_widget_show(entry);
4968  gtk_widget_grab_focus(entry);
4969  LEAVE("opened for editing");
4970 }
4971 
4972 static void
4973 gnc_main_window_cmd_view_toolbar (GSimpleAction *simple,
4974  GVariant *parameter,
4975  gpointer user_data)
4976 {
4977  GncMainWindow *window = (GncMainWindow*)user_data;
4978  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4979  GVariant *state = g_action_get_state (G_ACTION(simple));
4980 
4981  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4982 
4983  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
4984 
4985  if (!g_variant_get_boolean (state))
4986  gtk_widget_show (priv->toolbar);
4987  else
4988  gtk_widget_hide (priv->toolbar);
4989 
4990  g_variant_unref (state);
4991 }
4992 
4993 static void
4994 gnc_main_window_cmd_view_summary (GSimpleAction *simple,
4995  GVariant *parameter,
4996  gpointer user_data)
4997 {
4998  GncMainWindow *window = (GncMainWindow*)user_data;
4999  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5000  GList *item;
5001  gboolean visible;
5002 
5003  visible = gnc_main_window_show_summarybar (window, G_ACTION(simple));
5004 
5005  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!visible));
5006 
5007  for (item = priv->installed_pages; item; item = g_list_next (item))
5008  {
5009  gnc_plugin_page_show_summarybar (static_cast<GncPluginPage*>(item->data),
5010  !visible);
5011  }
5012 }
5013 
5014 static void
5015 gnc_main_window_cmd_view_statusbar (GSimpleAction *simple,
5016  GVariant *parameter,
5017  gpointer user_data)
5018 {
5019  GncMainWindow *window = (GncMainWindow*)user_data;
5020  GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5021  GVariant *state = g_action_get_state (G_ACTION(simple));
5022 
5023  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5024 
5025  g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
5026 
5027  if (!g_variant_get_boolean (state))
5028  gtk_widget_show (priv->statusbar);
5029  else
5030  gtk_widget_hide (priv->statusbar);
5031 
5032  g_variant_unref (state);
5033 }
5034 
5035 static void
5036 gnc_main_window_cmd_window_new (GSimpleAction *simple,
5037  GVariant *paramter,
5038  gpointer user_data)
5039 {
5040  GncMainWindow *new_window;
5041 
5042  /* Create the new window */
5043  ENTER(" ");
5044  new_window = gnc_main_window_new ();
5045  gtk_widget_show(GTK_WIDGET(new_window));
5046  LEAVE(" ");
5047 }
5048 
5049 static void
5050 gnc_main_window_cmd_window_move_page (GSimpleAction *simple,
5051  GVariant *paramter,
5052  gpointer user_data)
5053 {
5054  GncMainWindow *window = (GncMainWindow*)user_data;
5055  GncMainWindowPrivate *priv;
5056  GncMainWindow *new_window;
5057  GncPluginPage *page;
5058  GtkNotebook *notebook;
5059  GtkWidget *tab_widget, *menu_widget;
5060 
5061  ENTER("action %p, window %p", simple, window);
5062 
5063  /* Setup */
5064  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5065  page = priv->current_page;
5066  if (!page)
5067  {
5068  LEAVE("invalid page");
5069  return;
5070  }
5071  if (!page->notebook_page)
5072  {
5073  LEAVE("invalid notebook_page");
5074  return;
5075  }
5076 
5077 #ifndef MAC_INTEGRATION
5078  if (gnc_list_length_cmp (active_windows, gnc_main_window_max_number) == 0)
5079  gnc_info_dialog (GTK_WINDOW(window), "%s",
5080  _("The maximum number of window menu entries reached, no more entries will be added."));
5081 #endif /* !MAC_INTEGRATION */
5082 
5083  notebook = GTK_NOTEBOOK (priv->notebook);
5084  tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
5085  menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
5086 
5087  // Remove the page_changed signal callback
5088  gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
5089 
5090  /* Ref the page components, then remove it from its old window */
5091  g_object_ref(page);
5092  g_object_ref(tab_widget);
5093  g_object_ref(menu_widget);
5094  g_object_ref(page->notebook_page);
5095  gnc_main_window_disconnect(window, page);
5096 
5097  /* Create the new window */
5098  new_window = gnc_main_window_new ();
5099  gtk_widget_show(GTK_WIDGET(new_window));
5100 
5101  /* Now add the page to the new window */
5102  gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
5103 
5104  /* Unref the page components now that we're done */
5105  g_object_unref(page->notebook_page);
5106  g_object_unref(menu_widget);
5107  g_object_unref(tab_widget);
5108  g_object_unref(page);
5109 
5110  /* just a little debugging. :-) */
5111  DEBUG("Moved page %p from window %p to new window %p",
5112  page, window, new_window);
5113  DEBUG("Old window current is %p, new window current is %p",
5114  priv->current_page, priv->current_page);
5115 
5116  LEAVE("page moved");
5117 }
5118 
5119 static void
5120 gnc_main_window_cmd_view_tab_position (GSimpleAction *simple,
5121  GVariant *parameter,
5122  gpointer user_data)
5123 {
5124  gint item = g_variant_get_int32 (parameter);
5125 
5126  g_action_change_state (G_ACTION(simple), parameter);
5127 
5128  if (item < 0 || item > 3)
5129  return;
5130 
5131  if (item != 0 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP))
5132  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, FALSE);
5133 
5134  if (item != 1 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
5135  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, FALSE);
5136 
5137  if (item != 2 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
5138  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, FALSE);
5139 
5140  if (item != 3 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
5141  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, FALSE);
5142 
5143  switch (item)
5144  {
5145  case 0:
5146  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, TRUE);
5147  break;
5148 
5149  case 1:
5150  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, TRUE);
5151  break;
5152 
5153  case 2:
5154  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, TRUE);
5155  break;
5156 
5157  case 3:
5158  gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, TRUE);
5159  break;
5160  }
5161 
5162 }
5163 
5164 #ifndef MAC_INTEGRATION
5165 static void
5166 gnc_main_window_cmd_window_raise (GSimpleAction *simple,
5167  GVariant *parameter,
5168  gpointer user_data)
5169 {
5170  GncMainWindow *window = (GncMainWindow*)user_data;
5171  GncMainWindow *new_window;
5172  gint item;
5173 
5174  g_return_if_fail (G_IS_SIMPLE_ACTION(simple));
5175  g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
5176 
5177  item = g_variant_get_int32 (parameter);
5178 
5179  ENTER("action %p, window %p, item %d", simple, window, item);
5180 
5181  g_action_change_state (G_ACTION(simple), parameter);
5182 
5183  new_window = static_cast<GncMainWindow*>(g_list_nth_data (active_windows, item));
5184  gtk_window_present (GTK_WINDOW(new_window));
5185 
5186  /* revert the change in the radio group
5187  * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
5188  g_idle_add ((GSourceFunc)gnc_main_window_update_radio_button, window);
5189  LEAVE(" ");
5190 }
5191 #endif /* !MAC_INTEGRATION */
5192 
5193 static void
5194 gnc_main_window_cmd_help_tutorial (GSimpleAction *simple,
5195  GVariant *paramter,
5196  gpointer user_data)
5197 {
5198  GncMainWindow *window = (GncMainWindow*)user_data;
5199  gnc_gnome_help (GTK_WINDOW(window), DF_GUIDE, NULL);
5200 }
5201 
5202 static void
5203 gnc_main_window_cmd_help_contents (GSimpleAction *simple,
5204  GVariant *paramter,
5205  gpointer user_data)
5206 {
5207  GncMainWindow *window = (GncMainWindow*)user_data;
5208  gnc_gnome_help (GTK_WINDOW(window), DF_MANUAL, NULL);
5209 }
5210 
5220 static gchar *
5221 get_file (const gchar *partial)
5222 {
5223  gchar *filename, *text = nullptr;
5224  gsize length;
5225 
5226  filename = gnc_filepath_locate_doc_file(partial);
5227  if (filename && g_file_get_contents(filename, &text, &length, nullptr))
5228  {
5229  if (length)
5230  {
5231  g_free(filename);
5232  return text;
5233  }
5234  g_free(text);
5235  }
5236  g_free (filename);
5237  return nullptr;
5238 }
5239 
5240 
5250 static gchar **
5251 get_file_strsplit (const gchar *partial)
5252 {
5253  gchar *text, **lines;
5254 
5255  text = get_file(partial);
5256  if (!text)
5257  return nullptr;
5258 
5259  lines = g_strsplit_set(text, "\r\n", -1);
5260  g_free(text);
5261  return lines;
5262 }
5269 static gboolean
5270 url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
5271 {
5272  gnc_launch_doclink (GTK_WINDOW(dialog), uri);
5273  return TRUE;
5274 }
5275 
5276 #define DEFAULT_MARGIN 5
5277 
5278 static void
5279 set_text_cursor (GdkWindow *win, GdkCursorType type)
5280 {
5281  if (!win && !type)
5282  return;
5283 
5284  GdkCursor *current = gdk_window_get_cursor (win);
5285  if (type == gdk_cursor_get_cursor_type (current))
5286  return;
5287 
5288  GdkCursor *cur = gdk_cursor_new_for_display (gdk_window_get_display (win), type);
5289  gdk_window_set_cursor (win, cur);
5290 }
5291 
5292 static gboolean
5293 textview_motion_notify_cb (GtkWidget *textview,
5294  GdkEventMotion *event,
5295  gpointer user_data)
5296 {
5297  if ((event->state & GDK_BUTTON1_MASK) ||
5298  gtk_text_buffer_get_has_selection (gtk_text_view_get_buffer
5299  (GTK_TEXT_VIEW(textview))))
5300  return false;
5301 
5302  GtkTextIter iter;
5303  gboolean valid = gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(textview),
5304  &iter, event->x, event->y);
5305 
5306  if (valid && (event->y > DEFAULT_MARGIN))
5307  {
5308  GSList *tt_list = gtk_text_iter_get_tags (&iter);
5309 
5310  if (tt_list)
5311  {
5312  GtkTextTag *tt = (GtkTextTag*)g_slist_nth_data (tt_list, 0);
5313 
5314  if (g_object_get_data (G_OBJECT(tt), "link"))
5315  set_text_cursor (event->window, GDK_HAND1);
5316  else
5317  set_text_cursor (event->window, GDK_XTERM);
5318 
5319  g_slist_free (tt_list);
5320  }
5321  else
5322  set_text_cursor (event->window, GDK_XTERM);
5323  }
5324  else
5325  set_text_cursor (event->window, GDK_XTERM);
5326 
5327  return true;
5328 }
5329 
5330 static gboolean
5331 textview_url_activate (GtkTextTag *tag,
5332  GObject *object,
5333  GdkEvent *event,
5334  GtkTextIter *iter,
5335  gpointer user_data)
5336 {
5337  GdkEventButton *event_button = (GdkEventButton*)event;
5338 
5339  if ((event->type == GDK_BUTTON_RELEASE) &&
5340  (event_button->button == 1) &&
5341  !gtk_text_buffer_get_has_selection (gtk_text_view_get_buffer
5342  (GTK_TEXT_VIEW (object))) &&
5343  (event_button->y > DEFAULT_MARGIN))
5344  {
5345  gchar *link = (gchar*)g_object_get_data (G_OBJECT(tag), "link");
5346  PINFO("Link is '%s'", link);
5347  gchar *escaped_uri = g_uri_escape_string (link, ":/.\\", true);
5348  PINFO("Escaped Link is '%s'", escaped_uri);
5349  gnc_launch_doclink (GTK_WINDOW(user_data), escaped_uri);
5350  g_free (escaped_uri);
5351 
5352  return true;
5353  }
5354  return false;
5355 }
5356 
5357 static gint
5358 get_max_text_width (GtkTextView *textview, std::vector<EnvPaths> ep_vec)
5359 {
5360  gint max_text_width = 0;
5361  for (const auto& ep : ep_vec)
5362  {
5363  gint text_width;
5364  gchar *env_name = g_strconcat (ep.env_name, ":", nullptr);
5365 
5366  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(textview),
5367  env_name);
5368 
5369  pango_layout_get_pixel_size (layout, &text_width, nullptr);
5370  g_object_unref (layout);
5371  g_free (env_name);
5372 
5373  max_text_width = MAX(max_text_width, text_width);
5374  }
5375  return max_text_width;
5376 }
5377 
5378 static GtkTextTag *
5379 create_left_margin_text_tag (GtkTextView *textview,
5380  gchar *lmargin_tag,
5381  const gchar *text,
5382  gint max_width)
5383 {
5384  if (!lmargin_tag)
5385  return nullptr;
5386 
5387  GtkTextTag *lmargin_tt = gtk_text_tag_new (lmargin_tag);
5388  int text_width;
5389 
5390  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(textview), text);
5391  pango_layout_get_pixel_size (layout, &text_width, nullptr);
5392  g_object_unref (layout);
5393 
5394  g_object_set (G_OBJECT(lmargin_tt), "left_margin",
5395  DEFAULT_MARGIN + max_width - text_width, nullptr);
5396 
5397  return lmargin_tt;
5398 }
5399 
5400 static GdkRGBA
5401 get_link_color (void)
5402 {
5403  GdkRGBA link_color;
5404  GtkWidget *dummy_link_button = gtk_link_button_new_with_label ("https://www.gnucash.org", "Dummy");
5405  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(dummy_link_button));
5406  gtk_style_context_get_color (context, GTK_STATE_FLAG_LINK, &link_color);
5407 
5408  return link_color;
5409 }
5410 
5411 static GtkTextTag *
5412 create_url_text_tag (GtkDialog *dialog,
5413  GdkRGBA link_color,
5414  gchar *url_tag,
5415  const gchar *uri)
5416 {
5417  if (!url_tag)
5418  return nullptr;
5419 
5420  GtkTextTag *url_tt = gtk_text_tag_new (url_tag);
5421  g_object_set (G_OBJECT(url_tt), "underline", PANGO_UNDERLINE_SINGLE,
5422  "underline-set", true, nullptr);
5423  g_object_set (G_OBJECT(url_tt), "foreground-rgba", &link_color, nullptr);
5424 
5425  g_object_set_data_full (G_OBJECT(url_tt), "link", g_strdup (uri), g_free);
5426 
5427  g_signal_connect (G_OBJECT(url_tt), "event",
5428  G_CALLBACK(textview_url_activate), dialog);
5429  return url_tt;
5430 }
5431 
5432 static void
5433 add_textview_css_class (GtkTextView *textview)
5434 {
5435  GdkRGBA color;
5436  GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(textview));
5437  gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
5438 
5439  if (gnc_is_dark_theme (&color))
5440  gtk_style_context_add_class (stylectxt, "gnc-class-textview-dark");
5441  else
5442  gtk_style_context_add_class (stylectxt, "gnc-class-textview");
5443 }
5444 
5445 static void
5446 add_about_paths (GtkDialog *dialog)
5447 {
5448  GtkWidget *page_vbox = gnc_get_dialog_widget_from_id (dialog, "page_vbox");
5449 
5450  if (!page_vbox)
5451  {
5452  PWARN("Unable to find AboutDialog 'page_vbox' Widget");
5453  return;
5454  }
5455 
5456  GtkWidget *textview = gtk_text_view_new ();
5457  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(textview));
5458  GtkTextTagTable *ttt = gtk_text_buffer_get_tag_table (buffer);
5459  GtkTextIter iter;
5460 
5461  std::vector<EnvPaths> ep_vec = gnc_list_all_paths ();
5462  int ep_size = (int)ep_vec.size();
5463  int row = 1;
5464 
5465  GdkRGBA link_color = get_link_color ();
5466  gint max_text_width = get_max_text_width (GTK_TEXT_VIEW(textview), ep_vec);
5467 
5468  add_textview_css_class (GTK_TEXT_VIEW(textview));
5469 
5470  gtk_text_view_set_left_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
5471  gtk_text_view_set_right_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
5472  gtk_text_view_set_top_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
5473  gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
5474  gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(textview), GTK_WRAP_NONE);
5475 
5476  gtk_text_buffer_create_tag (buffer, "italic", "style", PANGO_STYLE_ITALIC, nullptr);
5477 
5478  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
5479 
5480  for (const auto& ep : ep_vec)
5481  {
5482  gchar *env_name = g_strconcat (ep.env_name, ":", nullptr);
5483  const gchar *uri = gnc_uri_create_uri ("file", nullptr, 0, nullptr, nullptr, ep.env_path);
5484  gchar *display_uri = gnc_doclink_get_unescaped_just_uri (uri);
5485 
5486  gchar *url_tag = g_strdup_printf ("%s%d", "url_tag", row);
5487  gchar *lmargin_tag = g_strdup_printf ("%s%d", "lmargin_tag", row);
5488 
5489  gtk_text_tag_table_add (ttt, create_left_margin_text_tag (GTK_TEXT_VIEW(textview),
5490  lmargin_tag,
5491  env_name,
5492  max_text_width));
5493  gtk_text_tag_table_add (ttt, create_url_text_tag (dialog,
5494  link_color,
5495  url_tag,
5496  uri));
5497 
5498  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, env_name, -1, lmargin_tag, nullptr);
5499  gtk_text_buffer_insert (buffer, &iter, " ", -1);
5500  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, display_uri, -1, url_tag, nullptr);
5501 
5502  g_free (url_tag);
5503  g_free (lmargin_tag);
5504  g_free (display_uri);
5505  g_free (env_name);
5506 
5507  if (ep.modifiable)
5508  {
5509  gtk_text_buffer_insert (buffer, &iter, " ", -1);
5510  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, _("(User modifiable)"), -1, "italic", nullptr);
5511  }
5512 
5513  if (row != ep_size)
5514  gtk_text_buffer_insert (buffer, &iter, "\n", -1);
5515 
5516  row++;
5517  }
5518  gtk_text_view_set_editable (GTK_TEXT_VIEW(textview), false);
5519 
5520  g_signal_connect (G_OBJECT(textview), "motion-notify-event",
5521  G_CALLBACK(textview_motion_notify_cb), nullptr);
5522 
5523  gtk_container_add_with_properties (GTK_CONTAINER(page_vbox), textview,
5524  "position", 1, nullptr);
5525  gtk_widget_show_all (page_vbox);
5526 }
5527 
5530 static void
5531 gnc_main_window_cmd_help_about (GSimpleAction *simple,
5532  GVariant *paramter,
5533  gpointer user_data)
5534 {
5535  GncMainWindow *window = (GncMainWindow*)user_data;
5536  /* Translators: %s will be replaced with the current year */
5537  gchar *copyright = g_strdup_printf(_("Copyright © 1997-%s The GnuCash contributors."),
5538  GNC_VCS_REV_YEAR);
5539  gchar **authors = get_file_strsplit("AUTHORS");
5540  gchar **documenters = get_file_strsplit("DOCUMENTERS");
5541  gchar *license = get_file("LICENSE");
5542  GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
5543  GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
5544  GNC_ICON_APP,
5545  128,
5546  GTK_ICON_LOOKUP_USE_BUILTIN,
5547  nullptr);
5548  gchar *version = g_strdup_printf ("%s: %s\n%s: %s\nFinance::Quote: %s",
5549  _("Version"), gnc_version(),
5550  _("Build ID"), gnc_build_id(),
5553  : "-");
5554  GtkDialog *dialog = GTK_DIALOG (gtk_about_dialog_new ());
5555  g_object_set(G_OBJECT(dialog), "authors", authors, "documenters",
5556  documenters, "comments",
5557  _("Accounting for personal and small business finance."),
5558  "copyright", copyright, "license", license, "logo", logo,
5559  "name", "GnuCash",
5560  /* Translators: the following string will be shown in
5561  * Help->About->Credits It's intended to be generated
5562  * automatically before each release so there's
5563  * usually no need to modify it, but if you do be
5564  * sure to include a newline (\\n) after each credit
5565  * so that it displays one per line.
5566  */
5567  "translator-credits", _("translator-credits\n"),
5568  "version", version,
5569  "website", PACKAGE_URL,
5570  "website-label", _("Visit the GnuCash website."),
5571  nullptr);
5572 
5573  g_free(version);
5574  g_free(copyright);
5575  if (license)
5576  g_free(license);
5577  if (documenters)
5578  g_strfreev(documenters);
5579  if (authors)
5580  g_strfreev(authors);
5581  g_object_unref (logo);
5582  g_signal_connect (dialog, "activate-link",
5583  G_CALLBACK (url_signal_cb), nullptr);
5584 
5585  // Add environment paths
5586  add_about_paths (dialog);
5587 
5588  /* Set dialog to resize. */
5589  gtk_window_set_resizable(GTK_WINDOW (dialog), TRUE);
5590 
5591  gtk_window_set_transient_for (GTK_WINDOW (dialog),
5592  GTK_WINDOW (window));
5593  gtk_dialog_run (dialog);
5594  gtk_widget_destroy (GTK_WIDGET (dialog));
5595 }
5596 
5597 
5598 /************************************************************
5599  * *
5600  ************************************************************/
5601 
5602 void
5604 {
5605  GList *window_iter;
5606 #ifdef MAC_INTEGRATION
5607  auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
5608 #endif
5609  for (window_iter = active_windows; window_iter != nullptr; window_iter = window_iter->next)
5610  {
5611  gtk_widget_show(GTK_WIDGET(window_iter->data));
5612  }
5613 #ifdef MAC_INTEGRATION
5614  g_signal_connect(theApp, "NSApplicationWillTerminate",
5615  G_CALLBACK(gnc_quartz_shutdown), nullptr);
5616  gtkosx_application_ready(theApp);
5617  g_object_unref (theApp);
5618 #endif
5619 }
5620 
5621 GtkWindow *
5622 gnc_ui_get_gtk_window (GtkWidget *widget)
5623 {
5624  GtkWidget *toplevel;
5625 
5626  if (!widget)
5627  return nullptr;
5628 
5629  toplevel = gtk_widget_get_toplevel (widget);
5630  if (toplevel && GTK_IS_WINDOW (toplevel))
5631  return GTK_WINDOW (toplevel);
5632  else
5633  return nullptr;
5634 }
5635 
5636 GtkWindow *
5637 gnc_ui_get_main_window (GtkWidget *widget)
5638 {
5639  GList *window;
5640 
5641  GtkWindow *toplevel = gnc_ui_get_gtk_window (widget);
5642  while (toplevel && !GNC_IS_MAIN_WINDOW (toplevel))
5643  toplevel = gtk_window_get_transient_for(toplevel);
5644 
5645  if (toplevel)
5646  return toplevel;
5647 
5648  for (window = active_windows; window; window = window->next)
5649  if (gtk_window_is_active (GTK_WINDOW (window->data)))
5650  return static_cast<GtkWindow*>(window->data);
5651 
5652  for (window = active_windows; window; window = window->next)
5653  if (gtk_widget_get_mapped (GTK_WIDGET(window->data)))
5654  return static_cast<GtkWindow*>(window->data);
5655 
5656  return nullptr;
5657 }
5658 
5659 
5665 static GtkWindow *
5666 gnc_main_window_get_gtk_window (GncWindow *window)
5667 {
5668  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
5669  return GTK_WINDOW(window);
5670 }
5671 
5672 
5678 static GtkWidget *
5679 gnc_main_window_get_statusbar (GncWindow *window_in)
5680 {
5681  GncMainWindowPrivate *priv;
5682  GncMainWindow *window;
5683 
5684  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
5685 
5686  window = GNC_MAIN_WINDOW(window_in);
5687  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5688  return priv->statusbar;
5689 }
5690 
5691 
5697 static GtkWidget *
5698 gnc_main_window_get_progressbar (GncWindow *window_in)
5699 {
5700  GncMainWindowPrivate *priv;
5701  GncMainWindow *window;
5702 
5703  g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
5704 
5705  window = GNC_MAIN_WINDOW(window_in);
5706  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5707  return priv->progressbar;
5708 }
5709 
5710 
5716 static GtkWidget *
5717 gnc_main_window_get_menubar (GncWindow *window)
5718 {
5719  GncMainWindowPrivate *priv;
5720 
5721  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5722 
5723  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5724 
5725  return priv->menubar;
5726 }
5727 
5733 static GtkWidget *
5734 gnc_main_window_get_toolbar (GncWindow *window)
5735 {
5736  GncMainWindowPrivate *priv;
5737 
5738  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5739 
5740  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5741 
5742  return priv->toolbar;
5743 }
5744 
5750 static GMenuModel *
5751 gnc_main_window_get_menubar_model (GncWindow *window)
5752 {
5753  GncMainWindowPrivate *priv;
5754 
5755  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5756 
5757  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5758 
5759  return priv->menubar_model;
5760 }
5761 
5767 static GtkAccelGroup *
5768 gnc_main_window_get_accel_group (GncWindow *window)
5769 {
5770  GncMainWindowPrivate *priv;
5771 
5772  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5773 
5774  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5775 
5776  return priv->accel_group;
5777 }
5778 
5783 static void
5784 gnc_window_main_window_init (GncWindowInterface *iface)
5785 {
5786  iface->get_gtk_window = gnc_main_window_get_gtk_window;
5787  iface->get_statusbar = gnc_main_window_get_statusbar;
5788  iface->get_progressbar = gnc_main_window_get_progressbar;
5789  iface->get_menubar = gnc_main_window_get_menubar;
5790  iface->get_toolbar = gnc_main_window_get_toolbar;
5791  iface->get_menubar_model = gnc_main_window_get_menubar_model;
5792  iface->get_accel_group = gnc_main_window_get_accel_group;
5793 }
5794 
5795 
5796 /* Set the window where all progressbar updates should occur. This
5797  * is a wrapper around the gnc_window_set_progressbar_window()
5798  * function.
5799  */
5800 void
5802 {
5803  GncWindow *gncwin;
5804  gncwin = GNC_WINDOW(window);
5805  gnc_window_set_progressbar_window(gncwin);
5806 }
5807 
5808 
5821 static void
5822 do_popup_menu (GncPluginPage *page, GdkEventButton *event)
5823 {
5824  GtkBuilder *builder;
5825  GMenuModel *menu_model;
5826  GtkWidget *menu;
5827  const gchar *menu_qualifier;
5828  gchar *popup_menu_name;
5829  GncWindow* gnc_window;
5830  GtkWidget *statusbar;
5831 
5832  g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
5833 
5834  ENTER("page %p, event %p", page, event);
5835 
5836  gnc_window = GNC_WINDOW(GNC_PLUGIN_PAGE(page)->window);
5837 
5838  statusbar = gnc_window_get_statusbar (gnc_window);
5839 
5840  builder = gnc_plugin_page_get_builder (page);
5841 
5842  menu_qualifier = gnc_plugin_page_get_menu_popup_qualifier (page);
5843 
5844  if (!menu_qualifier)
5845  menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
5846 
5847  if (builder == nullptr)
5848  {
5849  LEAVE("no builder");
5850  return;
5851  }
5852 
5853  if (menu_qualifier)
5854  popup_menu_name = g_strconcat ("mainwin-popup-", menu_qualifier, nullptr);
5855  else
5856  popup_menu_name = g_strdup ("mainwin-popup");
5857 
5858  menu_model = (GMenuModel *)gtk_builder_get_object (builder, popup_menu_name);
5859 
5860  if (!menu_model)
5861  menu_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-popup");
5862 
5863  menu = gtk_menu_new_from_model (menu_model);
5864 
5865  if (!menu)
5866  {
5867  LEAVE("no menu");
5868  return;
5869  }
5870 
5871  // add tooltip redirect call backs
5872  gnc_plugin_add_menu_tooltip_callbacks (menu, menu_model, statusbar);
5873 
5874  gtk_menu_attach_to_widget (GTK_MENU(menu), GTK_WIDGET(page->window), nullptr);
5875  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
5876 
5877  g_free (popup_menu_name);
5878 
5879  LEAVE(" ");
5880 }
5881 
5882 
5896 gboolean
5898  GncPluginPage *page)
5899 {
5900  ENTER("widget %p, page %p", widget, page);
5901  do_popup_menu(page, nullptr);
5902  LEAVE(" ");
5903  return TRUE;
5904 }
5905 
5906 
5907 /* Callback function invoked when the user clicks in the content of
5908  * any Gnucash window. If this was a "right-click" then Gnucash will
5909  * popup the contextual menu.
5910  */
5911 gboolean
5912 gnc_main_window_button_press_cb (GtkWidget *whatever,
5913  GdkEventButton *event,
5914  GncPluginPage *page)
5915 {
5916  g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
5917 
5918  ENTER("widget %p, event %p, page %p", whatever, event, page);
5919  /* Ignore double-clicks and triple-clicks */
5920  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
5921  {
5922  do_popup_menu(page, event);
5923  LEAVE("menu shown");
5924  return TRUE;
5925  }
5926 
5927  LEAVE("other click");
5928  return FALSE;
5929 }
5930 
5931 void
5933  gboolean sensitive)
5934 {
5935  for (auto tmp = active_windows; tmp; tmp = g_list_next(tmp))
5936  {
5937  auto action{gnc_main_window_find_action (static_cast<GncMainWindow*>(tmp->data), action_name)};
5938  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
5939  }
5940 }
5941 
5942 GMenuModel *
5943 gnc_main_window_get_menu_model (GncMainWindow *window)
5944 {
5945  GncMainWindowPrivate *priv;
5946 
5947  g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
5948 
5949  priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
5950 
5951  return priv->menubar_model;
5952 }
5953 
5954 gboolean
5955 gnc_main_window_just_plugin_prefs (GncMainWindow* window)
5956 {
5957  return window->just_plugin_prefs;
5958 }
5959 
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.cpp:127
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.cpp: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.
gboolean gnc_is_dark_theme(GdkRGBA *fg_color)
Return whether the current gtk theme is a dark one.
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.cpp:152
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.
gboolean gnc_main_window_is_quitting(GncMainWindow *window)
Check if the main window is quitting.
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:575
#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.cpp: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:370
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:262
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.cpp: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.cpp:142
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.