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