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