mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5955 lines
198 KiB
5955 lines
198 KiB
/*
|
|
* gnc-main-window.c -- GtkWindow which represents the
|
|
* GnuCash main window.
|
|
*
|
|
* Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
|
|
* Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, contact:
|
|
*
|
|
* Free Software Foundation Voice: +1-617-542-5942
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org
|
|
*/
|
|
|
|
/** @addtogroup Windows
|
|
@{ */
|
|
/** @addtogroup GncMainWindow Main Window functions.
|
|
@{ */
|
|
/** @file gnc-main-window.c
|
|
@brief Functions for adding content to a window.
|
|
@author Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
|
|
@author Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
|
|
*/
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include "dialog-options.hpp"
|
|
#include <libguile.h>
|
|
|
|
#include <config.h>
|
|
|
|
|
|
#include "gnc-plugin.h"
|
|
#include "gnc-plugin-manager.h"
|
|
#include "gnc-main-window.h"
|
|
|
|
#include "dialog-preferences.h"
|
|
#include "dialog-reset-warnings.h"
|
|
#include "dialog-transfer.h"
|
|
#include "dialog-utils.h"
|
|
#include "engine-helpers.h"
|
|
#include "file-utils.h"
|
|
#include "gnc-component-manager.h"
|
|
#include "dialog-doclink-utils.h"
|
|
#include "gnc-engine.h"
|
|
#include "gnc-features.h"
|
|
#include "gnc-file.h"
|
|
#include "gnc-filepath-utils.h"
|
|
#include "gnc-gkeyfile-utils.h"
|
|
#include "gnc-gnome-utils.h"
|
|
#include "gnc-gobject-utils.h"
|
|
#include "gnc-gui-query.h"
|
|
#include "gnc-gtk-utils.h"
|
|
#include "gnc-hooks.h"
|
|
#include "gnc-icons.h"
|
|
#include "gnc-session.h"
|
|
#include "gnc-state.h"
|
|
#include "gnc-ui.h"
|
|
#include "gnc-ui-util.h"
|
|
#include <gnc-glib-utils.h>
|
|
#include "gnc-uri-utils.h"
|
|
#include "gnc-version.h"
|
|
#include "gnc-warnings.h"
|
|
#include "gnc-window.h"
|
|
#include "gnc-prefs.h"
|
|
#include "gnc-optiondb.h"
|
|
#include "gnc-autosave.h"
|
|
#include "print-session.h"
|
|
#ifdef MAC_INTEGRATION
|
|
#include <gtkmacintegration/gtkosxapplication.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
# define __need_system_sys_stat_h //To block Guile-2.0's evil substitute
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h> // for stat(2)
|
|
#endif
|
|
|
|
/** Names of signals generated by the main window. */
|
|
enum
|
|
{
|
|
PAGE_ADDED,
|
|
PAGE_CHANGED,
|
|
MENU_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
/** This label is used to provide a mapping from a visible page widget
|
|
* back to the corresponding GncPluginPage object. */
|
|
#define PLUGIN_PAGE_LABEL "plugin-page"
|
|
|
|
#define PLUGIN_PAGE_CLOSE_BUTTON "close-button"
|
|
#define PLUGIN_PAGE_TAB_LABEL "label"
|
|
|
|
#define GNC_PREF_SHOW_CLOSE_BUTTON "tab-close-buttons"
|
|
#define GNC_PREF_TAB_NEXT_RECENT "tab-next-recent"
|
|
#define GNC_PREF_TAB_POSITION_TOP "tab-position-top"
|
|
#define GNC_PREF_TAB_POSITION_BOTTOM "tab-position-bottom"
|
|
#define GNC_PREF_TAB_POSITION_LEFT "tab-position-left"
|
|
#define GNC_PREF_TAB_POSITION_RIGHT "tab-position-right"
|
|
#define GNC_PREF_TAB_WIDTH "tab-width"
|
|
#define GNC_PREF_TAB_COLOR "show-account-color-tabs"
|
|
#define GNC_PREF_SAVE_CLOSE_EXPIRES "save-on-close-expires"
|
|
#define GNC_PREF_SAVE_CLOSE_WAIT_TIME "save-on-close-wait-time"
|
|
#define GNC_PREF_TAB_OPEN_ADJACENT "tab-open-adjacent"
|
|
|
|
#define GNC_MAIN_WINDOW_NAME "GncMainWindow"
|
|
|
|
#define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
|
|
|
|
/**
|
|
* Processes selected options in the Book Options dialog: checks book_currency
|
|
* and use_split_action_for_num to see if features kvp should be set. To be used
|
|
* where ever a new book situation requires book option selection (e.g., not
|
|
* just in Book Options dialog opened from main window but also in new-file
|
|
* assistant).
|
|
*
|
|
* @param GncOptionDB * options.
|
|
*
|
|
* @return TRUE if gnc_gui_refresh_all should be called; otherwise FALSE.
|
|
**/
|
|
extern gboolean gnc_book_options_dialog_apply_helper(GncOptionDB * options);
|
|
|
|
/** Max number of windows allowed */
|
|
[[maybe_unused]] constexpr auto gnc_main_window_max_number {10};
|
|
|
|
/* Static Globals *******************************************************/
|
|
|
|
/** The debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = GNC_MOD_GUI;
|
|
/** An identifier that indicates a "main" window. */
|
|
static GQuark window_type = 0;
|
|
/** A list of all extant main windows. This is for convenience as the
|
|
* same information can be obtained from the object tracking code. */
|
|
static GList *active_windows = nullptr;
|
|
/** Count down timer for the save changes dialog. If the timer reaches zero
|
|
* any changes will be saved and the save dialog closed automatically */
|
|
static guint secs_to_save = 0;
|
|
#define MSG_AUTO_SAVE _("Changes will be saved automatically in %u seconds")
|
|
|
|
/* Declarations *********************************************************/
|
|
static void gnc_main_window_constructed (GObject *object);
|
|
static void gnc_main_window_finalize (GObject *object);
|
|
static void gnc_main_window_destroy (GtkWidget *widget);
|
|
|
|
static void gnc_main_window_setup_window (GncMainWindow *window);
|
|
static void gnc_window_main_window_init (GncWindowInterface *iface);
|
|
#ifndef MAC_INTEGRATION
|
|
static void gnc_main_window_update_all_menu_items (void);
|
|
#endif
|
|
|
|
/* Callbacks */
|
|
static void gnc_main_window_switch_page (GtkNotebook *notebook, gpointer *notebook_page, gint pos, GncMainWindow *window);
|
|
static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window);
|
|
static void gnc_main_window_plugin_added (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
|
|
static void gnc_main_window_plugin_removed (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
|
|
static void gnc_main_window_engine_commit_error_callback( gpointer data, QofBackendError errcode );
|
|
|
|
/* Command callbacks */
|
|
static void gnc_main_window_cmd_redirect (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_page_setup (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_file_properties (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_file_close (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_file_quit (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_edit_cut (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_edit_copy (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_edit_paste (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_edit_preferences (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_view_refresh (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_view_toolbar (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_view_summary (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_view_statusbar (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
|
|
static void gnc_main_window_cmd_view_tab_position (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
|
|
|
|
static void gnc_main_window_cmd_actions_reset_warnings (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_actions_rename_page (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_window_new (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_window_move_page (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
#ifndef MAC_INTEGRATION
|
|
static void gnc_main_window_cmd_window_raise (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
|
|
#endif
|
|
static void gnc_main_window_cmd_help_tutorial (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_help_contents (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
static void gnc_main_window_cmd_help_about (GSimpleAction *simple, GVariant *paramter, gpointer user_data);
|
|
|
|
static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
|
|
static GtkWidget *gnc_main_window_get_statusbar (GncWindow *window_in);
|
|
static void statusbar_notification_lastmodified (void);
|
|
static void gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data);
|
|
static void gnc_main_window_remove_prefs (GncMainWindow *window);
|
|
|
|
#ifdef MAC_INTEGRATION
|
|
static void gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data);
|
|
static gboolean gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window);
|
|
static void gnc_quartz_set_menu (GncMainWindow* window);
|
|
#endif
|
|
static void gnc_main_window_init_menu_updaters (GncMainWindow *window);
|
|
|
|
struct _GncMainWindow
|
|
{
|
|
GtkApplicationWindow gtk_application_window; /**< The parent object for a main window. */
|
|
gboolean window_quitting; /**< Set to TRUE when quitting from this window. */
|
|
gboolean just_plugin_prefs; /**< Just remove preferences only from plugins */
|
|
};
|
|
|
|
/** The instance private data structure for an embedded window
|
|
* object. */
|
|
typedef struct
|
|
{
|
|
/** The dock (vbox) at the top of the window containing the
|
|
* menubar and toolbar. These items are generated by the UI
|
|
* manager and stored here when the UI manager provides them
|
|
* to the main window. */
|
|
GtkWidget *menu_dock;
|
|
/** The menubar */
|
|
GtkWidget *menubar;
|
|
/** The menubar_model */
|
|
GMenuModel *menubar_model;
|
|
/** The toolbar. This pointer provides easy access for
|
|
* showing/hiding the toolbar. */
|
|
GtkWidget *toolbar;
|
|
/** The notebook containing all the pages in this window. */
|
|
GtkWidget *notebook;
|
|
/** Show account color as background on tabs */
|
|
gboolean show_color_tabs;
|
|
/** A pointer to the status bar at the bottom edge of the
|
|
* window. This pointer provides easy access for
|
|
* updating/showing/hiding the status bar. */
|
|
GtkWidget *statusbar;
|
|
/** A pointer to the progress bar at the bottom right of the
|
|
* window that is contained in the status bar. This pointer
|
|
* provides easy access for updating the progressbar. */
|
|
GtkWidget *progressbar;
|
|
/** A list of all pages that are installed in this window. */
|
|
GList *installed_pages;
|
|
/** A list of pages in order of use (most recent -> least recent) */
|
|
GList *usage_order;
|
|
/** The currently selected page. */
|
|
GncPluginPage *current_page;
|
|
/** The identifier for this window's engine event handler. */
|
|
gint event_handler_id;
|
|
/** Array for window position. */
|
|
gint pos[2];
|
|
/** Set when restoring plugin pages */
|
|
gboolean restoring_pages;
|
|
|
|
const gchar *previous_plugin_page_name;
|
|
const gchar *previous_menu_qualifier;
|
|
|
|
/** The accelerator group for the window */
|
|
GtkAccelGroup *accel_group;
|
|
|
|
GHashTable *display_item_hash;
|
|
|
|
} GncMainWindowPrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_APPLICATION_WINDOW,
|
|
G_ADD_PRIVATE (GncMainWindow)
|
|
G_IMPLEMENT_INTERFACE (GNC_TYPE_WINDOW,
|
|
gnc_window_main_window_init))
|
|
|
|
#define GNC_MAIN_WINDOW_GET_PRIVATE(o) \
|
|
((GncMainWindowPrivate*)gnc_main_window_get_instance_private((GncMainWindow*)o))
|
|
|
|
/** A holding place for all the signals generated by the main window
|
|
* code. */
|
|
static guint main_window_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
/** An array of all of the actions provided by the main window code.
|
|
* This includes some placeholder actions for the menus that are
|
|
* visible in the menu bar but have no action associated with
|
|
* them. */
|
|
static GActionEntry gnc_menu_actions [] =
|
|
{
|
|
{ "FilePageSetupAction", gnc_main_window_cmd_page_setup, nullptr, nullptr, nullptr },
|
|
{ "FilePropertiesAction", gnc_main_window_cmd_file_properties, nullptr, nullptr, nullptr },
|
|
{ "FileCloseAction", gnc_main_window_cmd_file_close, nullptr, nullptr, nullptr },
|
|
{ "FilePrintAction", gnc_main_window_cmd_redirect, nullptr, nullptr, nullptr },
|
|
{ "FileQuitAction", gnc_main_window_cmd_file_quit, nullptr, nullptr, nullptr },
|
|
|
|
{ "EditCutAction", gnc_main_window_cmd_edit_cut, nullptr, nullptr, nullptr },
|
|
{ "EditCopyAction", gnc_main_window_cmd_edit_copy, nullptr, nullptr, nullptr },
|
|
{ "EditPasteAction", gnc_main_window_cmd_edit_paste, nullptr, nullptr, nullptr },
|
|
{ "EditPreferencesAction", gnc_main_window_cmd_edit_preferences, nullptr, nullptr, nullptr },
|
|
|
|
{ "ActionsForgetWarningsAction", gnc_main_window_cmd_actions_reset_warnings, nullptr, nullptr, nullptr },
|
|
{ "ActionsRenamePageAction", gnc_main_window_cmd_actions_rename_page, nullptr, nullptr, nullptr },
|
|
|
|
{ "TransactionAction", nullptr, nullptr, nullptr, nullptr },
|
|
|
|
{ "ViewSortByAction", nullptr, nullptr, nullptr, nullptr },
|
|
{ "ViewFilterByAction", nullptr, nullptr, nullptr, nullptr },
|
|
{ "ViewRefreshAction", gnc_main_window_cmd_view_refresh, nullptr, nullptr, nullptr },
|
|
{ "ViewToolbarAction", gnc_main_window_cmd_view_toolbar, nullptr, "true", nullptr },
|
|
{ "ViewSummaryAction", gnc_main_window_cmd_view_summary, nullptr, "true", nullptr },
|
|
{ "ViewStatusbarAction", gnc_main_window_cmd_view_statusbar, nullptr, "true", nullptr },
|
|
{ "ViewTabPositionAction", gnc_main_window_cmd_view_tab_position, "i", "@i 0", nullptr },
|
|
|
|
{ "ScheduledAction", nullptr, nullptr, nullptr, nullptr },
|
|
|
|
{ "ExtensionsAction", nullptr, nullptr, nullptr, nullptr },
|
|
|
|
{ "WindowNewAction", gnc_main_window_cmd_window_new, nullptr, nullptr, nullptr },
|
|
{ "WindowMovePageAction", gnc_main_window_cmd_window_move_page, nullptr, nullptr, nullptr },
|
|
#ifndef MAC_INTEGRATION
|
|
{ "WindowAction", gnc_main_window_cmd_window_raise, "i", "@i 0", nullptr },
|
|
#endif
|
|
{ "HelpTutorialAction", gnc_main_window_cmd_help_tutorial, nullptr, nullptr, nullptr },
|
|
{ "HelpContentsAction", gnc_main_window_cmd_help_contents, nullptr, nullptr, nullptr },
|
|
{ "HelpAboutAction", gnc_main_window_cmd_help_about, nullptr, nullptr, nullptr },
|
|
};
|
|
/** The number of actions provided by the main window. */
|
|
static guint gnc_menu_n_actions = G_N_ELEMENTS(gnc_menu_actions);
|
|
|
|
/** The following are in the main window so they will always be
|
|
* present in the menu structure, but they are never sensitive.
|
|
* These actions should be overridden in child windows where they
|
|
* have meaning. */
|
|
static const gchar *always_insensitive_actions[] =
|
|
{
|
|
"FilePrintAction",
|
|
nullptr
|
|
};
|
|
|
|
|
|
/** The following items in the main window should be made insensitive
|
|
* at startup time. The sensitivity will be changed by some later
|
|
* event. */
|
|
static const gchar *initially_insensitive_actions[] =
|
|
{
|
|
"FileCloseAction",
|
|
nullptr
|
|
};
|
|
|
|
|
|
/** The following are in the main window so they will always be
|
|
* present in the menu structure, but they are always hidden.
|
|
* These actions should be overridden in child windows where they
|
|
* have meaning. */
|
|
static const gchar *always_hidden_actions[] =
|
|
{
|
|
"ViewSortByAction",
|
|
"ViewFilterByAction",
|
|
nullptr
|
|
};
|
|
|
|
|
|
/** If a page is flagged as immutable, then the following actions
|
|
* cannot be performed on that page. */
|
|
static const gchar *immutable_page_actions[] =
|
|
{
|
|
"FileCloseAction",
|
|
nullptr
|
|
};
|
|
|
|
|
|
/** The following actions can only be performed if there are multiple
|
|
* pages in a window. */
|
|
static const gchar *multiple_page_actions[] =
|
|
{
|
|
"WindowMovePageAction",
|
|
nullptr
|
|
};
|
|
|
|
|
|
/************************************************************
|
|
* *
|
|
************************************************************/
|
|
#define WINDOW_COUNT "WindowCount"
|
|
#define WINDOW_STRING "Window %d"
|
|
#define WINDOW_GEOMETRY "WindowGeometry"
|
|
#define WINDOW_POSITION "WindowPosition"
|
|
#define WINDOW_MAXIMIZED "WindowMaximized"
|
|
#define TOOLBAR_VISIBLE "ToolbarVisible"
|
|
#define STATUSBAR_VISIBLE "StatusbarVisible"
|
|
#define SUMMARYBAR_VISIBLE "SummarybarVisible"
|
|
#define WINDOW_FIRSTPAGE "FirstPage"
|
|
#define WINDOW_PAGECOUNT "PageCount"
|
|
#define WINDOW_PAGEORDER "PageOrder"
|
|
#define PAGE_TYPE "PageType"
|
|
#define PAGE_NAME "PageName"
|
|
#define PAGE_STRING "Page %d"
|
|
|
|
typedef struct
|
|
{
|
|
GKeyFile *key_file;
|
|
const gchar *group_name;
|
|
gint window_num;
|
|
gint page_num;
|
|
gint page_offset;
|
|
} GncMainWindowSaveData;
|
|
|
|
|
|
gboolean
|
|
gnc_main_window_is_restoring_pages (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
return priv->restoring_pages;
|
|
}
|
|
|
|
|
|
/* Iterator function to walk all pages in all windows, calling the
|
|
* specified function for each page. */
|
|
void
|
|
gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data)
|
|
{
|
|
ENTER(" ");
|
|
for (auto w = active_windows; w; w = g_list_next(w))
|
|
{
|
|
auto window{static_cast<GncMainWindow*>(w->data)};
|
|
auto priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
for (auto p = priv->installed_pages; p; p = g_list_next(p))
|
|
{
|
|
auto page{static_cast<GncPluginPage*>(p->data)};
|
|
fn(page, user_data);
|
|
}
|
|
}
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Restore a single page to a window. This function calls a page
|
|
* specific function to create the actual page. It then handles all
|
|
* the common tasks such as insuring the page is installed into a
|
|
* window, updating the page name, and anything else that might be
|
|
* common to all pages.
|
|
*
|
|
* @param window The GncMainWindow where the new page will be
|
|
* installed.
|
|
*
|
|
* @param data A data structure containing state about the
|
|
* window/page restoration process.
|
|
*
|
|
* @return true if page was added else false.
|
|
* */
|
|
static gboolean
|
|
gnc_main_window_restore_page (GncMainWindow *window,
|
|
GncMainWindowSaveData *data)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page = nullptr;
|
|
gchar *page_group, *page_type = nullptr, *name = nullptr;
|
|
const gchar *class_type;
|
|
GError *error = nullptr;
|
|
|
|
ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
|
|
window, data, data->key_file, data->window_num, data->page_offset,
|
|
data->page_num);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page_group = g_strdup_printf(PAGE_STRING,
|
|
data->page_offset + data->page_num);
|
|
page_type = g_key_file_get_string(data->key_file, page_group,
|
|
PAGE_TYPE, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
page_group, PAGE_TYPE, error->message);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* See if the page already exists. */
|
|
page = static_cast<GncPluginPage*>(g_list_nth_data(priv->installed_pages,
|
|
data->page_num));
|
|
if (page)
|
|
{
|
|
class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
|
|
if (strcmp(page_type, class_type) != 0)
|
|
{
|
|
g_warning("error: page types don't match: state %s, existing page %s",
|
|
page_type, class_type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* create and install the page */
|
|
page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
|
|
data->key_file, page_group);
|
|
if (page)
|
|
{
|
|
/* Does the page still need to be installed into the window? */
|
|
if (page->window == nullptr)
|
|
{
|
|
gnc_plugin_page_set_use_new_window(page, FALSE);
|
|
gnc_main_window_open_page(window, page);
|
|
}
|
|
|
|
/* Restore the page name */
|
|
name = g_key_file_get_string(data->key_file, page_group,
|
|
PAGE_NAME, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
page_group, PAGE_NAME, error->message);
|
|
/* Fall through and still show the page. */
|
|
}
|
|
else
|
|
{
|
|
DEBUG("updating page name for %p to %s.", page, name);
|
|
main_window_update_page_name(page, name);
|
|
g_free(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
LEAVE("ok");
|
|
cleanup:
|
|
if (error)
|
|
g_error_free(error);
|
|
if (page_type)
|
|
g_free(page_type);
|
|
g_free(page_group);
|
|
|
|
return (page ? true : false);
|
|
}
|
|
|
|
static bool
|
|
intersects_some_monitor(const GdkRectangle& rect)
|
|
{
|
|
auto display = gdk_display_get_default();
|
|
if (!display)
|
|
return false;
|
|
|
|
int n = gdk_display_get_n_monitors(display);
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
auto monitor = gdk_display_get_monitor(display, i);
|
|
GdkRectangle monitor_geometry;
|
|
gdk_monitor_get_geometry(monitor, &monitor_geometry);
|
|
DEBUG("Monitor %d: position (%d,%d), size %dx%d\n", i,
|
|
monitor_geometry.x, monitor_geometry.y,
|
|
monitor_geometry.width, monitor_geometry.height);
|
|
if (gdk_rectangle_intersect(&rect, &monitor_geometry, nullptr))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
set_window_geometry(GncMainWindow *window, GncMainWindowSaveData *data, gchar *window_group)
|
|
{
|
|
gsize length;
|
|
GError *error = nullptr;
|
|
gint *geom = g_key_file_get_integer_list(data->key_file, window_group,
|
|
WINDOW_GEOMETRY, &length, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_GEOMETRY, error->message);
|
|
g_error_free(error);
|
|
error = nullptr;
|
|
}
|
|
else if (length != 2)
|
|
{
|
|
g_warning("invalid number of values for group %s key %s",
|
|
window_group, WINDOW_GEOMETRY);
|
|
}
|
|
else
|
|
{
|
|
gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
|
|
DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
|
|
}
|
|
|
|
/* keep the geometry for a test whether the windows position
|
|
is offscreen */
|
|
gint *pos = g_key_file_get_integer_list(data->key_file, window_group,
|
|
WINDOW_POSITION, &length, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_POSITION, error->message);
|
|
g_error_free(error);
|
|
error = nullptr;
|
|
}
|
|
else if (length != 2)
|
|
{
|
|
g_warning("invalid number of values for group %s key %s",
|
|
window_group, WINDOW_POSITION);
|
|
}
|
|
else if (pos)
|
|
{
|
|
// Prevent restoring coordinates if this would move the window off-screen
|
|
// If missing geom, use height=width=1 to make the intersection check work
|
|
GdkRectangle geometry{pos[0], pos[1], geom ? geom[0] : 1, geom ? geom[1] : 1};
|
|
if (intersects_some_monitor(geometry))
|
|
{
|
|
gtk_window_move(GTK_WINDOW(window), geometry.x, geometry.y);
|
|
auto priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
priv->pos[0] = geometry.x;
|
|
priv->pos[1] = geometry.y;
|
|
DEBUG("window (%p) position (%d,%d)", window, geometry.x, geometry.y);
|
|
}
|
|
else
|
|
{
|
|
DEBUG("position (%d,%d), size %dx%d is offscreen; will not move",
|
|
geometry.x, geometry.y, geometry.width, geometry.height);
|
|
}
|
|
}
|
|
g_free(geom);
|
|
g_free(pos);
|
|
|
|
gboolean max = g_key_file_get_boolean(data->key_file, window_group,
|
|
WINDOW_MAXIMIZED, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_MAXIMIZED, error->message);
|
|
g_error_free(error);
|
|
error = nullptr;
|
|
}
|
|
else if (max)
|
|
{
|
|
gtk_window_maximize(GTK_WINDOW(window));
|
|
}
|
|
}
|
|
|
|
/** Restore all the pages in a given window. This function restores
|
|
* all the window specific attributes, then calls a helper function
|
|
* to restore all the pages that are contained in the window.
|
|
*
|
|
* @param window The GncMainWindow whose pages should be restored.
|
|
*
|
|
* @param data A data structure containing state about the
|
|
* window/page restoration process. */
|
|
static void
|
|
gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GAction *action;
|
|
gint *order;
|
|
gsize length;
|
|
gsize page_start, page_count, i;
|
|
GError *error = nullptr;
|
|
GSList *added_page_offsets = nullptr;
|
|
gint offset = 0;
|
|
|
|
/* Setup */
|
|
ENTER("window %p, data %p (key file %p, window %d)",
|
|
window, data, data->key_file, data->window_num);
|
|
gchar *window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);
|
|
|
|
/* Deal with the uncommon case that the state file defines a window
|
|
* but no pages. An example to get in such a situation can be found
|
|
* here: https://bugs.gnucash.org/show_bug.cgi?id=436479#c3
|
|
* If this happens on the first window, we will open an account hierarchy
|
|
* to avoid confusing the user by presenting a completely empty window.
|
|
* If it happens on a later window, we'll just skip restoring that window.
|
|
*/
|
|
if (!g_key_file_has_group (data->key_file, window_group) ||
|
|
!g_key_file_has_key (data->key_file, window_group, WINDOW_PAGECOUNT, &error))
|
|
{
|
|
if (window)
|
|
{
|
|
gnc_main_window_restore_default_state (window);
|
|
PINFO ("saved state had an empty first main window\n"
|
|
"an account hierarchy page was added automatically to avoid confusion");
|
|
}
|
|
else
|
|
PINFO ("saved state had an empty main window, skipping restore");
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
/* Get this window's notebook info */
|
|
page_count = g_key_file_get_integer(data->key_file,
|
|
window_group, WINDOW_PAGECOUNT, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_PAGECOUNT, error->message);
|
|
goto cleanup;
|
|
}
|
|
if (page_count == 0)
|
|
{
|
|
/* Should never happen, but has during alpha testing. Having this
|
|
* check doesn't hurt anything. */
|
|
goto cleanup;
|
|
}
|
|
page_start = g_key_file_get_integer(data->key_file,
|
|
window_group, WINDOW_FIRSTPAGE, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_FIRSTPAGE, error->message);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Build a window if we don't already have one */
|
|
if (window == nullptr)
|
|
{
|
|
DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
|
|
DEBUG("active_windows %p.", active_windows);
|
|
if (active_windows)
|
|
DEBUG("first window %p.", active_windows->data);
|
|
window = gnc_main_window_new();
|
|
}
|
|
|
|
/* Get the window coordinates, etc. */
|
|
set_window_geometry(window, data, window_group);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
// need to add the accelerator keys
|
|
gnc_add_accelerator_keys_for_menu (GTK_WIDGET(priv->menubar), priv->menubar_model, priv->accel_group);
|
|
|
|
/* Common view menu items */
|
|
action = gnc_main_window_find_action (window, "ViewToolbarAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
gboolean visible = g_variant_get_boolean (state);
|
|
gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
|
|
TOOLBAR_VISIBLE, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("error reading group %s key %s: %s",
|
|
window_group, TOOLBAR_VISIBLE, error->message);
|
|
g_error_free (error);
|
|
error = nullptr;
|
|
}
|
|
else if (visible != desired_visibility)
|
|
{
|
|
g_action_activate (action, nullptr);
|
|
}
|
|
g_variant_unref (state);
|
|
}
|
|
|
|
action = gnc_main_window_find_action (window, "ViewSummaryAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
gboolean visible = g_variant_get_boolean (state);
|
|
gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
|
|
SUMMARYBAR_VISIBLE, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("error reading group %s key %s: %s",
|
|
window_group, SUMMARYBAR_VISIBLE, error->message);
|
|
g_error_free (error);
|
|
error = nullptr;
|
|
}
|
|
else if (visible != desired_visibility)
|
|
{
|
|
g_action_activate (action, nullptr);
|
|
}
|
|
g_variant_unref (state);
|
|
}
|
|
|
|
action = gnc_main_window_find_action (window, "ViewStatusbarAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
gboolean visible = g_variant_get_boolean (state);
|
|
gboolean desired_visibility = g_key_file_get_boolean (data->key_file, window_group,
|
|
STATUSBAR_VISIBLE, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("error reading group %s key %s: %s",
|
|
window_group, STATUSBAR_VISIBLE, error->message);
|
|
g_error_free (error);
|
|
error = nullptr;
|
|
}
|
|
else if (visible != desired_visibility)
|
|
{
|
|
g_action_activate (action, nullptr);
|
|
}
|
|
g_variant_unref (state);
|
|
}
|
|
priv->restoring_pages = TRUE;
|
|
/* Now populate the window with pages. */
|
|
for (i = 0; i < page_count; i++)
|
|
{
|
|
data->page_offset = page_start;
|
|
data->page_num = i;
|
|
gboolean page_added = gnc_main_window_restore_page (window, data);
|
|
|
|
if (!page_added) // if page not added, increase offset to compensate
|
|
{
|
|
offset ++;
|
|
added_page_offsets = g_slist_append (added_page_offsets,
|
|
GINT_TO_POINTER(-1));
|
|
}
|
|
else
|
|
added_page_offsets = g_slist_append (added_page_offsets,
|
|
GINT_TO_POINTER(offset));
|
|
|
|
/* give the page a chance to display */
|
|
while (gtk_events_pending ())
|
|
gtk_main_iteration ();
|
|
}
|
|
priv->restoring_pages = FALSE;
|
|
/* Restore page ordering within the notebook. Use +1 notation so the
|
|
* numbers in the page order match the page sections, at least for
|
|
* the one window case. */
|
|
order = g_key_file_get_integer_list (data->key_file, window_group,
|
|
WINDOW_PAGEORDER, &length, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
window_group, WINDOW_PAGEORDER, error->message);
|
|
g_error_free(error);
|
|
error = nullptr;
|
|
}
|
|
else if (length != page_count)
|
|
{
|
|
g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %" G_GSIZE_FORMAT,
|
|
window_group, WINDOW_PAGEORDER, length, page_count);
|
|
}
|
|
else
|
|
{
|
|
/* Dump any list that might exist */
|
|
g_list_free(priv->usage_order);
|
|
priv->usage_order = nullptr;
|
|
|
|
gint default_page_position = -1;
|
|
|
|
/* Now rebuild the list from the key file, skipping pages not added */
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
gint zero_based_page_number = order[i] - 1;
|
|
|
|
gint offset = GPOINTER_TO_INT(g_slist_nth_data (added_page_offsets,
|
|
zero_based_page_number));
|
|
|
|
if (offset == -1)
|
|
continue;
|
|
|
|
gpointer page = g_list_nth_data (priv->installed_pages,
|
|
zero_based_page_number - offset);
|
|
|
|
if (default_page_position == -1)
|
|
default_page_position = zero_based_page_number - offset;
|
|
|
|
if (page)
|
|
priv->usage_order = g_list_append (priv->usage_order, page);
|
|
}
|
|
gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
|
|
default_page_position);
|
|
|
|
g_signal_emit_by_name (window, "page_changed",
|
|
g_list_nth_data (priv->usage_order, 0));
|
|
}
|
|
if (order)
|
|
{
|
|
g_free(order);
|
|
}
|
|
|
|
LEAVE("window %p", window);
|
|
cleanup:
|
|
g_slist_free (added_page_offsets);
|
|
if (error)
|
|
g_error_free(error);
|
|
g_free(window_group);
|
|
if (window)
|
|
gtk_widget_show (GTK_WIDGET(window));
|
|
}
|
|
|
|
void
|
|
gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
|
|
{
|
|
gint i, window_count;
|
|
GError *error = nullptr;
|
|
GncMainWindowSaveData data;
|
|
|
|
/* We use the same struct for reading and for writing, so we cast
|
|
away the const. */
|
|
data.key_file = (GKeyFile *) keyfile;
|
|
window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
|
|
WINDOW_COUNT, &error);
|
|
if (error)
|
|
{
|
|
g_warning("error reading group %s key %s: %s",
|
|
STATE_FILE_TOP, WINDOW_COUNT, error->message);
|
|
g_error_free(error);
|
|
LEAVE("can't read count");
|
|
return;
|
|
}
|
|
|
|
/* Restore all state information on the open windows. Window
|
|
numbers in state file are 1-based. GList indices are 0-based. */
|
|
gnc_set_busy_cursor (nullptr, TRUE);
|
|
for (i = 0; i < window_count; i++)
|
|
{
|
|
data.window_num = i;
|
|
auto window{static_cast<GncMainWindow*>(g_list_nth_data(active_windows,
|
|
i))};
|
|
gnc_main_window_restore_window(window, &data);
|
|
}
|
|
gnc_unset_busy_cursor (nullptr);
|
|
|
|
statusbar_notification_lastmodified();
|
|
}
|
|
|
|
void
|
|
gnc_main_window_restore_default_state (GncMainWindow *window)
|
|
{
|
|
GAction *action;
|
|
|
|
/* The default state should be to have an Account Tree page open
|
|
* in the window. */
|
|
DEBUG("no saved state file");
|
|
if (!window)
|
|
window = static_cast<GncMainWindow*>(g_list_nth_data(active_windows, 0));
|
|
gtk_widget_show (GTK_WIDGET(window));
|
|
action = gnc_main_window_find_action_in_group (window,
|
|
"gnc-plugin-account-tree-actions",
|
|
"ViewAccountTreeAction");
|
|
g_action_activate (action, nullptr);
|
|
}
|
|
|
|
/** Save the state of a single page to a disk. This function handles
|
|
* all the common tasks such as saving the page type and name, and
|
|
* anything else that might be common to all pages. It then calls a
|
|
* page specific function to save the actual page.
|
|
*
|
|
* @param page The GncPluginPage whose state should be saved.
|
|
*
|
|
* @param data A data structure containing state about the
|
|
* window/page saving process. */
|
|
static void
|
|
gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
|
|
{
|
|
gchar *page_group;
|
|
const gchar *plugin_name, *page_name;
|
|
|
|
ENTER("page %p, data %p (key file %p, window %d, page %d)",
|
|
page, data, data->key_file, data->window_num, data->page_num);
|
|
plugin_name = gnc_plugin_page_get_plugin_name(page);
|
|
page_name = gnc_plugin_page_get_page_name(page);
|
|
if (!plugin_name || !page_name)
|
|
{
|
|
LEAVE("not saving invalid page");
|
|
return;
|
|
}
|
|
page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
|
|
g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
|
|
g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
|
|
|
|
gnc_plugin_page_save_page(page, data->key_file, page_group);
|
|
g_free(page_group);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Saves all the pages in a single window to a disk. This function
|
|
* saves all the window specific attributes, then calls a helper
|
|
* function to save all the pages that are contained in the window.
|
|
*
|
|
* @param window The GncMainWindow whose pages should be saved.
|
|
*
|
|
* @param data A data structure containing state about the
|
|
* window/page saving process. */
|
|
static void
|
|
gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GAction *action;
|
|
gint i, num_pages, coords[4], *order;
|
|
gboolean maximized, minimized, visible = true;
|
|
gchar *window_group;
|
|
|
|
/* Setup */
|
|
ENTER("window %p, data %p (key file %p, window %d)",
|
|
window, data, data->key_file, data->window_num);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
/* Check for bogus window structures. */
|
|
num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
|
|
if (0 == num_pages)
|
|
{
|
|
LEAVE("empty window %p", window);
|
|
return;
|
|
}
|
|
|
|
/* Save this window's notebook info */
|
|
window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
|
|
g_key_file_set_integer(data->key_file, window_group,
|
|
WINDOW_PAGECOUNT, num_pages);
|
|
g_key_file_set_integer(data->key_file, window_group,
|
|
WINDOW_FIRSTPAGE, data->page_num);
|
|
|
|
/* Save page ordering within the notebook. Use +1 notation so the
|
|
* numbers in the page order match the page sections, at least for
|
|
* the one window case. */
|
|
order = static_cast<int*>(g_malloc(sizeof(gint) * num_pages));
|
|
for (i = 0; i < num_pages; i++)
|
|
{
|
|
gpointer page = g_list_nth_data(priv->usage_order, i);
|
|
order[i] = g_list_index(priv->installed_pages, page) + 1;
|
|
}
|
|
g_key_file_set_integer_list(data->key_file, window_group,
|
|
WINDOW_PAGEORDER, order, num_pages);
|
|
g_free(order);
|
|
|
|
/* Save the window coordinates, etc. */
|
|
gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
|
|
gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
|
|
maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
|
|
& GDK_WINDOW_STATE_MAXIMIZED) != 0;
|
|
minimized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
|
|
& GDK_WINDOW_STATE_ICONIFIED) != 0;
|
|
|
|
if (minimized)
|
|
{
|
|
gint *pos = priv->pos;
|
|
g_key_file_set_integer_list(data->key_file, window_group,
|
|
WINDOW_POSITION, &pos[0], 2);
|
|
DEBUG("window minimized (%p) position (%d,%d)", window, pos[0], pos[1]);
|
|
}
|
|
else
|
|
g_key_file_set_integer_list(data->key_file, window_group,
|
|
WINDOW_POSITION, &coords[0], 2);
|
|
g_key_file_set_integer_list(data->key_file, window_group,
|
|
WINDOW_GEOMETRY, &coords[2], 2);
|
|
g_key_file_set_boolean(data->key_file, window_group,
|
|
WINDOW_MAXIMIZED, maximized);
|
|
DEBUG("window (%p) position (%d,%d), size %dx%d, %s", window, coords[0], coords[1],
|
|
coords[2], coords[3],
|
|
maximized ? "maximized" : "not maximized");
|
|
|
|
/* Common view menu items */
|
|
action = gnc_main_window_find_action (window, "ViewToolbarAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
visible = g_variant_get_boolean (state);
|
|
g_variant_unref (state);
|
|
}
|
|
g_key_file_set_boolean (data->key_file, window_group,
|
|
TOOLBAR_VISIBLE, visible);
|
|
action = gnc_main_window_find_action (window, "ViewSummaryAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
visible = g_variant_get_boolean (state);
|
|
g_variant_unref (state);
|
|
}
|
|
g_key_file_set_boolean (data->key_file, window_group,
|
|
SUMMARYBAR_VISIBLE, visible);
|
|
action = gnc_main_window_find_action (window, "ViewStatusbarAction");
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
visible = g_variant_get_boolean (state);
|
|
g_variant_unref (state);
|
|
}
|
|
g_key_file_set_boolean (data->key_file, window_group,
|
|
STATUSBAR_VISIBLE, visible);
|
|
|
|
/* Save individual pages in this window */
|
|
g_list_foreach (priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
|
|
|
|
g_free(window_group);
|
|
LEAVE("window %p", window);
|
|
}
|
|
|
|
void
|
|
gnc_main_window_save_all_windows(GKeyFile *keyfile)
|
|
{
|
|
GncMainWindowSaveData data;
|
|
|
|
/* Set up the iterator data structures */
|
|
data.key_file = keyfile;
|
|
data.window_num = 1;
|
|
data.page_num = 1;
|
|
|
|
g_key_file_set_integer(data.key_file,
|
|
STATE_FILE_TOP, WINDOW_COUNT,
|
|
g_list_length(active_windows));
|
|
/* Dump all state information on the open windows */
|
|
g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
|
|
}
|
|
|
|
|
|
gboolean
|
|
gnc_main_window_finish_pending (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GList *item;
|
|
|
|
g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
for (item = priv->installed_pages; item; item = g_list_next(item))
|
|
{
|
|
if (!gnc_plugin_page_finish_pending(static_cast<GncPluginPage*>(item->data)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
gnc_main_window_all_finish_pending (void)
|
|
{
|
|
const GList *windows, *item;
|
|
|
|
windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
|
|
for (item = windows; item; item = g_list_next(item))
|
|
{
|
|
if (!gnc_main_window_finish_pending(static_cast<GncMainWindow*>(item->data)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (gnc_gui_refresh_suspended ())
|
|
{
|
|
gnc_warning_dialog (nullptr, "%s", "An operation is still running, wait for it to complete before quitting.");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/** See if the page already exists. For each open window, look
|
|
* through the list of pages installed in that window and see if the
|
|
* specified page is there.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param page The page to search for.
|
|
*
|
|
* @return TRUE if the page is present in the window, FALSE otherwise.
|
|
*/
|
|
static gboolean
|
|
gnc_main_window_page_exists (GncPluginPage *page)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GList *walker;
|
|
|
|
for (walker = active_windows; walker; walker = g_list_next(walker))
|
|
{
|
|
auto window{static_cast<GncMainWindow*>(walker->data)};
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (g_list_find(priv->installed_pages, page))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean auto_save_countdown (GtkWidget *dialog)
|
|
{
|
|
GtkWidget *label;
|
|
gchar *timeoutstr = nullptr;
|
|
|
|
/* Stop count down if user closed the dialog since the last time we were called */
|
|
if (!GTK_IS_DIALOG (dialog))
|
|
return FALSE; /* remove timer */
|
|
|
|
/* Stop count down if count down text can't be updated */
|
|
label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
|
|
if (!GTK_IS_LABEL (label))
|
|
return FALSE; /* remove timer */
|
|
|
|
/* Protect against rolling over to MAXUINT */
|
|
if (secs_to_save)
|
|
--secs_to_save;
|
|
DEBUG ("Counting down: %d seconds", secs_to_save);
|
|
|
|
timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
|
|
gtk_label_set_text (GTK_LABEL (label), timeoutstr);
|
|
g_free (timeoutstr);
|
|
|
|
/* Count down reached 0. Save and close dialog */
|
|
if (!secs_to_save)
|
|
{
|
|
gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
|
|
return FALSE; /* remove timer */
|
|
}
|
|
|
|
/* Run another cycle */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/** This function prompts the user to save the file with a dialog that
|
|
* follows the HIG guidelines.
|
|
*
|
|
* @internal
|
|
*
|
|
* @returns This function returns TRUE if the user clicked the Cancel
|
|
* button. It returns FALSE if the closing of the window should
|
|
* continue.
|
|
*/
|
|
static gboolean
|
|
gnc_main_window_prompt_for_save (GtkWidget *window)
|
|
{
|
|
QofSession *session;
|
|
QofBook *book;
|
|
GtkWidget *dialog, *msg_area, *label;
|
|
gint response;
|
|
const gchar *filename, *tmp;
|
|
const gchar *title = _("Save changes to file %s before closing?");
|
|
/* This should be the same message as in gnc-file.c */
|
|
const gchar *message_hours =
|
|
_("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
|
|
const gchar *message_days =
|
|
_("If you don't save, changes from the past %d days and %d hours will be discarded.");
|
|
time64 oldest_change;
|
|
gint minutes, hours, days;
|
|
guint timer_source = 0;
|
|
if (!gnc_current_session_exist())
|
|
return FALSE;
|
|
session = gnc_get_current_session();
|
|
book = qof_session_get_book(session);
|
|
if (!qof_book_session_not_saved(book))
|
|
return FALSE;
|
|
filename = qof_session_get_url(session);
|
|
if (!strlen (filename))
|
|
filename = _("<unknown>");
|
|
if ((tmp = strrchr(filename, '/')) != nullptr)
|
|
filename = tmp + 1;
|
|
|
|
/* Remove any pending auto-save timeouts */
|
|
gnc_autosave_remove_timer(book);
|
|
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(window),
|
|
GTK_DIALOG_MODAL,
|
|
GTK_MESSAGE_WARNING,
|
|
GTK_BUTTONS_NONE,
|
|
title,
|
|
filename);
|
|
oldest_change = qof_book_get_session_dirty_time(book);
|
|
minutes = (gnc_time (nullptr) - oldest_change) / 60 + 1;
|
|
hours = minutes / 60;
|
|
minutes = minutes % 60;
|
|
days = hours / 24;
|
|
hours = hours % 24;
|
|
if (days > 0)
|
|
{
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
message_days, days, hours);
|
|
}
|
|
else if (hours > 0)
|
|
{
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
message_hours, hours, minutes);
|
|
}
|
|
else
|
|
{
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
ngettext("If you don't save, changes from the past %d minute will be discarded.",
|
|
"If you don't save, changes from the past %d minutes will be discarded.",
|
|
minutes), minutes);
|
|
}
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
|
|
_("Close _Without Saving"), GTK_RESPONSE_CLOSE,
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_Save"), GTK_RESPONSE_APPLY,
|
|
nullptr);
|
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
|
|
|
|
/* If requested by the user, add a timeout to the question to save automatically
|
|
* if the user doesn't answer after a chosen number of seconds.
|
|
*/
|
|
if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
|
|
{
|
|
gchar *timeoutstr = nullptr;
|
|
|
|
secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
|
|
timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
|
|
label = GTK_WIDGET(gtk_label_new (timeoutstr));
|
|
g_free (timeoutstr);
|
|
gtk_widget_show (label);
|
|
|
|
msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
|
|
gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
|
|
g_object_set (G_OBJECT (label), "xalign", 0.0, nullptr);
|
|
|
|
g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
|
|
timer_source = g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
|
|
}
|
|
|
|
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
|
if (timer_source)
|
|
g_source_remove (timer_source);
|
|
gtk_widget_destroy(dialog);
|
|
|
|
switch (response)
|
|
{
|
|
case GTK_RESPONSE_APPLY:
|
|
gnc_file_save (GTK_WINDOW (window));
|
|
return FALSE;
|
|
|
|
case GTK_RESPONSE_CLOSE:
|
|
qof_book_mark_session_saved(book);
|
|
return FALSE;
|
|
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_add_plugin (gpointer plugin,
|
|
gpointer window)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (GNC_IS_PLUGIN (plugin));
|
|
|
|
ENTER(" ");
|
|
gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
|
|
GNC_MAIN_WINDOW (window),
|
|
window_type);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_remove_plugin (gpointer plugin,
|
|
gpointer window)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (GNC_IS_PLUGIN (plugin));
|
|
|
|
ENTER(" ");
|
|
gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
|
|
GNC_MAIN_WINDOW (window),
|
|
window_type);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gnc_main_window_timed_quit (gpointer dummy)
|
|
{
|
|
if (gnc_file_save_in_progress())
|
|
return TRUE;
|
|
|
|
gnc_shutdown (0);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_main_window_quit(GncMainWindow *window)
|
|
{
|
|
QofSession *session;
|
|
gboolean needs_save, do_shutdown = TRUE;
|
|
if (gnc_current_session_exist())
|
|
{
|
|
session = gnc_get_current_session();
|
|
needs_save =
|
|
qof_book_session_not_saved(qof_session_get_book(session)) &&
|
|
!gnc_file_save_in_progress();
|
|
do_shutdown = !needs_save ||
|
|
(needs_save &&
|
|
!gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
|
|
}
|
|
if (do_shutdown)
|
|
{
|
|
GList *w, *next;
|
|
|
|
/* This is not a typical list iteration. There is a possibility
|
|
* that the window may be removed from the active_windows list so
|
|
* we have to cache the 'next' pointer before executing any code
|
|
* in the loop. */
|
|
for (w = active_windows; w; w = next)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncMainWindow *window = static_cast<GncMainWindow*>(w->data);
|
|
|
|
next = g_list_next (w);
|
|
|
|
window->window_quitting = TRUE; //set window_quitting on all windows
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
// if there are no pages destroy window
|
|
if (priv->installed_pages == NULL)
|
|
gtk_widget_destroy (GTK_WIDGET(window));
|
|
}
|
|
/* remove the preference callbacks from the main window */
|
|
gnc_main_window_remove_prefs (window);
|
|
g_timeout_add(250, gnc_main_window_timed_quit, nullptr);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_main_window_delete_event (GtkWidget *window,
|
|
GdkEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
static gboolean already_dead = FALSE;
|
|
|
|
if (already_dead)
|
|
return TRUE;
|
|
|
|
if (gnc_list_length_cmp (active_windows, 1) > 0)
|
|
{
|
|
gint response;
|
|
GtkWidget *dialog;
|
|
gchar *message = _("This window is closing and will not be restored.");
|
|
|
|
dialog = gtk_message_dialog_new (GTK_WINDOW (window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_NONE,
|
|
"%s", _("Close Window?"));
|
|
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
|
|
"%s", message);
|
|
|
|
gtk_dialog_add_buttons (GTK_DIALOG(dialog),
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_OK"), GTK_RESPONSE_YES,
|
|
(gchar *)NULL);
|
|
gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
|
|
response = gnc_dialog_run (GTK_DIALOG(dialog), GNC_PREF_WARN_CLOSING_WINDOW_QUESTION);
|
|
gtk_widget_destroy (dialog);
|
|
|
|
if (response == GTK_RESPONSE_CANCEL)
|
|
return TRUE;
|
|
}
|
|
|
|
if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
|
|
{
|
|
/* Don't close the window. */
|
|
return TRUE;
|
|
}
|
|
|
|
if (gnc_list_length_cmp (active_windows, 1) > 0)
|
|
return FALSE;
|
|
|
|
already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/** This function handles any event notifications from the engine.
|
|
* The only event it currently cares about is the deletion of a book.
|
|
* When a book is deleted, it runs through all installed pages
|
|
* looking for pages that reference the just (about to be?) deleted
|
|
* book. It closes any page it finds so there are no dangling
|
|
* references to the book.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param entity The guid the item being added, deleted, etc.
|
|
*
|
|
* @param type The type of the item being added, deleted, etc. This
|
|
* function only cares about a type of GNC_ID_BOOK.
|
|
*
|
|
* @param event_type The type of the event. This function only cares
|
|
* about an event type of QOF_EVENT_DESTROY.
|
|
*
|
|
* @param user_data A pointer to the window data structure.
|
|
*/
|
|
static void
|
|
gnc_main_window_event_handler (QofInstance *entity, QofEventId event_type,
|
|
gpointer user_data, gpointer event_data)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
GList *item, *next;
|
|
|
|
/* hard failures */
|
|
g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
|
|
|
|
/* soft failures */
|
|
if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
|
|
return;
|
|
if (event_type != QOF_EVENT_DESTROY)
|
|
return;
|
|
|
|
ENTER("entity %p, event %d, window %p, event data %p",
|
|
entity, event_type, user_data, event_data);
|
|
window = GNC_MAIN_WINDOW(user_data);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
/* This is not a typical list iteration. We're removing while
|
|
* we iterate, so we have to cache the 'next' pointer before
|
|
* executing any code in the loop. */
|
|
for (item = priv->installed_pages; item; item = next)
|
|
{
|
|
next = g_list_next(item);
|
|
page = GNC_PLUGIN_PAGE(item->data);
|
|
if (gnc_plugin_page_has_book (page, (QofBook *)entity))
|
|
gnc_main_window_close_page (page);
|
|
}
|
|
|
|
if (GTK_IS_WIDGET(window) && window->window_quitting)
|
|
gtk_widget_destroy (GTK_WIDGET(window));
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Generate a title for this window based upon the Gnome Human
|
|
* Interface Guidelines, v2.0. This title will be used as both the
|
|
* window title and the title of the "Window" menu item associated
|
|
* with the window.
|
|
*
|
|
* As a side-effect, the save action is set sensitive iff the book
|
|
* is dirty, and the immutable_page_actions are set sensitive iff the page is
|
|
* mutable.
|
|
*
|
|
* @param window The window whose title should be generated.
|
|
*
|
|
* @return The title for the window. It is the callers
|
|
* responsibility to free this string.
|
|
*
|
|
* @internal
|
|
*/
|
|
static gchar *
|
|
gnc_main_window_generate_title (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
QofBook *book;
|
|
gboolean immutable;
|
|
gchar *filename = nullptr;
|
|
const gchar *uri = nullptr;
|
|
const gchar *dirty = "";
|
|
const gchar *readonly_text = nullptr;
|
|
gchar *readonly;
|
|
gchar *title;
|
|
|
|
if (gnc_current_session_exist())
|
|
{
|
|
uri = qof_session_get_url (gnc_get_current_session ());
|
|
book = gnc_get_current_book();
|
|
if (qof_book_session_not_saved (book))
|
|
dirty = "*";
|
|
if (qof_book_is_readonly(book))
|
|
{
|
|
/* Translators: This string is shown in the window title if this
|
|
document is, well, read-only. */
|
|
readonly_text = _("(read-only)");
|
|
}
|
|
}
|
|
readonly = (readonly_text != nullptr)
|
|
? g_strdup_printf(" %s", readonly_text)
|
|
: g_strdup("");
|
|
|
|
if (!uri || g_strcmp0 (uri, "") == 0)
|
|
filename = g_strdup(_("Unsaved Book"));
|
|
else
|
|
{
|
|
if (gnc_uri_targets_local_fs (uri))
|
|
{
|
|
/* The filename is a true file.
|
|
The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
|
|
gchar *path = gnc_uri_get_path ( uri );
|
|
filename = g_path_get_basename ( path );
|
|
g_free ( path );
|
|
}
|
|
else
|
|
{
|
|
/* The filename is composed of database connection parameters.
|
|
For this we will show access_method://username@database[:port] */
|
|
filename = gnc_uri_normalize_uri (uri, FALSE);
|
|
}
|
|
}
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page = priv->current_page;
|
|
if (page)
|
|
{
|
|
/* The Gnome HIG 2.0 recommends the application name not be used. (p16)
|
|
but several developers prefer to use it anyway. */
|
|
title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
|
|
gnc_plugin_page_get_page_name(page));
|
|
}
|
|
else
|
|
{
|
|
title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
|
|
}
|
|
/* Update the menus based upon whether this is an "immutable" page. */
|
|
immutable = page &&
|
|
g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
|
|
gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
|
|
immutable_page_actions,
|
|
!immutable);
|
|
/* Trigger sensitivity updtates of other actions such as Save/Revert */
|
|
g_signal_emit_by_name (window, "page_changed", page);
|
|
g_free( filename );
|
|
g_free(readonly);
|
|
|
|
return title;
|
|
}
|
|
|
|
|
|
/** Update the title bar on the specified window. This routine uses
|
|
* the gnc_main_window_generate_title() function to create the title.
|
|
* It is called whenever the user switched pages in a window, as the
|
|
* title includes the name of the current page.
|
|
*
|
|
* @param window The window whose title should be updated.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_update_title (GncMainWindow *window)
|
|
{
|
|
gchar *title;
|
|
|
|
title = gnc_main_window_generate_title(window);
|
|
gtk_window_set_title(GTK_WINDOW(window), title);
|
|
g_free(title);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_update_all_titles (void)
|
|
{
|
|
g_list_foreach(active_windows,
|
|
(GFunc)gnc_main_window_update_title,
|
|
nullptr);
|
|
}
|
|
|
|
/* Callback function invoked when the user clicks on a GtkNotebook tab.
|
|
*
|
|
* This function is needed to make it possible to close a tab
|
|
* when it's clicked using the middle mouse button;
|
|
* there does not seem to be a way to do this with GtkNotebook natively.
|
|
*
|
|
* @param widget The event box in the tab, which was clicked.
|
|
*
|
|
* @param event The event parameter describing where on the screen
|
|
* the mouse was pointing when clicked, type of click, modifiers,
|
|
* etc.
|
|
*
|
|
* @param page This is the GncPluginPage corresponding to the tab.
|
|
*
|
|
* @return Returns TRUE if this was a middle-click, meaning Gnucash
|
|
* handled the click.
|
|
*/
|
|
static gboolean
|
|
gnc_tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, GncPluginPage *page) {
|
|
if (event->type == GDK_BUTTON_PRESS && event->button == 2)
|
|
{
|
|
gnc_main_window_close_page(page);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_book_dirty_cb (QofBook *book,
|
|
gboolean dirty,
|
|
gpointer user_data)
|
|
{
|
|
gnc_main_window_update_all_titles();
|
|
|
|
/* Auto-save feature */
|
|
gnc_autosave_dirty_handler(book, dirty);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_attach_to_book (QofSession *session)
|
|
{
|
|
QofBook *book;
|
|
|
|
g_return_if_fail(session);
|
|
|
|
book = qof_session_get_book(session);
|
|
qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, nullptr);
|
|
gnc_main_window_update_all_titles();
|
|
#ifndef MAC_INTEGRATION
|
|
gnc_main_window_update_all_menu_items();
|
|
#endif
|
|
}
|
|
|
|
static guint gnc_statusbar_notification_messageid = 0;
|
|
//#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
|
|
#ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
|
|
/* Removes the statusbar notification again that has been pushed to the
|
|
* statusbar by generate_statusbar_lastmodified_message. */
|
|
static gboolean statusbar_notification_off(gpointer user_data_unused)
|
|
{
|
|
GncMainWindow *mainwindow = GNC_MAIN_WINDOW (gnc_ui_get_main_window (nullptr));
|
|
//g_warning("statusbar_notification_off\n");
|
|
if (gnc_statusbar_notification_messageid == 0)
|
|
return FALSE;
|
|
|
|
if (mainwindow)
|
|
{
|
|
GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
|
|
gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
|
|
gnc_statusbar_notification_messageid = 0;
|
|
}
|
|
else
|
|
{
|
|
g_warning("oops, no GncMainWindow obtained\n");
|
|
}
|
|
return FALSE; // should not be called again
|
|
}
|
|
#endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
|
|
|
|
/* Creates a statusbar message stating the last modification time of the opened
|
|
* data file. */
|
|
static gchar *generate_statusbar_lastmodified_message()
|
|
{
|
|
gchar *message = nullptr;
|
|
const gchar *uri = nullptr;
|
|
|
|
if (gnc_current_session_exist())
|
|
{
|
|
uri = qof_session_get_url (gnc_get_current_session ());
|
|
}
|
|
|
|
if (!(uri && strlen (uri)))
|
|
return nullptr;
|
|
else
|
|
{
|
|
if (gnc_uri_targets_local_fs (uri))
|
|
{
|
|
/* The filename is a true file. */
|
|
gchar *filepath = gnc_uri_get_path ( uri );
|
|
gchar *filename = g_path_get_basename ( filepath );
|
|
GFile *file = g_file_new_for_uri (uri);
|
|
GFileInfo *info = g_file_query_info (file,
|
|
G_FILE_ATTRIBUTE_TIME_MODIFIED,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL, NULL);
|
|
|
|
if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
|
|
{
|
|
// Access the mtime information through stat(2)
|
|
struct stat statbuf;
|
|
int r = stat(filepath, &statbuf);
|
|
if (r == 0)
|
|
{
|
|
/* Translators: This is the date and time that is shown in
|
|
the status bar after opening a file: The date and time of
|
|
last modification. The string is a format string using
|
|
boost::date_time's format flags, see the boost docs for an
|
|
explanation of the modifiers. */
|
|
char *time_string = gnc_print_time64(statbuf.st_mtime,
|
|
_("Last modified on %a, %b %d, %Y at %I:%M %p"));
|
|
//g_warning("got time %ld, str=%s\n", mtime, time_string);
|
|
/* Translators: This message appears in the status bar after opening the file. */
|
|
message = g_strdup_printf(_("File %s opened. %s"),
|
|
filename, time_string);
|
|
free(time_string);
|
|
}
|
|
else
|
|
{
|
|
g_warning("Unable to read mtime for file %s\n", filepath);
|
|
// message is still nullptr
|
|
}
|
|
}
|
|
g_free(filename);
|
|
g_free(filepath);
|
|
g_object_unref (info);
|
|
g_object_unref (file);
|
|
}
|
|
// If the URI is not a file but a database, we can maybe also show
|
|
// something useful, but I have no idea how to obtain this information.
|
|
}
|
|
return message;
|
|
}
|
|
|
|
static void
|
|
statusbar_notification_lastmodified()
|
|
{
|
|
// First look up the first GncMainWindow to set the statusbar there
|
|
GList *iter;
|
|
GtkWidget *widget = nullptr;
|
|
for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
|
|
iter = g_list_next(iter))
|
|
{
|
|
widget = static_cast<GtkWidget*>(iter->data);
|
|
}
|
|
if (widget && GNC_IS_MAIN_WINDOW(widget))
|
|
{
|
|
// Ok, we found a mainwindow where we can set a statusbar message
|
|
GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
|
|
GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
|
|
|
|
gchar *msg = generate_statusbar_lastmodified_message();
|
|
if (msg)
|
|
{
|
|
gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
|
|
}
|
|
g_free(msg);
|
|
|
|
#ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
|
|
// Also register a timeout callback to remove that statusbar
|
|
// notification again after 10 seconds
|
|
g_timeout_add(10 * 1000, statusbar_notification_off, nullptr); // maybe not needed anyway?
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
|
|
}
|
|
}
|
|
|
|
|
|
/** This data structure is used to describe the requested state of a
|
|
* GAction, and is used to pass data among several functions. */
|
|
struct menu_update
|
|
{
|
|
/** The name of the GAction to be updated. */
|
|
gchar *action_name;
|
|
|
|
/** The new label for this GAction. */
|
|
gchar *label;
|
|
|
|
/** Whether or not the GAction should be visible. */
|
|
gboolean visible;
|
|
|
|
/** Index number in active windows list */
|
|
gint index;
|
|
};
|
|
|
|
#ifndef MAC_INTEGRATION
|
|
/** Update the label on the menu item specified by the GAction in the
|
|
* specified window. This action is displayed as a menu item in the
|
|
* "Windows" menu. This function will end up being called whenever the
|
|
* front page is changed in any window, or whenever a window is added
|
|
* or deleted.
|
|
*
|
|
* @param window The window whose menu item should be updated.
|
|
*
|
|
* @param data A data structure containing the name of the GAction and
|
|
* describing the new state for this action.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_update_one_menu_action (GncMainWindow *window,
|
|
struct menu_update *data)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
|
|
GMenuItem *item;
|
|
gint pos;
|
|
|
|
ENTER("window %p, action %s, label %s, index %d, visible %d", window,
|
|
data->action_name, data->label, data->index, data->visible);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
gsm->search_action_label = nullptr;
|
|
gsm->search_action_name = "WindowsPlaceholder1"; // placeholder
|
|
gsm->search_action_target = nullptr;
|
|
|
|
if (!gnc_menubar_model_find_item (priv->menubar_model, gsm))
|
|
{
|
|
LEAVE("Could not find placeholder 'WindowsPlaceholder1' for windows entries");
|
|
g_free (gsm);
|
|
return;
|
|
}
|
|
|
|
pos = gsm->index + data->index + 1;
|
|
|
|
if (!data->visible)
|
|
{
|
|
if (pos < g_menu_model_get_n_items (gsm->model))
|
|
g_menu_remove (G_MENU(gsm->model), pos);
|
|
|
|
g_free (gsm);
|
|
LEAVE(" ");
|
|
return;
|
|
}
|
|
|
|
item = g_menu_item_new (data->label, "mainwin.WindowAction");
|
|
g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_TARGET, "i", data->index);
|
|
|
|
if (pos < g_menu_model_get_n_items (gsm->model))
|
|
g_menu_remove (G_MENU(gsm->model), pos);
|
|
g_menu_insert_item (G_MENU(gsm->model), pos, item);
|
|
g_object_unref (item);
|
|
|
|
g_free (gsm);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
/** Update the window selection GtkRadioAction for a specific window.
|
|
* This is fairly simple since the windows are listed in the same
|
|
* order that they appear in the active_windows list, so the index
|
|
* from the window list is used to generate the name of the action.
|
|
* If the code is ever changed to allow more than ten open windows in
|
|
* the menu, then the actions in the menu will need to be dynamically
|
|
* generated/deleted and it gets harder.
|
|
*
|
|
* @param window The window whose menu item should be updated.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_update_radio_button (GncMainWindow *window)
|
|
{
|
|
GAction *action;
|
|
gsize index;
|
|
|
|
ENTER("window %p", window);
|
|
|
|
/* Show the new entry in all windows. */
|
|
index = g_list_index (active_windows, window);
|
|
|
|
if (index >= gnc_main_window_max_number)
|
|
{
|
|
LEAVE("window %" G_GSIZE_FORMAT ", only %d actions", index, gnc_main_window_max_number);
|
|
return;
|
|
}
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP(window),
|
|
"WindowAction");
|
|
|
|
/* Block the signal so as not to affect window ordering (top to
|
|
* bottom) on the screen */
|
|
g_signal_handlers_block_by_func (G_OBJECT(action),
|
|
(gpointer)gnc_main_window_cmd_window_raise,
|
|
window);
|
|
|
|
DEBUG("blocked signal on action %p, window %p", action, window);
|
|
g_action_change_state (G_ACTION(action), g_variant_new_int32 (index));
|
|
|
|
g_signal_handlers_unblock_by_func (G_OBJECT(action),
|
|
(gpointer)gnc_main_window_cmd_window_raise,
|
|
window);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
/** In every window that the user has open, update the "Window" menu
|
|
* item that points to the specified window. This keeps the "Window"
|
|
* menu items consistent across all open windows.
|
|
*
|
|
* This function is called whenever the user switches pages in a
|
|
* window, or whenever a window is added or deleted.
|
|
*
|
|
* @param window The window whose menu item should be updated in all
|
|
* open windows.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_update_menu_item (GncMainWindow *window)
|
|
{
|
|
struct menu_update data;
|
|
gchar **strings, *title, *expanded;
|
|
gsize index;
|
|
|
|
ENTER("window %p", window);
|
|
|
|
index = g_list_index (active_windows, window);
|
|
|
|
if (index >= gnc_main_window_max_number)
|
|
{
|
|
LEAVE("skip window %" G_GSIZE_FORMAT " (only %d entries)", index, gnc_main_window_max_number);
|
|
return;
|
|
}
|
|
|
|
/* Figure out the label name. Add the accelerator if possible. */
|
|
title = gnc_main_window_generate_title (window);
|
|
strings = g_strsplit (title, "_", 0);
|
|
g_free (title);
|
|
expanded = g_strjoinv ("__", strings);
|
|
if (index < gnc_main_window_max_number)
|
|
{
|
|
data.label = g_strdup_printf ("_%" G_GSIZE_FORMAT " %s", (index + 1) % 10, expanded);
|
|
g_free (expanded);
|
|
}
|
|
else
|
|
{
|
|
data.label = expanded;
|
|
}
|
|
g_strfreev (strings);
|
|
|
|
data.visible = TRUE;
|
|
data.action_name = g_strdup_printf ("Window%" G_GSIZE_FORMAT "Action", index);
|
|
data.index = index;
|
|
|
|
g_list_foreach (active_windows,
|
|
(GFunc)gnc_main_window_update_one_menu_action,
|
|
&data);
|
|
|
|
g_free (data.action_name);
|
|
g_free (data.label);
|
|
|
|
LEAVE(" ");
|
|
}
|
|
#endif /* !MAC_INTEGRATION */
|
|
|
|
/** Update all menu entries for all window menu items in all windows.
|
|
* This function is called whenever a window is added or deleted.
|
|
* The worst case scenario is where the user has deleted the first
|
|
* window, so every single visible item needs to be updated.
|
|
*
|
|
* @internal
|
|
*/
|
|
|
|
#ifndef MAC_INTEGRATION
|
|
static void
|
|
gnc_main_window_update_all_menu_items (void)
|
|
{
|
|
struct menu_update data;
|
|
|
|
ENTER("");
|
|
/* First update the entries for all existing windows */
|
|
g_list_foreach (active_windows,
|
|
(GFunc)gnc_main_window_update_menu_item,
|
|
nullptr);
|
|
|
|
g_list_foreach (active_windows,
|
|
(GFunc)gnc_main_window_update_radio_button,
|
|
nullptr);
|
|
|
|
/* Now hide any entries that aren't being used. */
|
|
data.visible = FALSE;
|
|
// need i to descend from gnc_main_window_max_number
|
|
for (gsize i = gnc_main_window_max_number - 1; i > 0 && i >= g_list_length (active_windows); i--)
|
|
{
|
|
data.index = i;
|
|
data.action_name = g_strdup_printf ("Window%dAction", data.index);
|
|
data.label = g_strdup_printf ("mywin%" G_GSIZE_FORMAT, i % 10);
|
|
|
|
g_list_foreach (active_windows,
|
|
(GFunc)gnc_main_window_update_one_menu_action,
|
|
&data);
|
|
|
|
g_free (data.action_name);
|
|
g_free (data.label);
|
|
}
|
|
LEAVE(" ");
|
|
}
|
|
#endif /* !MAC_INTEGRATION */
|
|
|
|
/** Show/hide the close box on the tab of a notebook page. This
|
|
* function first checks to see if the specified page has a close
|
|
* box, and if so, sets its visibility to the requested state.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param page The GncPluginPage whose notebook tab should be updated.
|
|
*
|
|
* @param new_value A pointer to the boolean that indicates whether
|
|
* or not the close button should be visible.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
|
|
gpointer user_data)
|
|
{
|
|
auto new_value{static_cast<gboolean*>(user_data)};
|
|
ENTER("page %p, visible %d", page, *new_value);
|
|
auto close_button{static_cast<GtkWidget*>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON))};
|
|
if (!close_button)
|
|
{
|
|
LEAVE("no close button");
|
|
return;
|
|
}
|
|
|
|
if (*new_value)
|
|
gtk_widget_show (close_button);
|
|
else
|
|
gtk_widget_hide (close_button);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Show/hide the close box on all pages in all windows. This function
|
|
* calls gnc_main_window_update_tab_close() for each plugin page in the
|
|
* application.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param prefs Unused.
|
|
*
|
|
* @param pref Unused.
|
|
*
|
|
* @param user_data Unused.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
|
|
{
|
|
gboolean new_value;
|
|
|
|
ENTER(" ");
|
|
new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
|
|
gnc_main_window_foreach_page(
|
|
gnc_main_window_update_tab_close_one_page,
|
|
&new_value);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Show/hide the account color on the tab of a notebook page.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param page The GncPluginPage whose notebook tab should be updated.
|
|
*
|
|
* @param user_data GncMainWindow.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *color_string;
|
|
|
|
ENTER("page %p", page);
|
|
color_string = gnc_plugin_page_get_page_color(page);
|
|
main_window_update_page_color (page, color_string);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Show/hide the account color on tabs.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param prefs Unused.
|
|
*
|
|
* @param pref Name of the preference that was changed.
|
|
*
|
|
* @param user_data GncMainWindow.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
|
|
{
|
|
ENTER(" ");
|
|
g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
|
|
auto window{static_cast<GncMainWindow*>(user_data)};
|
|
auto priv{GNC_MAIN_WINDOW_GET_PRIVATE(window)};
|
|
if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
|
|
priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
|
|
gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** This data structure allows the passing of the tab width and
|
|
* whether the tab layout is on the left or right.
|
|
*/
|
|
typedef struct
|
|
{
|
|
gint tab_width;
|
|
gboolean tabs_left_right;
|
|
} TabWidth;
|
|
|
|
static TabWidth *
|
|
populate_tab_width_struct (void)
|
|
{
|
|
TabWidth *tw;
|
|
|
|
tw = g_new0 (TabWidth, 1);
|
|
tw->tab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
|
|
tw->tabs_left_right = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT) ||
|
|
gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT);
|
|
|
|
return tw;
|
|
}
|
|
|
|
/** Set the tab label ellipsize value.
|
|
* When the tabs are on the left or right, the label width is set to
|
|
* the tab_width value. Doing this maintains a steady notepad header
|
|
* width for the tabs.
|
|
*
|
|
* When the tabs are on the top or bottom, the label width is set to
|
|
* the number of characters when shorter than tab_width so they take
|
|
* up less room.
|
|
*
|
|
* The special check for a zero value handles the case where a user
|
|
* hasn't set a tab width and the preference default isn't detected.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param label GtkLabel for the tab.
|
|
*
|
|
* @param tab_width Tab width the user has set in preferences.
|
|
*
|
|
* @param tab_left_right Whether the tab layout is on the left or right.
|
|
*
|
|
*/
|
|
static void
|
|
gnc_main_window_set_tab_ellipsize (GtkWidget *label, gint tab_width, gboolean tab_left_right)
|
|
{
|
|
const gchar *lab_text = gtk_label_get_text (GTK_LABEL(label));
|
|
|
|
if (tab_width != 0)
|
|
{
|
|
gint text_length = g_utf8_strlen (lab_text, -1);
|
|
if (text_length < tab_width)
|
|
{
|
|
if (tab_left_right) // tabs position is left or right
|
|
gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
|
|
else // tabs position is top or bottom
|
|
gtk_label_set_width_chars (GTK_LABEL(label), text_length);
|
|
|
|
gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
|
|
}
|
|
else
|
|
{
|
|
gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
|
|
gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gtk_label_set_width_chars (GTK_LABEL(label), 15);
|
|
gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
|
|
}
|
|
}
|
|
|
|
|
|
/** Update the width of the label in the tab of a notebook page. This
|
|
* function adjusts both the width and the ellipsize mode so that the
|
|
* tab label looks correct.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param page The GncPluginPage whose notebook tab should be updated.
|
|
*
|
|
* @param new_value The new width of the label in the tab.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
|
|
gpointer user_data)
|
|
{
|
|
auto tw{static_cast<TabWidth*>(user_data)};
|
|
|
|
ENTER("page %p, tab width %d, tabs on left or right %d",
|
|
page, tw->tab_width, tw->tabs_left_right);
|
|
|
|
auto label{static_cast<GtkWidget *>(g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL))};
|
|
if (!label)
|
|
{
|
|
LEAVE("no label");
|
|
return;
|
|
}
|
|
gnc_main_window_set_tab_ellipsize (label, tw->tab_width, tw->tabs_left_right);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Update the tab label width in all pages in all windows. This function
|
|
* calls gnc_main_window_update_tab_width() for each plugin page in the
|
|
* application.
|
|
*
|
|
* @internal
|
|
*
|
|
* @param prefs Unused.
|
|
*
|
|
* @param pref Unused.
|
|
*
|
|
* @param user_data Unused.
|
|
*/
|
|
static void
|
|
gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
|
|
{
|
|
TabWidth *tw;
|
|
|
|
ENTER(" ");
|
|
|
|
tw = populate_tab_width_struct ();
|
|
|
|
gnc_main_window_foreach_page (gnc_main_window_update_tab_width_one_page, tw);
|
|
g_free (tw);
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* Tab Label Implementation *
|
|
************************************************************/
|
|
static gboolean
|
|
main_window_find_tab_items (GncMainWindow *window,
|
|
GncPluginPage *page,
|
|
GtkWidget **label_p,
|
|
GtkWidget **entry_p)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *tab_hbox, *widget, *tab_widget;
|
|
GList *children, *tmp;
|
|
|
|
ENTER("window %p, page %p, label_p %p, entry_p %p",
|
|
window, page, label_p, entry_p);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
*label_p = *entry_p = nullptr;
|
|
|
|
if (!page->notebook_page)
|
|
{
|
|
LEAVE("invalid notebook_page");
|
|
return FALSE;
|
|
}
|
|
|
|
tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
|
|
page->notebook_page);
|
|
|
|
// Walk through children to find the box containing label+entry
|
|
tab_hbox = tab_widget;
|
|
while (tab_hbox) {
|
|
if (g_strcmp0(gtk_widget_get_name(tab_hbox), "tab-content") == 0) {
|
|
break;
|
|
}
|
|
GList* _children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
|
|
tab_hbox = _children ? GTK_WIDGET(_children->data) : nullptr;
|
|
g_list_free(_children);
|
|
}
|
|
|
|
if (!GTK_IS_BOX(tab_hbox))
|
|
{
|
|
PWARN ("Unknown widget for tab label %p", tab_widget);
|
|
return FALSE;
|
|
}
|
|
|
|
children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
|
|
for (tmp = children; tmp; tmp = g_list_next(tmp))
|
|
{
|
|
widget = static_cast<GtkWidget*>(tmp->data);
|
|
if (GTK_IS_LABEL(widget))
|
|
{
|
|
*label_p = widget;
|
|
}
|
|
else if (GTK_IS_ENTRY(widget))
|
|
{
|
|
*entry_p = widget;
|
|
}
|
|
}
|
|
g_list_free(children);
|
|
|
|
LEAVE("label %p, entry %p", *label_p, *entry_p);
|
|
return (*label_p && *entry_p);
|
|
}
|
|
|
|
static gboolean
|
|
main_window_find_tab_widget (GncMainWindow *window,
|
|
GncPluginPage *page,
|
|
GtkWidget **widget_p)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
ENTER("window %p, page %p, widget %p",
|
|
window, page, widget_p);
|
|
*widget_p = nullptr;
|
|
|
|
if (!page->notebook_page)
|
|
{
|
|
LEAVE("invalid notebook_page");
|
|
return FALSE;
|
|
}
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
*widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
|
|
page->notebook_page);
|
|
|
|
LEAVE("widget %p", *widget_p);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
main_window_update_page_long_name (GncPluginPage *page,
|
|
const gchar *long_name_in)
|
|
{
|
|
GtkWidget *tab_widget;
|
|
|
|
ENTER(" ");
|
|
|
|
if ((long_name_in == nullptr) || (*long_name_in == '\0'))
|
|
{
|
|
LEAVE("no string");
|
|
return;
|
|
}
|
|
gchar *long_name = g_strstrip (g_strdup (long_name_in));
|
|
const gchar *old_long_name = gnc_plugin_page_get_page_long_name (page);
|
|
|
|
/* Optimization, if the long_name hasn't changed, don't update X. */
|
|
if (*long_name == '\0' || strcmp (long_name, old_long_name) == 0)
|
|
{
|
|
g_free (long_name);
|
|
LEAVE("empty string or name unchanged");
|
|
return;
|
|
}
|
|
|
|
gnc_plugin_page_set_page_long_name (page, long_name);
|
|
|
|
GncMainWindow *window = GNC_MAIN_WINDOW(page->window);
|
|
if (!window)
|
|
{
|
|
g_free (long_name);
|
|
LEAVE("no window widget available");
|
|
return;
|
|
}
|
|
|
|
/* Update the notebook tab tooltip */
|
|
if (main_window_find_tab_widget (window, page, &tab_widget))
|
|
gtk_widget_set_tooltip_text (tab_widget, long_name);
|
|
|
|
g_free (long_name);
|
|
LEAVE("");
|
|
}
|
|
|
|
void
|
|
main_window_update_page_name (GncPluginPage *page,
|
|
const gchar *name_in)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *label, *entry;
|
|
gchar *name;
|
|
TabWidth *tw;
|
|
|
|
ENTER(" ");
|
|
|
|
if ((name_in == nullptr) || (*name_in == '\0'))
|
|
{
|
|
LEAVE("no string");
|
|
return;
|
|
}
|
|
name = g_strstrip(g_strdup(name_in));
|
|
|
|
/* Optimization, if the name hasn't changed, don't update X. */
|
|
if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
|
|
{
|
|
g_free(name);
|
|
LEAVE("empty string or name unchanged");
|
|
return;
|
|
}
|
|
|
|
/* Update the plugin */
|
|
gnc_plugin_page_set_page_name(page, name);
|
|
|
|
/* Update the notebook tab */
|
|
window = GNC_MAIN_WINDOW(page->window);
|
|
if (!window)
|
|
{
|
|
g_free(name);
|
|
LEAVE("no window widget available");
|
|
return;
|
|
}
|
|
|
|
if (main_window_find_tab_items(window, page, &label, &entry))
|
|
gtk_label_set_text(GTK_LABEL(label), name);
|
|
|
|
/* Adjust the label width for new text */
|
|
tw = populate_tab_width_struct ();
|
|
gnc_main_window_update_tab_width_one_page (page, tw);
|
|
g_free (tw);
|
|
|
|
/* Update the notebook menu */
|
|
if (page->notebook_page)
|
|
{
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
|
|
page->notebook_page);
|
|
gtk_label_set_text(GTK_LABEL(label), name);
|
|
}
|
|
|
|
/* Force an update of the window title */
|
|
gnc_main_window_update_title(window);
|
|
g_free(name);
|
|
LEAVE("done");
|
|
}
|
|
|
|
|
|
void
|
|
main_window_update_page_color (GncPluginPage *page,
|
|
const gchar *color_in)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *tab_widget;
|
|
GdkRGBA tab_color;
|
|
gchar *color_string = nullptr;
|
|
gboolean want_color = FALSE;
|
|
|
|
ENTER(" ");
|
|
if (color_in)
|
|
color_string = g_strstrip(g_strdup(color_in));
|
|
|
|
if (color_string && *color_string != '\0')
|
|
want_color = TRUE;
|
|
|
|
/* Update the plugin */
|
|
window = GNC_MAIN_WINDOW(page->window);
|
|
if (want_color)
|
|
gnc_plugin_page_set_page_color(page, color_string);
|
|
else
|
|
gnc_plugin_page_set_page_color(page, nullptr);
|
|
|
|
/* Update the notebook tab */
|
|
main_window_find_tab_widget (window, page, &tab_widget);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
|
|
{
|
|
GtkCssProvider *provider = gtk_css_provider_new();
|
|
GtkStyleContext *stylectxt;
|
|
gchar *col_str, *widget_css;
|
|
|
|
if (!GTK_IS_EVENT_BOX (tab_widget))
|
|
{
|
|
GtkWidget *event_box = gtk_event_box_new ();
|
|
g_object_ref (tab_widget);
|
|
gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
|
|
page->notebook_page, event_box);
|
|
gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
|
|
g_object_unref (tab_widget);
|
|
tab_widget = event_box;
|
|
}
|
|
|
|
stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
|
|
col_str = gdk_rgba_to_string (&tab_color);
|
|
widget_css = g_strconcat ("*{\n background-color:", col_str, ";\n}\n", nullptr);
|
|
|
|
gtk_css_provider_load_from_data (provider, widget_css, -1, nullptr);
|
|
gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
|
|
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
g_object_unref (provider);
|
|
g_free (col_str);
|
|
g_free (widget_css);
|
|
}
|
|
else
|
|
{
|
|
if (GTK_IS_EVENT_BOX (tab_widget))
|
|
{
|
|
GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
|
|
g_object_ref (tab_hbox);
|
|
gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
|
|
gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
|
|
page->notebook_page, tab_hbox);
|
|
g_object_unref (tab_hbox);
|
|
}
|
|
}
|
|
g_free(color_string);
|
|
LEAVE("done");
|
|
}
|
|
|
|
|
|
void
|
|
main_window_update_page_set_read_only_icon (GncPluginPage *page,
|
|
gboolean read_only)
|
|
{
|
|
GncMainWindow *window;
|
|
GtkWidget *tab_widget;
|
|
GtkWidget *image = NULL;
|
|
GList *children;
|
|
gchar *image_name = NULL;
|
|
const gchar *icon_name;
|
|
|
|
ENTER(" ");
|
|
|
|
g_return_if_fail (page && page->window);
|
|
|
|
if (!GNC_IS_MAIN_WINDOW (page->window))
|
|
return;
|
|
|
|
window = GNC_MAIN_WINDOW(page->window);
|
|
|
|
/* Get the notebook tab widget */
|
|
main_window_find_tab_widget (window, page, &tab_widget);
|
|
|
|
if (!tab_widget)
|
|
{
|
|
LEAVE("no tab widget");
|
|
return;
|
|
}
|
|
|
|
if (GTK_IS_EVENT_BOX(tab_widget))
|
|
tab_widget = gtk_bin_get_child (GTK_BIN(tab_widget));
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER(tab_widget));
|
|
/* For each, walk the list of container children to get image widget */
|
|
for (GList *child = children; child; child = g_list_next (child))
|
|
{
|
|
GtkWidget *widget = static_cast<GtkWidget*>(child->data);
|
|
if (GTK_IS_IMAGE(widget))
|
|
image = widget;
|
|
}
|
|
g_list_free (children);
|
|
|
|
if (!image)
|
|
{
|
|
LEAVE("no image to replace");
|
|
return;
|
|
}
|
|
|
|
g_object_get (image, "icon-name", &image_name, NULL);
|
|
|
|
if (read_only)
|
|
icon_name = "changes-prevent-symbolic";
|
|
else
|
|
icon_name = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
|
|
|
|
if (g_strcmp0 (icon_name, image_name) == 0)
|
|
{
|
|
LEAVE("page icon the same, no need to replace");
|
|
g_free (image_name);
|
|
return;
|
|
}
|
|
gtk_container_remove (GTK_CONTAINER(tab_widget), image);
|
|
image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
|
|
gtk_widget_show (image);
|
|
|
|
gtk_container_add (GTK_CONTAINER(tab_widget), image);
|
|
gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
|
|
gtk_box_reorder_child (GTK_BOX(tab_widget), image, 0);
|
|
|
|
g_free (image_name);
|
|
LEAVE("done");
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_tab_entry_activate (GtkWidget *entry,
|
|
GncPluginPage *page)
|
|
{
|
|
GtkWidget *label, *entry2;
|
|
|
|
g_return_if_fail(GTK_IS_ENTRY(entry));
|
|
g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
|
|
|
|
ENTER("");
|
|
if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
|
|
page, &label, &entry2))
|
|
{
|
|
LEAVE("can't find required widgets");
|
|
return;
|
|
}
|
|
|
|
main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
|
|
|
|
gtk_widget_hide(entry);
|
|
gtk_widget_show(label);
|
|
LEAVE("");
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
|
|
GncPluginPage *page)
|
|
{
|
|
ENTER("");
|
|
gnc_main_window_tab_entry_activate(entry, page);
|
|
LEAVE("");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
|
|
GdkEvent *event,
|
|
GncPluginPage *page)
|
|
{
|
|
ENTER("");
|
|
gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
|
|
LEAVE("");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
|
|
GdkEventKey *event,
|
|
GncPluginPage *page)
|
|
{
|
|
if (event->keyval == GDK_KEY_Escape)
|
|
{
|
|
GtkWidget *label, *entry2;
|
|
|
|
g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
|
|
g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
|
|
|
|
ENTER("");
|
|
if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
|
|
page, &label, &entry2))
|
|
{
|
|
LEAVE("can't find required widgets");
|
|
return FALSE;
|
|
}
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
|
|
gtk_widget_hide(entry);
|
|
gtk_widget_show(label);
|
|
LEAVE("");
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/************************************************************
|
|
* Widget Implementation *
|
|
************************************************************/
|
|
|
|
|
|
|
|
/** Initialize the class for a new gnucash main window. This will set
|
|
* up any function pointers that override functions in the parent
|
|
* class, and also initialize the signals that this class of widget
|
|
* can generate.
|
|
*
|
|
* @param klass The new class structure created by the object system.
|
|
*/
|
|
static void
|
|
gnc_main_window_class_init (GncMainWindowClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
|
|
|
|
window_type = g_quark_from_static_string ("gnc-main-window");
|
|
|
|
object_class->constructed = gnc_main_window_constructed;
|
|
object_class->finalize = gnc_main_window_finalize;
|
|
|
|
/* GtkWidget signals */
|
|
gtkwidget_class->destroy = gnc_main_window_destroy;
|
|
|
|
/**
|
|
* GncMainWindow::page_added:
|
|
* @param window: the #GncMainWindow
|
|
* @param page: the #GncPluginPage
|
|
*
|
|
* The "page_added" signal is emitted when a new page is added
|
|
* to the notebook of a GncMainWindow. This can be used to
|
|
* attach a signal from the page so that menu actions can be
|
|
* adjusted based upon events that occur within the page
|
|
* (e.g. an account is selected.)
|
|
*/
|
|
main_window_signals[PAGE_ADDED] =
|
|
g_signal_new ("page_added",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (GncMainWindowClass, page_added),
|
|
nullptr, nullptr,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_OBJECT);
|
|
|
|
/**
|
|
* GncMainWindow::page_changed:
|
|
* @param window: the #GncMainWindow
|
|
* @param page: the #GncPluginPage
|
|
*
|
|
* The "page_changed" signal is emitted when a new page is
|
|
* selected in the notebook of a GncMainWindow. This can be
|
|
* used to adjust menu actions based upon which page is
|
|
* currently displayed in a window.
|
|
*/
|
|
main_window_signals[PAGE_CHANGED] =
|
|
g_signal_new ("page_changed",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
|
|
nullptr, nullptr,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_OBJECT);
|
|
|
|
/**
|
|
* GncMainWindow::menu_changed:
|
|
* @param window: the #GncMainWindow
|
|
* @param page: the #GncPluginPage
|
|
*
|
|
* The "menu_changed" signal is emitted when the menu has been
|
|
* changed. This can be used to adjust other menu actions.
|
|
*/
|
|
main_window_signals[MENU_CHANGED] =
|
|
g_signal_new ("menu_changed",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (GncMainWindowClass, menu_changed),
|
|
nullptr, nullptr,
|
|
g_cclosure_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_OBJECT);
|
|
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_SHOW_CLOSE_BUTTON,
|
|
(gpointer)gnc_main_window_update_tab_close,
|
|
nullptr);
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_WIDTH,
|
|
(gpointer)gnc_main_window_update_tab_width,
|
|
nullptr);
|
|
|
|
gnc_hook_add_dangler(HOOK_BOOK_SAVED,
|
|
(GFunc)gnc_main_window_update_all_titles, nullptr, nullptr);
|
|
gnc_hook_add_dangler(HOOK_BOOK_OPENED,
|
|
(GFunc)gnc_main_window_attach_to_book, nullptr, nullptr);
|
|
|
|
}
|
|
|
|
|
|
/** Initialize a new instance of a gnucash main window. This function
|
|
* initializes the object private storage space.
|
|
*
|
|
* @param window The new object instance created by the object system.
|
|
* */
|
|
static void
|
|
gnc_main_window_init (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
// Set the name for this dialog so it can be easily manipulated with css
|
|
gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-main-window");
|
|
|
|
priv->event_handler_id =
|
|
qof_event_register_handler(gnc_main_window_event_handler, window);
|
|
|
|
priv->restoring_pages = FALSE;
|
|
|
|
priv->display_item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nullptr);
|
|
|
|
priv->previous_plugin_page_name = nullptr;
|
|
priv->previous_menu_qualifier = nullptr;
|
|
|
|
priv->accel_group = gtk_accel_group_new ();
|
|
gtk_window_add_accel_group (GTK_WINDOW(window), priv->accel_group);
|
|
|
|
/* Get the show_color_tabs value preference */
|
|
priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
|
|
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_COLOR,
|
|
(gpointer)gnc_main_window_update_tab_color,
|
|
window);
|
|
|
|
gnc_main_window_setup_window (window);
|
|
}
|
|
|
|
/** The object has been fully constructed.
|
|
* This function adds the object to the tracking system.
|
|
*
|
|
* @param obj The new object instance created by the object
|
|
* system.
|
|
*/
|
|
static void
|
|
gnc_main_window_constructed (GObject *obj)
|
|
{
|
|
gnc_gobject_tracking_remember(obj);
|
|
|
|
G_OBJECT_CLASS (gnc_main_window_parent_class)->constructed (obj);
|
|
}
|
|
|
|
/** Finalize the GncMainWindow object. This function is called from
|
|
* the G_Object level to complete the destruction of the object. It
|
|
* should release any memory not previously released by the destroy
|
|
* function (i.e. the private data structure), then chain up to the
|
|
* parent's destroy function.
|
|
*
|
|
* @param object The object being destroyed.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_finalize (GObject *object)
|
|
{
|
|
g_return_if_fail (object != nullptr);
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
|
|
|
|
if (active_windows == nullptr)
|
|
{
|
|
/* Oops. User killed last window and we didn't catch it. */
|
|
g_idle_add((GSourceFunc)gnc_shutdown, 0);
|
|
}
|
|
|
|
gnc_gobject_tracking_forget(object);
|
|
G_OBJECT_CLASS (gnc_main_window_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_remove_prefs (GncMainWindow *window)
|
|
{
|
|
// remove the registered preference callbacks setup in this file.
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_COLOR,
|
|
(gpointer)gnc_main_window_update_tab_color,
|
|
window);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_SHOW_CLOSE_BUTTON,
|
|
(gpointer)gnc_main_window_update_tab_close,
|
|
nullptr);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_WIDTH,
|
|
(gpointer)gnc_main_window_update_tab_width,
|
|
nullptr);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_TOP,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_BOTTOM,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_LEFT,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
|
|
gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_RIGHT,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
|
|
// remove the registered negative color preference callback.
|
|
if (gnc_prefs_get_reg_negative_color_pref_id() > 0 && window->window_quitting)
|
|
{
|
|
gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL,
|
|
gnc_prefs_get_reg_negative_color_pref_id());
|
|
gnc_prefs_set_reg_negative_color_pref_id (0);
|
|
}
|
|
|
|
// remove the registered auto_raise_lists preference callback.
|
|
if (gnc_prefs_get_reg_auto_raise_lists_id() > 0 && window->window_quitting)
|
|
{
|
|
gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL_REGISTER,
|
|
gnc_prefs_get_reg_auto_raise_lists_id());
|
|
gnc_prefs_set_reg_auto_raise_lists_id (0);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_destroy (GtkWidget *widget)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginManager *manager;
|
|
GList *plugins;
|
|
|
|
g_return_if_fail (widget != nullptr);
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
|
|
|
|
window = GNC_MAIN_WINDOW (widget);
|
|
#ifdef MAC_INTEGRATION
|
|
auto entry = g_list_find (active_windows, window);
|
|
if (entry && (entry->next || entry->prev))
|
|
gnc_quartz_set_menu (GNC_MAIN_WINDOW (entry->next ? entry->next->data : entry->prev->data));
|
|
#endif
|
|
active_windows = g_list_remove (active_windows, window);
|
|
|
|
/* Do these things once */
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (priv->event_handler_id > 0)
|
|
{
|
|
|
|
/* Close any pages in this window */
|
|
while (priv->current_page)
|
|
gnc_main_window_close_page(priv->current_page);
|
|
|
|
if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
|
|
gnc_window_set_progressbar_window(nullptr);
|
|
#ifndef MAC_INTEGRATION
|
|
/* Update the "Windows" menu in all other windows */
|
|
gnc_main_window_update_all_menu_items();
|
|
#endif
|
|
/* remove the preference callbacks from the main window */
|
|
gnc_main_window_remove_prefs (window);
|
|
|
|
qof_event_unregister_handler(priv->event_handler_id);
|
|
priv->event_handler_id = 0;
|
|
|
|
g_hash_table_destroy (priv->display_item_hash);
|
|
|
|
/* GncPluginManager stuff */
|
|
manager = gnc_plugin_manager_get ();
|
|
plugins = gnc_plugin_manager_get_plugins (manager);
|
|
g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
|
|
g_list_free (plugins);
|
|
}
|
|
|
|
GTK_WIDGET_CLASS (gnc_main_window_parent_class)->destroy (widget);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gnc_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GdkModifierType modifiers;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(widget), FALSE);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(widget);
|
|
|
|
modifiers = gtk_accelerator_get_default_mod_mask ();
|
|
|
|
if ((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) // Ctrl+Alt+
|
|
{
|
|
const gchar *account_key = C_ ("lower case key for short cut to 'Accounts'", "a");
|
|
guint account_keyval = gdk_keyval_from_name (account_key);
|
|
|
|
if ((account_keyval == event->keyval) || (account_keyval == gdk_keyval_to_lower (event->keyval)))
|
|
{
|
|
gint page = 0;
|
|
|
|
for (GList *item = priv->installed_pages; item; item = g_list_next (item))
|
|
{
|
|
const gchar *pname = gnc_plugin_page_get_plugin_name (GNC_PLUGIN_PAGE(item->data));
|
|
|
|
if (g_strcmp0 (pname, "GncPluginPageAccountTree") == 0)
|
|
{
|
|
gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook), page);
|
|
return TRUE;
|
|
}
|
|
page++;
|
|
}
|
|
}
|
|
else if ((GDK_KEY_Menu == event->keyval) || (GDK_KEY_space == event->keyval))
|
|
{
|
|
GList *menu = gtk_menu_get_for_attach_widget (GTK_WIDGET(priv->notebook));
|
|
|
|
if (menu)
|
|
{
|
|
gtk_menu_popup_at_widget (GTK_MENU(menu->data),
|
|
GTK_WIDGET(priv->notebook),
|
|
GDK_GRAVITY_SOUTH,
|
|
GDK_GRAVITY_SOUTH,
|
|
NULL);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Create a new gnc main window plugin.
|
|
*/
|
|
GncMainWindow *
|
|
gnc_main_window_new (void)
|
|
{
|
|
auto window{static_cast<GncMainWindow*>(g_object_new (GNC_TYPE_MAIN_WINDOW, nullptr))};
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
|
|
|
|
auto old_window = gnc_ui_get_main_window (nullptr);
|
|
if (old_window)
|
|
{
|
|
gint width, height;
|
|
gtk_window_get_size (old_window, &width, &height);
|
|
gtk_window_resize (GTK_WINDOW (window), width, height);
|
|
if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
|
|
& GDK_WINDOW_STATE_MAXIMIZED) != 0)
|
|
{
|
|
gtk_window_maximize (GTK_WINDOW (window));
|
|
}
|
|
}
|
|
active_windows = g_list_append (active_windows, window);
|
|
gnc_main_window_update_title(window);
|
|
window->window_quitting = FALSE;
|
|
window->just_plugin_prefs = FALSE;
|
|
#ifdef MAC_INTEGRATION
|
|
gnc_quartz_set_menu(window);
|
|
#else
|
|
gnc_main_window_update_all_menu_items();
|
|
#endif
|
|
gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
|
|
|
|
// set up a callback for notebook navigation
|
|
g_signal_connect (G_OBJECT(window), "key-press-event",
|
|
G_CALLBACK(gnc_main_window_key_press_event),
|
|
NULL);
|
|
|
|
return window;
|
|
}
|
|
|
|
/************************************************************
|
|
* Utility Functions *
|
|
************************************************************/
|
|
|
|
static void
|
|
gnc_main_window_engine_commit_error_callback( gpointer data,
|
|
QofBackendError errcode )
|
|
{
|
|
GncMainWindow* window = GNC_MAIN_WINDOW(data);
|
|
GtkWidget* dialog;
|
|
const gchar *reason = _("Unable to save to database.");
|
|
if ( errcode == ERR_BACKEND_READONLY )
|
|
reason = _("Unable to save to database: Book is marked read-only.");
|
|
dialog = gtk_message_dialog_new( GTK_WINDOW(window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
"%s",
|
|
reason );
|
|
gtk_dialog_run(GTK_DIALOG (dialog));
|
|
gtk_widget_destroy(dialog);
|
|
|
|
}
|
|
|
|
/** Connect a GncPluginPage to the window. This function will insert
|
|
* the page in to the window's notebook and its list of active pages.
|
|
* It will also emit the "inserted" signal on the page, and the
|
|
* "add_page" signal on the window.
|
|
*
|
|
* @param window The window where the new page should be added.
|
|
*
|
|
* @param page The GncPluginPage that should be added to the window.
|
|
* The visible widget for this plugin must have already been created.
|
|
*
|
|
* @param tab_hbox The widget that should be added into the notebook
|
|
* tab for this page. Generally this is a GtkLabel, but could also
|
|
* be a GtkBox containing an icon and a label.
|
|
*
|
|
* @param menu_label The widget that should be added into the
|
|
* notebook popup menu for this page. This should be a GtkLabel.
|
|
*/
|
|
static void
|
|
gnc_main_window_connect (GncMainWindow *window,
|
|
GncPluginPage *page,
|
|
GtkWidget *tab_hbox,
|
|
GtkWidget *menu_label)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkNotebook *notebook;
|
|
gint current_position = -1;
|
|
|
|
page->window = GTK_WIDGET(window);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
notebook = GTK_NOTEBOOK (priv->notebook);
|
|
|
|
if (!priv->restoring_pages
|
|
&& gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_OPEN_ADJACENT))
|
|
current_position = g_list_index (priv->installed_pages, priv->current_page) + 1;
|
|
|
|
priv->installed_pages = g_list_insert (priv->installed_pages, page, current_position);
|
|
priv->usage_order = g_list_prepend (priv->usage_order, page);
|
|
gtk_notebook_insert_page_menu (notebook, page->notebook_page,
|
|
tab_hbox, menu_label, current_position);
|
|
gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
|
|
gnc_plugin_page_inserted (page);
|
|
if (!priv->restoring_pages)
|
|
gtk_notebook_set_current_page (notebook, current_position);
|
|
|
|
if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
|
|
(GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
|
|
g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
|
|
|
|
g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
|
|
G_CALLBACK(gnc_main_window_popup_menu_cb), page);
|
|
g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
|
|
G_CALLBACK(gnc_main_window_button_press_cb), page);
|
|
}
|
|
|
|
|
|
/** Disconnect a GncPluginPage page from the window. If this page is
|
|
* currently foremost in the window's notebook, its user interface
|
|
* actions will be disconnected and the page's summarybar widget (if
|
|
* any) will be removed. The page is then removed from the window's
|
|
* notebook and its list of active pages.
|
|
*
|
|
* @param window The window the page should be removed from.
|
|
*
|
|
* @param page The GncPluginPage that should be removed from the
|
|
* window.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_disconnect (GncMainWindow *window,
|
|
GncPluginPage *page)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkNotebook *notebook;
|
|
GncPluginPage *new_page;
|
|
gint page_num;
|
|
|
|
/* Disconnect the callbacks */
|
|
g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
|
|
(gpointer)gnc_main_window_popup_menu_cb, page);
|
|
g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
|
|
(gpointer)gnc_main_window_button_press_cb, page);
|
|
|
|
// Remove the page_changed signal callback
|
|
gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
|
|
|
|
/* Disconnect the page and summarybar from the window */
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (priv->current_page == page)
|
|
{
|
|
gnc_plugin_page_unselected (page);
|
|
priv->current_page = nullptr;
|
|
}
|
|
|
|
/* Remove it from the list of pages in the window */
|
|
priv->installed_pages = g_list_remove (priv->installed_pages, page);
|
|
priv->usage_order = g_list_remove (priv->usage_order, page);
|
|
|
|
/* Switch to the last recently used page */
|
|
notebook = GTK_NOTEBOOK (priv->notebook);
|
|
if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
|
|
{
|
|
new_page = static_cast<GncPluginPage*>(g_list_nth_data (priv->usage_order, 0));
|
|
if (new_page)
|
|
{
|
|
page_num = gtk_notebook_page_num(notebook, new_page->notebook_page);
|
|
gtk_notebook_set_current_page(notebook, page_num);
|
|
/* This may have caused WebKit to schedule a timer interrupt which it
|
|
sometimes forgets to cancel before deleting the object. See
|
|
<https://bugs.webkit.org/show_bug.cgi?id=119003>. Get around this
|
|
by flushing all events to get rid of the timer interrupt. */
|
|
while (gtk_events_pending())
|
|
gtk_main_iteration();
|
|
}
|
|
}
|
|
|
|
/* Remove the page from the notebook */
|
|
page_num = gtk_notebook_page_num(notebook, page->notebook_page);
|
|
gtk_notebook_remove_page (notebook, page_num);
|
|
|
|
if ( gtk_notebook_get_current_page(notebook) == -1)
|
|
{
|
|
/* Need to synthesize a page changed signal when the last
|
|
* page is removed. The notebook doesn't generate a signal
|
|
* for this, therefore the switch_page code in this file
|
|
* never gets called to generate this signal. */
|
|
gnc_main_window_switch_page(notebook, nullptr, -1, window);
|
|
//g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, nullptr);
|
|
}
|
|
|
|
gnc_plugin_page_removed (page);
|
|
|
|
gnc_window_set_status (GNC_WINDOW(window), page, nullptr);
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* *
|
|
************************************************************/
|
|
|
|
|
|
void
|
|
gnc_main_window_display_page (GncPluginPage *page)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
GtkNotebook *notebook;
|
|
gint page_num;
|
|
|
|
window = GNC_MAIN_WINDOW (page->window);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
notebook = GTK_NOTEBOOK (priv->notebook);
|
|
page_num = gtk_notebook_page_num(notebook, page->notebook_page);
|
|
gtk_notebook_set_current_page (notebook, page_num);
|
|
gtk_window_present(GTK_WINDOW(window));
|
|
}
|
|
|
|
|
|
/* Display a data plugin page in a window. If the page already
|
|
* exists in any window, then that window will be brought to the
|
|
* front and the notebook switch to display the specified page. If
|
|
* the page is new then it will be added to the specified window. If
|
|
* the window is nullptr, the new page will be added to the first
|
|
* window.
|
|
*/
|
|
void
|
|
gnc_main_window_open_page (GncMainWindow *window,
|
|
GncPluginPage *page)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *tab_container, *tab_clickable_area;
|
|
GtkWidget *label, *entry;
|
|
const gchar *icon, *text, *color_string, *lab_text;
|
|
GtkWidget *image;
|
|
GList *tmp;
|
|
TabWidth *tw;
|
|
|
|
ENTER("window %p, page %p", window, page);
|
|
if (window)
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
|
|
g_return_if_fail (gnc_plugin_page_has_books(page));
|
|
|
|
if (gnc_main_window_page_exists(page))
|
|
{
|
|
gnc_main_window_display_page (page);
|
|
return;
|
|
}
|
|
|
|
/* Does the page want to be in a new window? */
|
|
if (gnc_plugin_page_get_use_new_window(page))
|
|
{
|
|
/* See if there's a blank window. If so, use that. */
|
|
for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
|
|
{
|
|
window = GNC_MAIN_WINDOW(tmp->data);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (priv->installed_pages == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (tmp == nullptr)
|
|
window = gnc_main_window_new ();
|
|
gtk_widget_show(GTK_WIDGET(window));
|
|
}
|
|
else if ((window == nullptr) && active_windows)
|
|
{
|
|
window = static_cast<GncMainWindow*>(active_windows->data);
|
|
}
|
|
|
|
page->window = GTK_WIDGET(window);
|
|
page->notebook_page = gnc_plugin_page_create_widget (page);
|
|
g_object_set_data (G_OBJECT (page->notebook_page),
|
|
PLUGIN_PAGE_LABEL, page);
|
|
|
|
/*
|
|
* The page tab.
|
|
* Component structure:
|
|
*
|
|
* tab_container (GtkBox)
|
|
* ├── tab_clickable_area (GtkEventBox)
|
|
* │ └── tab_content (GtkBox)
|
|
* │ ├── image (GtkImage, optional)
|
|
* │ ├── label (GtkLabel)
|
|
* │ └── entry (GtkEntry, hidden)
|
|
* └── close_button (GtkButton, if not immutable)
|
|
*/
|
|
icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
|
|
lab_text = gnc_plugin_page_get_page_name(page);
|
|
label = gtk_label_new (lab_text);
|
|
g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
|
|
|
|
tw = populate_tab_width_struct ();
|
|
gnc_main_window_update_tab_width_one_page (page, tw);
|
|
g_free (tw);
|
|
|
|
gtk_widget_show (label);
|
|
|
|
tab_container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
|
|
|
|
text = gnc_plugin_page_get_page_long_name(page);
|
|
if (text)
|
|
{
|
|
gtk_widget_set_tooltip_text(tab_container, text);
|
|
}
|
|
|
|
if (g_strcmp0 (gnc_plugin_page_get_plugin_name (page), "GncPluginPageAccountTree") == 0)
|
|
gtk_widget_set_name (GTK_WIDGET(tab_container), "gnc-id-account-page-tab-box");
|
|
|
|
gtk_box_set_homogeneous (GTK_BOX (tab_container), FALSE);
|
|
gtk_widget_show (tab_container);
|
|
|
|
// Create a custom clickable area for the tab to support middle-clicking
|
|
tab_clickable_area = gtk_event_box_new();
|
|
gtk_widget_show(tab_clickable_area);
|
|
gtk_box_pack_start (GTK_BOX (tab_container), tab_clickable_area, TRUE, TRUE, 0);
|
|
|
|
// Create a box for the tab's content
|
|
// Give it a name so we can find it later (see main_window_find_tab_items)
|
|
GtkWidget *tab_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
|
gtk_widget_set_name(tab_content, "tab-content");
|
|
gtk_container_add(GTK_CONTAINER(tab_clickable_area), tab_content);
|
|
gtk_widget_show(tab_content);
|
|
|
|
if (icon != nullptr)
|
|
{
|
|
image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
|
|
gtk_widget_show (image);
|
|
gtk_box_pack_start (GTK_BOX (tab_content), image, FALSE, FALSE, 0);
|
|
gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
|
|
gtk_box_pack_start (GTK_BOX (tab_content), label, TRUE, TRUE, 0);
|
|
}
|
|
else
|
|
gtk_box_pack_start (GTK_BOX (tab_content), label, TRUE, TRUE, 0);
|
|
|
|
entry = gtk_entry_new();
|
|
gtk_widget_hide (entry);
|
|
gtk_box_pack_start (GTK_BOX (tab_content), entry, TRUE, TRUE, 0);
|
|
g_signal_connect(G_OBJECT(entry), "activate",
|
|
G_CALLBACK(gnc_main_window_tab_entry_activate), page);
|
|
g_signal_connect(G_OBJECT(entry), "focus-out-event",
|
|
G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
|
|
page);
|
|
g_signal_connect(G_OBJECT(entry), "key-press-event",
|
|
G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
|
|
page);
|
|
g_signal_connect(G_OBJECT(entry), "editing-done",
|
|
G_CALLBACK(gnc_main_window_tab_entry_editing_done),
|
|
page);
|
|
|
|
/* Add close button - Not for immutable pages */
|
|
if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
|
|
{
|
|
GtkWidget *close_image, *close_button;
|
|
GtkRequisition requisition;
|
|
|
|
close_button = gtk_button_new();
|
|
gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
|
|
close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
|
|
gtk_widget_show(close_image);
|
|
gtk_widget_get_preferred_size (close_image, &requisition, nullptr);
|
|
gtk_widget_set_size_request(close_button, requisition.width + 4,
|
|
requisition.height + 2);
|
|
gtk_container_add(GTK_CONTAINER(close_button), close_image);
|
|
if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
|
|
gtk_widget_show (close_button);
|
|
else
|
|
gtk_widget_hide (close_button);
|
|
|
|
// Custom handler to close on middle-clicks
|
|
g_signal_connect(G_OBJECT(tab_clickable_area), "button-press-event",
|
|
G_CALLBACK(gnc_tab_clicked_cb), page);
|
|
|
|
g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
|
|
G_CALLBACK(gnc_main_window_close_page), page);
|
|
|
|
gtk_box_pack_start (GTK_BOX (tab_container), close_button, FALSE, FALSE, 0);
|
|
gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
|
|
g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
|
|
}
|
|
|
|
/*
|
|
* The popup menu
|
|
*/
|
|
label = gtk_label_new (gnc_plugin_page_get_page_name(page));
|
|
|
|
/*
|
|
* Now install it all in the window.
|
|
*/
|
|
gnc_main_window_connect(window, page, tab_container, label);
|
|
|
|
color_string = gnc_plugin_page_get_page_color(page);
|
|
main_window_update_page_color (page, color_string);
|
|
LEAVE("");
|
|
}
|
|
|
|
|
|
/* Remove a data plugin page from a window and display the previous
|
|
* page. If the page removed was the last page in the window, and
|
|
* there is more than one window open, then the entire window will be
|
|
* destroyed.
|
|
*/
|
|
void
|
|
gnc_main_window_close_page (GncPluginPage *page)
|
|
{
|
|
GncMainWindow *window;
|
|
GncMainWindowPrivate *priv;
|
|
|
|
if (!page || !page->notebook_page)
|
|
return;
|
|
|
|
if (!gnc_plugin_page_finish_pending(page))
|
|
return;
|
|
|
|
if (!GNC_IS_MAIN_WINDOW (page->window))
|
|
return;
|
|
|
|
window = GNC_MAIN_WINDOW (page->window);
|
|
if (!window)
|
|
{
|
|
g_warning("Page is not in a window.");
|
|
return;
|
|
}
|
|
|
|
gnc_main_window_disconnect(window, page);
|
|
gnc_plugin_page_destroy_widget (page);
|
|
g_object_unref(page);
|
|
|
|
/* If this isn't the last window, go ahead and destroy the window. */
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (priv->installed_pages == nullptr)
|
|
{
|
|
if (window->window_quitting)
|
|
{
|
|
GncPluginManager *manager = gnc_plugin_manager_get ();
|
|
GList *plugins = gnc_plugin_manager_get_plugins (manager);
|
|
|
|
/* remove only the preference callbacks from the window plugins */
|
|
window->just_plugin_prefs = TRUE;
|
|
g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
|
|
window->just_plugin_prefs = FALSE;
|
|
g_list_free (plugins);
|
|
|
|
/* remove the preference callbacks from the main window */
|
|
gnc_main_window_remove_prefs (window);
|
|
}
|
|
if (window && (gnc_list_length_cmp (active_windows, 1) > 0))
|
|
gtk_widget_destroy (GTK_WIDGET(window));
|
|
}
|
|
}
|
|
|
|
|
|
/* Retrieve a pointer to the page that is currently at the front of
|
|
* the specified window. Any plugin that needs to manipulate its
|
|
* menus based upon the currently selected menu page should connect
|
|
* to the "page_changed" signal on a window. The callback function
|
|
* from that signal can then call this function to obtain a pointer
|
|
* to the current page.
|
|
*/
|
|
GncPluginPage *
|
|
gnc_main_window_get_current_page (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
return priv->current_page;
|
|
}
|
|
|
|
|
|
/* Manually add a set of actions to the specified window. Plugins
|
|
* whose user interface is not hard coded (e.g. the menu-additions
|
|
* plugin) must create their actions at run time, then use this
|
|
* function to install them into the window.
|
|
*/
|
|
void
|
|
gnc_main_window_manual_merge_actions (GncMainWindow *window,
|
|
const gchar *group_name,
|
|
GSimpleActionGroup *group)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
g_return_if_fail (group_name != nullptr);
|
|
g_return_if_fail (G_IS_SIMPLE_ACTION_GROUP(group));
|
|
|
|
gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
|
|
G_ACTION_GROUP(group));
|
|
}
|
|
|
|
|
|
static void
|
|
update_menu_model (GncMainWindow *window, const gchar *ui_filename,
|
|
const gchar **ui_updates)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GError *error = nullptr;
|
|
gchar *res_name;
|
|
GtkBuilder *builder = gtk_builder_new ();
|
|
GMenuModel *menu_model_part;
|
|
GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (ui_filename != nullptr);
|
|
g_return_if_fail (ui_updates != nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
gtk_builder_set_translation_domain (builder, PROJECT_NAME);
|
|
res_name = g_strconcat (GNUCASH_RESOURCE_PREFIX "/", ui_filename, NULL);
|
|
|
|
gtk_builder_add_from_resource (builder, res_name, &error);
|
|
g_free (res_name);
|
|
|
|
if (error)
|
|
{
|
|
g_critical ("Failed to load, Error %s", error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
for (gint i = 0; ui_updates[i]; i++)
|
|
{
|
|
menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
|
|
|
|
gsm->search_action_label = nullptr;
|
|
gsm->search_action_name = ui_updates[i];
|
|
gsm->search_action_target = nullptr;
|
|
|
|
if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
|
|
g_menu_insert_section (G_MENU(gsm->model), gsm->index, NULL, G_MENU_MODEL(menu_model_part));
|
|
else
|
|
PERR("Could not find '%s' in menu model", ui_updates[i]);
|
|
}
|
|
g_free (gsm);
|
|
g_object_unref (builder);
|
|
}
|
|
|
|
|
|
/* Add a set of actions to the specified window. This function
|
|
* should not need to be called directly by plugin implementors.
|
|
* Correctly assigning values to the GncPluginClass fields during
|
|
* plugin initialization will cause this routine to be automatically
|
|
* called.
|
|
*/
|
|
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)
|
|
{
|
|
GncMainWindowActionData *data;
|
|
GSimpleActionGroup *simple_action_group;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
g_return_if_fail (group_name != nullptr);
|
|
g_return_if_fail (actions != nullptr);
|
|
g_return_if_fail (n_actions > 0);
|
|
|
|
data = g_new0 (GncMainWindowActionData, 1);
|
|
data->window = window;
|
|
data->data = user_data;
|
|
|
|
simple_action_group = g_simple_action_group_new ();
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP(simple_action_group),
|
|
actions,
|
|
n_actions,
|
|
data);
|
|
|
|
gtk_widget_insert_action_group (GTK_WIDGET(window), group_name,
|
|
G_ACTION_GROUP(simple_action_group));
|
|
|
|
if (ui_filename)
|
|
update_menu_model (window, ui_filename, ui_updates);
|
|
}
|
|
|
|
|
|
/* Remove a set of actions from the specified window. This function
|
|
* should not need to be called directly by plugin implementors. It
|
|
* will automatically be called when a plugin is removed from a
|
|
* window.
|
|
*/
|
|
void
|
|
gnc_main_window_unmerge_actions (GncMainWindow *window,
|
|
const gchar *group_name)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (group_name != nullptr);
|
|
|
|
gtk_widget_insert_action_group (GTK_WIDGET(window), group_name, nullptr);
|
|
}
|
|
|
|
GAction *
|
|
gnc_main_window_find_action (GncMainWindow *window, const gchar *action_name)
|
|
{
|
|
GAction *action = nullptr;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
g_return_val_if_fail (action_name != nullptr, nullptr);
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP(window),
|
|
action_name);
|
|
|
|
return action;
|
|
}
|
|
|
|
GAction *
|
|
gnc_main_window_find_action_in_group (GncMainWindow *window,
|
|
const gchar *group_name,
|
|
const gchar *action_name)
|
|
{
|
|
GAction *action = nullptr;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
g_return_val_if_fail (group_name != nullptr, nullptr);
|
|
g_return_val_if_fail (action_name != nullptr, nullptr);
|
|
|
|
auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
|
|
|
|
if (action_group)
|
|
action = g_action_map_lookup_action (G_ACTION_MAP(action_group), action_name);
|
|
|
|
return action;
|
|
}
|
|
|
|
|
|
/* Retrieve a specific set of user interface actions from a window.
|
|
* This function can be used to get an group of action to be
|
|
* manipulated when the front page of a window has changed.
|
|
*/
|
|
GSimpleActionGroup *
|
|
gnc_main_window_get_action_group (GncMainWindow *window,
|
|
const gchar *group_name)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
g_return_val_if_fail (group_name != nullptr, nullptr);
|
|
|
|
auto action_group = gtk_widget_get_action_group (GTK_WIDGET(window), group_name);
|
|
return (GSimpleActionGroup*)action_group;
|
|
}
|
|
|
|
GtkWidget *
|
|
gnc_main_window_toolbar_find_tool_item (GncMainWindow *window, const gchar *action_name)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
g_return_val_if_fail (action_name != nullptr, nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return gnc_find_toolbar_item (priv->toolbar, action_name);
|
|
}
|
|
|
|
GtkWidget *
|
|
gnc_main_window_menu_find_menu_item (GncMainWindow *window, const gchar *action_name)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *menu_item;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
g_return_val_if_fail (action_name != nullptr, nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
menu_item = GTK_WIDGET(g_hash_table_lookup (priv->display_item_hash, action_name));
|
|
|
|
if (!menu_item)
|
|
{
|
|
menu_item = gnc_menubar_model_find_menu_item (priv->menubar_model, priv->menubar, action_name);
|
|
|
|
g_hash_table_insert (priv->display_item_hash, g_strdup (action_name), menu_item);
|
|
}
|
|
return menu_item;
|
|
}
|
|
|
|
|
|
void
|
|
gnc_main_window_menu_add_accelerator_keys (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
gnc_add_accelerator_keys_for_menu (priv->menubar, priv->menubar_model, priv->accel_group);
|
|
}
|
|
|
|
|
|
gboolean
|
|
gnc_main_window_update_menu_for_action (GncMainWindow *window,
|
|
const gchar *action_name,
|
|
const gchar *label,
|
|
const gchar *tooltip)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
gboolean found = false;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), false);
|
|
g_return_val_if_fail (action_name != nullptr, false);
|
|
g_return_val_if_fail (label != nullptr, false);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
found = gnc_menubar_model_update_item (priv->menubar_model, action_name,
|
|
nullptr, _(label), nullptr, _(tooltip));
|
|
|
|
// add tooltip redirect call backs
|
|
gnc_plugin_add_menu_tooltip_callbacks (priv->menubar,
|
|
priv->menubar_model,
|
|
priv->statusbar);
|
|
|
|
return found;
|
|
}
|
|
|
|
void
|
|
gnc_main_window_set_vis_of_items_by_action (GncMainWindow *window,
|
|
const gchar **action_names,
|
|
gboolean vis)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
for (gint i = 0; action_names[i]; i++)
|
|
{
|
|
GtkWidget *tool_item = gnc_find_toolbar_item (priv->toolbar, action_names[i]);
|
|
GtkWidget *menu_item = gnc_main_window_menu_find_menu_item (window, action_names[i]);
|
|
|
|
if (menu_item)
|
|
{
|
|
PINFO("Found menu_item %p with action name '%s', seting vis to '%s'",
|
|
menu_item, action_names[i], vis ? "true" : "false");
|
|
gtk_widget_set_visible (menu_item, vis);
|
|
}
|
|
else
|
|
PINFO("Did not find menu_item with action name '%s' to set vis '%s'",
|
|
action_names[i], vis ? "true" : "false");
|
|
|
|
if (tool_item)
|
|
{
|
|
PINFO("Found tool_item %p with action name '%s', seting vis to '%s'",
|
|
tool_item, action_names[i], vis ? "true" : "false");
|
|
gtk_widget_set_visible (tool_item, vis);
|
|
}
|
|
else
|
|
PINFO("Did not find tool_item with action name '%s' to set vis '%s'",
|
|
action_names[i], vis ? "true" : "false");
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
gnc_main_window_init_short_names (GncMainWindow *window,
|
|
GncToolBarShortNames *toolbar_labels)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
g_return_if_fail (toolbar_labels != nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
gnc_plugin_init_short_names (priv->toolbar, toolbar_labels);
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_update_toolbar (GncMainWindow *window, GncPluginPage *page,
|
|
const gchar *toolbar_qualifier)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkBuilder *builder;
|
|
GAction *action;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
builder = gnc_plugin_page_get_builder (page);
|
|
|
|
if (builder)
|
|
{
|
|
gchar *toolbar_name;
|
|
gtk_container_remove (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
|
|
|
|
if (toolbar_qualifier)
|
|
toolbar_name = g_strconcat ("mainwin-toolbar-", toolbar_qualifier, nullptr);
|
|
else
|
|
toolbar_name = g_strdup ("mainwin-toolbar");
|
|
|
|
priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, toolbar_name);
|
|
|
|
if (!priv->toolbar)
|
|
priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
|
|
|
|
g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
|
|
gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->toolbar);
|
|
g_free (toolbar_name);
|
|
}
|
|
|
|
action = gnc_main_window_find_action (window, "ViewToolbarAction");
|
|
|
|
// set visibility of toolbar
|
|
if (action)
|
|
{
|
|
GVariant *state = g_action_get_state (G_ACTION(action));
|
|
gtk_widget_set_visible (priv->toolbar, g_variant_get_boolean (state));
|
|
g_variant_unref (state);
|
|
}
|
|
// add tooltip redirect call backs
|
|
gnc_plugin_add_toolbar_tooltip_callbacks (priv->toolbar, priv->statusbar);
|
|
}
|
|
|
|
|
|
void
|
|
gnc_main_window_update_menu_and_toolbar (GncMainWindow *window,
|
|
GncPluginPage *page,
|
|
const gchar **ui_updates)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
const gchar *plugin_page_actions_group_name;
|
|
GtkBuilder *builder;
|
|
const gchar *menu_qualifier;
|
|
|
|
GMenuModel *menu_model_part;
|
|
#ifdef MAC_INTEGRATION
|
|
auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
|
|
#endif
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
g_return_if_fail (page != nullptr);
|
|
g_return_if_fail (ui_updates != nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
builder = gnc_plugin_page_get_builder (page);
|
|
|
|
if (!builder)
|
|
return;
|
|
|
|
menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
|
|
|
|
plugin_page_actions_group_name = gnc_plugin_page_get_simple_action_group_name (page);
|
|
|
|
if (!plugin_page_actions_group_name)
|
|
return;
|
|
|
|
gtk_widget_insert_action_group (GTK_WIDGET(window), gnc_plugin_page_get_simple_action_group_name (page),
|
|
G_ACTION_GROUP(gnc_plugin_page_get_action_group (page)));
|
|
|
|
if ((g_strcmp0 (priv->previous_plugin_page_name,
|
|
plugin_page_actions_group_name) == 0) &&
|
|
(g_strcmp0 (priv->previous_menu_qualifier,
|
|
menu_qualifier) == 0))
|
|
return;
|
|
|
|
priv->previous_plugin_page_name = plugin_page_actions_group_name;
|
|
priv->previous_menu_qualifier = menu_qualifier;
|
|
|
|
gnc_main_window_update_toolbar (window, page, menu_qualifier);
|
|
|
|
// reset hash table and remove added menu items
|
|
g_hash_table_remove_all (priv->display_item_hash);
|
|
gnc_menubar_model_remove_items_with_attrib (priv->menubar_model,
|
|
GNC_MENU_ATTRIBUTE_TEMPORARY);
|
|
|
|
GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
|
|
for (gint i = 0; ui_updates[i]; i++)
|
|
{
|
|
gchar *menu_name;
|
|
|
|
if (menu_qualifier)
|
|
menu_name = g_strconcat (ui_updates[i], "-", menu_qualifier, nullptr);
|
|
else
|
|
menu_name = g_strdup (ui_updates[i]);
|
|
|
|
menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, menu_name);
|
|
|
|
if (!menu_model_part)
|
|
menu_model_part = (GMenuModel *)gtk_builder_get_object (builder, ui_updates[i]);
|
|
|
|
gsm->search_action_label = nullptr;
|
|
gsm->search_action_name = ui_updates[i];
|
|
gsm->search_action_target = nullptr;
|
|
|
|
if (gnc_menubar_model_find_item (priv->menubar_model, gsm))
|
|
g_menu_insert_section (G_MENU(gsm->model), gsm->index,
|
|
nullptr, G_MENU_MODEL(menu_model_part));
|
|
else
|
|
PERR("Could not find '%s' in menu model", ui_updates[i]);
|
|
|
|
g_free (menu_name);
|
|
}
|
|
|
|
// add tooltip redirect call backs
|
|
gnc_plugin_add_menu_tooltip_callbacks (priv->menubar, priv->menubar_model, priv->statusbar);
|
|
|
|
// need to add the accelerator keys
|
|
gnc_add_accelerator_keys_for_menu (priv->menubar, priv->menubar_model, priv->accel_group);
|
|
#ifdef MAC_INTEGRATION
|
|
gtkosx_application_sync_menubar (theApp);
|
|
g_object_unref (theApp);
|
|
#endif
|
|
// need to signal menu has been changed
|
|
g_signal_emit_by_name (window, "menu_changed", page);
|
|
|
|
g_free (gsm);
|
|
}
|
|
|
|
|
|
static void
|
|
gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
|
|
{
|
|
GncMainWindow *window;
|
|
GtkPositionType position = GTK_POS_TOP;
|
|
gint item = 0;
|
|
GncMainWindowPrivate *priv;
|
|
GAction *action;
|
|
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(user_data));
|
|
|
|
window = GNC_MAIN_WINDOW(user_data);
|
|
|
|
ENTER ("window %p", window);
|
|
|
|
/* Ignore notification of the preference that is being set to false when
|
|
* the choice of tab position changes. */
|
|
if (pref && !gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, pref))
|
|
return;
|
|
|
|
if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
|
|
{
|
|
position = GTK_POS_BOTTOM;
|
|
item = 1;
|
|
}
|
|
else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
|
|
{
|
|
position = GTK_POS_LEFT;
|
|
item = 2;
|
|
}
|
|
else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
|
|
{
|
|
position = GTK_POS_RIGHT;
|
|
item = 3;
|
|
}
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
gtk_notebook_set_tab_pos (GTK_NOTEBOOK(priv->notebook), position);
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP(window),
|
|
"ViewTabPositionAction");
|
|
|
|
g_signal_handlers_block_by_func (G_OBJECT(action),
|
|
(gpointer)gnc_main_window_cmd_view_tab_position,
|
|
window);
|
|
g_action_change_state (G_ACTION(action), g_variant_new_int32 (item));
|
|
g_signal_handlers_unblock_by_func (G_OBJECT(action),
|
|
(gpointer)gnc_main_window_cmd_view_tab_position,
|
|
window);
|
|
|
|
gnc_main_window_update_tab_width (nullptr, (char*)GNC_PREF_TAB_WIDTH, nullptr);
|
|
|
|
LEAVE ("");
|
|
}
|
|
|
|
/*
|
|
* Based on code from Epiphany (src/ephy-window.c)
|
|
*/
|
|
static void
|
|
gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
|
|
GAction *action;
|
|
gboolean can_copy = false, can_cut = false, can_paste = false;
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page = priv->current_page;
|
|
|
|
if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
|
|
{
|
|
(GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
|
|
return;
|
|
}
|
|
|
|
if (GTK_IS_EDITABLE (widget))
|
|
{
|
|
gboolean has_selection;
|
|
|
|
has_selection = gtk_editable_get_selection_bounds
|
|
(GTK_EDITABLE (widget), nullptr, nullptr);
|
|
|
|
can_copy = has_selection;
|
|
can_cut = has_selection;
|
|
can_paste = TRUE;
|
|
}
|
|
else if (GTK_IS_TEXT_VIEW (widget))
|
|
{
|
|
gboolean has_selection;
|
|
GtkTextBuffer *text_buffer;
|
|
|
|
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
|
|
has_selection = gtk_text_buffer_get_selection_bounds
|
|
(text_buffer, nullptr, nullptr);
|
|
|
|
can_copy = has_selection;
|
|
can_cut = has_selection;
|
|
can_paste = TRUE;
|
|
}
|
|
else
|
|
{
|
|
#ifdef ORIGINAL_EPIPHANY_CODE
|
|
/* For now we assume all actions are possible */
|
|
can_copy = can_cut = can_paste = true;
|
|
#else
|
|
/* If its not a GtkEditable, we don't know what to do
|
|
* with it. */
|
|
can_copy = can_cut = can_paste = false;
|
|
#endif
|
|
}
|
|
action = gnc_main_window_find_action (window, "EditCopyAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_copy);
|
|
|
|
action = gnc_main_window_find_action (window, "EditCutAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_cut);
|
|
|
|
action = gnc_main_window_find_action (window, "EditPasteAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), can_paste);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
|
|
{
|
|
GAction *action;
|
|
|
|
action = gnc_main_window_find_action (window, "EditCopyAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
|
|
|
|
action = gnc_main_window_find_action (window, "EditCutAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
|
|
|
|
action = gnc_main_window_find_action (window, "EditPasteAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
|
|
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
|
|
GncMainWindow *window)
|
|
{
|
|
gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
|
|
GncMainWindow *window)
|
|
{
|
|
gnc_main_window_enable_edit_actions_sensitivity (window);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_init_menu_updaters (GncMainWindow *window)
|
|
{
|
|
GtkWidget *edit_menu_item, *edit_menu;
|
|
|
|
edit_menu_item = gnc_main_window_menu_find_menu_item (window, "EditAction");
|
|
|
|
edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM(edit_menu_item));
|
|
|
|
g_signal_connect (edit_menu, "show",
|
|
G_CALLBACK(gnc_main_window_edit_menu_show_cb), window);
|
|
g_signal_connect (edit_menu, "hide",
|
|
G_CALLBACK(gnc_main_window_edit_menu_hide_cb), window);
|
|
}
|
|
|
|
/* This is used to prevent the tab having focus */
|
|
static gboolean
|
|
gnc_main_window_page_focus_in (GtkWidget *widget, GdkEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
auto window{static_cast<GncMainWindow *>(user_data)};
|
|
GncPluginPage *page = gnc_main_window_get_current_page (window);
|
|
|
|
g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
|
|
return FALSE;
|
|
}
|
|
|
|
static GAction *
|
|
gnc_main_window_get_redirect (GncMainWindow *window, const gchar *action_name)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GAction *action = nullptr;
|
|
const gchar *group_name;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
|
|
g_return_val_if_fail (action_name != nullptr, nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
group_name = gnc_plugin_page_get_simple_action_group_name (priv->current_page);
|
|
|
|
PINFO("action anme is '%s', group_name is '%s'", action_name, group_name);
|
|
|
|
if (group_name)
|
|
{
|
|
action = gnc_main_window_find_action_in_group (window, group_name, action_name);
|
|
|
|
if (!action)
|
|
action = gnc_plugin_page_get_action (priv->current_page, action_name);
|
|
}
|
|
|
|
PINFO("Redirect action is %p for action anme '%s' and group_name '%s'",
|
|
action, action_name, group_name);
|
|
return action;
|
|
}
|
|
|
|
static void
|
|
main_window_realize_cb (GtkWidget *widget, gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
gnc_add_accelerator_keys_for_menu (GTK_WIDGET(priv->menubar), priv->menubar_model, priv->accel_group);
|
|
|
|
/* need to signal menu has been changed, this will call the
|
|
business function 'bind_extra_toolbuttons_visibility' */
|
|
g_signal_emit_by_name (window, "menu_changed", nullptr);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_setup_window (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *main_vbox;
|
|
GtkBuilder *builder;
|
|
GncPluginManager *manager;
|
|
GList *plugins;
|
|
GError *error = nullptr;
|
|
GAction *action;
|
|
|
|
ENTER(" ");
|
|
|
|
/* Catch window manager delete signal */
|
|
g_signal_connect (G_OBJECT (window), "delete-event",
|
|
G_CALLBACK (gnc_main_window_delete_event), window);
|
|
|
|
/* Create widgets and add them to the window */
|
|
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
|
|
gtk_widget_show (main_vbox);
|
|
gtk_container_add (GTK_CONTAINER (window), main_vbox);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
|
|
gtk_widget_show (priv->menu_dock);
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
|
|
FALSE, TRUE, 0);
|
|
|
|
priv->notebook = gtk_notebook_new ();
|
|
g_object_set(G_OBJECT(priv->notebook),
|
|
"scrollable", TRUE,
|
|
"enable-popup", TRUE,
|
|
(char *)nullptr);
|
|
gtk_widget_show (priv->notebook);
|
|
g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
|
|
G_CALLBACK (gnc_main_window_switch_page), window);
|
|
g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
|
|
G_CALLBACK (gnc_main_window_page_reordered), window);
|
|
g_signal_connect (G_OBJECT (priv->notebook), "focus-in-event",
|
|
G_CALLBACK (gnc_main_window_page_focus_in), window);
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
|
|
TRUE, TRUE, 0);
|
|
|
|
priv->statusbar = gtk_statusbar_new ();
|
|
gtk_widget_show (priv->statusbar);
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
|
|
FALSE, TRUE, 0);
|
|
|
|
priv->progressbar = gtk_progress_bar_new ();
|
|
gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(priv->progressbar), TRUE);
|
|
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
|
|
gtk_widget_show (priv->progressbar);
|
|
gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
|
|
FALSE, TRUE, 0);
|
|
gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
|
|
0.01);
|
|
|
|
builder = gtk_builder_new ();
|
|
gtk_builder_set_translation_domain (builder, PROJECT_NAME);
|
|
gtk_builder_add_from_resource (builder, GNUCASH_RESOURCE_PREFIX "/gnc-main-window.ui", &error);
|
|
|
|
if (error)
|
|
{
|
|
g_critical ("Failed to load, Error %s", error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP(window),
|
|
gnc_menu_actions,
|
|
gnc_menu_n_actions,
|
|
window);
|
|
|
|
priv->menubar_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-menu");
|
|
priv->menubar = gtk_menu_bar_new_from_model (priv->menubar_model);
|
|
gtk_container_add (GTK_CONTAINER(priv->menu_dock), priv->menubar);
|
|
gtk_widget_show (GTK_WIDGET(priv->menubar));
|
|
|
|
priv->toolbar = (GtkWidget *)gtk_builder_get_object (builder, "mainwin-toolbar");
|
|
g_object_set (priv->toolbar, "toolbar-style", GTK_TOOLBAR_BOTH, NULL);
|
|
gtk_container_add (GTK_CONTAINER(priv->menu_dock), GTK_WIDGET(priv->toolbar));
|
|
gtk_widget_show (GTK_WIDGET(priv->toolbar));
|
|
|
|
g_object_unref (builder);
|
|
|
|
gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
|
|
initially_insensitive_actions,
|
|
FALSE);
|
|
gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
|
|
always_insensitive_actions,
|
|
FALSE);
|
|
|
|
gnc_main_window_set_vis_of_items_by_action (window, always_hidden_actions,
|
|
false);
|
|
|
|
gtk_widget_insert_action_group (GTK_WIDGET(window), "mainwin",
|
|
G_ACTION_GROUP(window));
|
|
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_TOP,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_BOTTOM,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_LEFT,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
|
|
GNC_PREF_TAB_POSITION_RIGHT,
|
|
(gpointer)gnc_main_window_update_tab_position,
|
|
window);
|
|
gnc_main_window_update_tab_position (nullptr, nullptr, window);
|
|
|
|
gnc_main_window_init_menu_updaters (window);
|
|
|
|
/* Disable the Transaction menu */
|
|
action = gnc_main_window_find_action (window, "TransactionAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
|
|
/* Disable the Schedule menu */
|
|
action = gnc_main_window_find_action (window, "ScheduledAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
|
|
|
|
/* Now update the "eXtensions" menu */
|
|
if (!gnc_prefs_is_extra_enabled())
|
|
{
|
|
action = gnc_main_window_find_action (window, "ExtensionsAction");
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), false);
|
|
}
|
|
|
|
/* GncPluginManager stuff */
|
|
manager = gnc_plugin_manager_get ();
|
|
plugins = gnc_plugin_manager_get_plugins (manager);
|
|
g_list_foreach (plugins, gnc_main_window_add_plugin, window);
|
|
g_list_free (plugins);
|
|
|
|
g_signal_connect (G_OBJECT (manager), "plugin-added",
|
|
G_CALLBACK (gnc_main_window_plugin_added), window);
|
|
g_signal_connect (G_OBJECT (manager), "plugin-removed",
|
|
G_CALLBACK (gnc_main_window_plugin_removed), window);
|
|
|
|
// need to add the accelerator keys this way, mainly for --nofile
|
|
g_signal_connect (G_OBJECT(window), "realize",
|
|
G_CALLBACK(main_window_realize_cb), window);
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
#ifdef MAC_INTEGRATION
|
|
/* Event handlers for the shutdown process. Gnc_quartz_shutdown is
|
|
* connected to NSApplicationWillTerminate, the last chance to do
|
|
* anything before quitting. The problem is that it's launched from a
|
|
* CFRunLoop, not a g_main_loop, and if we call anything that would
|
|
* affect the main_loop we get an assert that we're in a subidiary
|
|
* loop.
|
|
*/
|
|
static void
|
|
gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
|
|
{
|
|
/* Do Nothing. It's too late. */
|
|
}
|
|
/* Should quit responds to NSApplicationBlockTermination; returning TRUE means
|
|
* "don't terminate", FALSE means "do terminate". gnc_main_window_quit() queues
|
|
* a timer that starts an orderly shutdown in 250ms and if we tell macOS it's OK
|
|
* to quit GnuCash gets terminated instead of doing its orderly shutdown,
|
|
* leaving the book locked.
|
|
*/
|
|
static gboolean
|
|
gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
|
|
{
|
|
if (gnc_main_window_all_finish_pending())
|
|
gnc_main_window_quit (window);
|
|
return TRUE;
|
|
}
|
|
/* Enable GtkMenuItem accelerators */
|
|
static gboolean
|
|
can_activate_cb(GtkWidget *widget, guint signal_id, gpointer data)
|
|
{
|
|
//return gtk_widget_is_sensitive (widget);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gnc_quartz_set_menu (GncMainWindow* window)
|
|
{
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
|
|
GtkWidget *item = nullptr;
|
|
GClosure *quit_closure;
|
|
|
|
gtk_widget_hide (priv->menubar);
|
|
gtk_widget_set_no_show_all (priv->menubar, true);
|
|
|
|
gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL(priv->menubar));
|
|
|
|
// File Quit
|
|
item = gnc_main_window_menu_find_menu_item (window, "FileQuitAction");
|
|
if (item)
|
|
gtk_widget_hide (GTK_WIDGET(item));
|
|
|
|
quit_closure = g_cclosure_new (G_CALLBACK (gnc_quartz_should_quit),
|
|
window, NULL);
|
|
gtk_accel_group_connect (priv->accel_group, 'q', GDK_META_MASK,
|
|
GTK_ACCEL_MASK, quit_closure);
|
|
|
|
|
|
// Help About
|
|
item = gnc_main_window_menu_find_menu_item (window, "HelpAboutAction");
|
|
if (item)
|
|
{
|
|
gtk_widget_hide (item);
|
|
gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 0);
|
|
}
|
|
|
|
// Edit Preferences
|
|
item = gnc_main_window_menu_find_menu_item (window, "EditPreferencesAction");
|
|
if (item)
|
|
{
|
|
gtk_widget_hide (GTK_WIDGET(item));
|
|
gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET(item), 2);
|
|
}
|
|
|
|
// Help Menu
|
|
item = gnc_main_window_menu_find_menu_item (window, "HelpAction");
|
|
if (item)
|
|
gtkosx_application_set_help_menu (theApp, GTK_MENU_ITEM(item));
|
|
// Windows Menu
|
|
item = gnc_main_window_menu_find_menu_item (window, "WindowsAction");
|
|
if (item)
|
|
gtkosx_application_set_window_menu (theApp, GTK_MENU_ITEM(item));
|
|
|
|
g_signal_connect (theApp, "NSApplicationBlockTermination",
|
|
G_CALLBACK(gnc_quartz_should_quit), window);
|
|
|
|
g_signal_connect (priv->menubar, "can-activate-accel",
|
|
G_CALLBACK (can_activate_cb), nullptr);
|
|
|
|
gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
|
|
g_object_unref (theApp);
|
|
}
|
|
#endif //MAC_INTEGRATION
|
|
|
|
/* Callbacks */
|
|
|
|
/** Should a summary bar be visible in this window? In order to
|
|
* prevent synchronization issues, the "ViewSummaryBar"
|
|
* GtkToggleAction is the sole source of information for whether or
|
|
* not any summary bar should be visible in a window.
|
|
*
|
|
* @param window A pointer to the window in question.
|
|
*
|
|
* @param action If known, a pointer to the "ViewSummaryBar"
|
|
* GtkToggleAction. If nullptr, the function will look up this action.
|
|
*
|
|
* @return TRUE if the summarybar should be visible.
|
|
*/
|
|
static gboolean
|
|
gnc_main_window_show_summarybar (GncMainWindow *window, GAction *action)
|
|
{
|
|
GVariant *state;
|
|
gboolean visible;
|
|
|
|
if (action == nullptr)
|
|
action = g_action_map_lookup_action (G_ACTION_MAP(window),
|
|
"ViewSummaryAction");
|
|
if (action == nullptr)
|
|
return TRUE;
|
|
|
|
state = g_action_get_state (G_ACTION(action));
|
|
|
|
visible = g_variant_get_boolean (state);
|
|
|
|
g_variant_unref (state);
|
|
|
|
return visible;
|
|
}
|
|
|
|
/** This function is invoked when the GtkNotebook switches pages. It
|
|
* is responsible for updating the rest of the window contents
|
|
* outside of the notebook. I.E. Updating the user interface, the
|
|
* summary bar, etc. This function also emits the "page_changed"
|
|
* signal from the window so that any plugin can also learn about the
|
|
* fact that the page has changed.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_switch_page (GtkNotebook *notebook,
|
|
gpointer *notebook_page,
|
|
gint pos,
|
|
GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GtkWidget *child;
|
|
GncPluginPage *page;
|
|
gboolean visible;
|
|
|
|
ENTER("Notebook %p, page, %p, index %d, window %p",
|
|
notebook, notebook_page, pos, window);
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
if (priv->current_page != nullptr)
|
|
{
|
|
page = priv->current_page;
|
|
gnc_plugin_page_unselected (page);
|
|
}
|
|
|
|
child = gtk_notebook_get_nth_page (notebook, pos);
|
|
if (child)
|
|
{
|
|
page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
|
|
}
|
|
else
|
|
{
|
|
page = nullptr;
|
|
}
|
|
|
|
priv->current_page = page;
|
|
|
|
if (page != nullptr)
|
|
{
|
|
/* Update the user interface (e.g. menus and toolbars */
|
|
gnc_plugin_page_merge_actions (page);
|
|
visible = gnc_main_window_show_summarybar (window, nullptr);
|
|
gnc_plugin_page_show_summarybar (page, visible);
|
|
|
|
/* Allow page specific actions */
|
|
gnc_plugin_page_selected (page);
|
|
gnc_window_update_status (GNC_WINDOW(window), page);
|
|
|
|
/* Update the page reference info */
|
|
priv->usage_order = g_list_remove (priv->usage_order, page);
|
|
priv->usage_order = g_list_prepend (priv->usage_order, page);
|
|
}
|
|
|
|
gnc_plugin_set_actions_enabled (G_ACTION_MAP(window),
|
|
multiple_page_actions,
|
|
g_list_length (priv->installed_pages) > 1);
|
|
|
|
gnc_main_window_update_title(window);
|
|
#ifndef MAC_INTEGRATION
|
|
gnc_main_window_update_menu_item(window);
|
|
#endif
|
|
g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
|
|
LEAVE(" ");
|
|
}
|
|
|
|
/** This function is invoked when a GtkNotebook tab gets reordered by
|
|
* drag and drop. It adjusts the list installed_pages to reflect the new
|
|
* ordering so that GnuCash saves and restores the tabs correctly.
|
|
*
|
|
* @internal
|
|
*/
|
|
static void
|
|
gnc_main_window_page_reordered (GtkNotebook *notebook,
|
|
GtkWidget *child,
|
|
guint pos,
|
|
GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
GList *old_link;
|
|
|
|
ENTER("Notebook %p, child %p, index %d, window %p",
|
|
notebook, child, pos, window);
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
|
|
if (!child) return;
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
page = static_cast<GncPluginPage*>(g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL));
|
|
if (!page) return;
|
|
|
|
old_link = g_list_find (priv->installed_pages, page);
|
|
if (!old_link) return;
|
|
|
|
priv->installed_pages = g_list_delete_link (priv->installed_pages,
|
|
old_link);
|
|
priv->installed_pages = g_list_insert (priv->installed_pages,
|
|
page, pos);
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_plugin_added (GncPlugin *manager,
|
|
GncPlugin *plugin,
|
|
GncMainWindow *window)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (GNC_IS_PLUGIN (plugin));
|
|
|
|
gnc_plugin_add_to_window (plugin, window, window_type);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_plugin_removed (GncPlugin *manager,
|
|
GncPlugin *plugin,
|
|
GncMainWindow *window)
|
|
{
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
|
|
g_return_if_fail (GNC_IS_PLUGIN (plugin));
|
|
|
|
gnc_plugin_remove_from_window (plugin, window, window_type);
|
|
}
|
|
|
|
|
|
/* Command callbacks */
|
|
static void
|
|
gnc_main_window_cmd_redirect (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GAction *redirect_action;
|
|
|
|
PINFO("Redirect action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
|
|
|
|
redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
|
|
|
|
if (redirect_action)
|
|
{
|
|
PINFO("Found action %p", redirect_action);
|
|
g_action_activate (redirect_action, nullptr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_page_setup (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GtkWindow *gtk_window;
|
|
|
|
g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
|
|
|
|
gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
|
|
gnc_ui_page_setup(gtk_window);
|
|
}
|
|
|
|
gboolean
|
|
gnc_book_options_dialog_apply_helper(GncOptionDB * options)
|
|
{
|
|
QofBook *book = gnc_get_current_book ();
|
|
gboolean use_split_action_for_num_before =
|
|
qof_book_use_split_action_for_num_field (book);
|
|
gint use_read_only_threshold_before =
|
|
qof_book_get_num_days_autoreadonly (book);
|
|
gboolean use_split_action_for_num_after;
|
|
gint use_read_only_threshold_after;
|
|
gboolean return_val = FALSE;
|
|
GList *results = nullptr, *iter;
|
|
|
|
if (!options) return return_val;
|
|
|
|
results = gnc_option_db_commit (options);
|
|
for (iter = results; iter; iter = iter->next)
|
|
{
|
|
GtkWidget *dialog = gtk_message_dialog_new(gnc_ui_get_main_window (nullptr),
|
|
(GtkDialogFlags)0,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_OK,
|
|
"%s",
|
|
(char*)iter->data);
|
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
g_free (iter->data);
|
|
}
|
|
g_list_free (results);
|
|
qof_book_begin_edit (book);
|
|
qof_book_save_options (book, gnc_option_db_save, options, TRUE);
|
|
use_split_action_for_num_after =
|
|
qof_book_use_split_action_for_num_field (book);
|
|
|
|
// mark cached value as invalid so we get new value
|
|
book->cached_num_days_autoreadonly_isvalid = FALSE;
|
|
use_read_only_threshold_after = qof_book_get_num_days_autoreadonly (book);
|
|
|
|
if (use_split_action_for_num_before != use_split_action_for_num_after)
|
|
{
|
|
gnc_book_option_num_field_source_change_cb (
|
|
use_split_action_for_num_after);
|
|
return_val = TRUE;
|
|
}
|
|
if (use_read_only_threshold_before != use_read_only_threshold_after)
|
|
return_val = TRUE;
|
|
|
|
qof_book_commit_edit (book);
|
|
return return_val;
|
|
}
|
|
|
|
static void
|
|
gnc_book_options_dialog_apply_cb(GncOptionsDialog * optionwin,
|
|
gpointer user_data)
|
|
{
|
|
auto options{static_cast<GncOptionDB *>(user_data)};
|
|
|
|
if (!options) return;
|
|
|
|
if (gnc_book_options_dialog_apply_helper (options))
|
|
gnc_gui_refresh_all ();
|
|
}
|
|
|
|
static void
|
|
gnc_book_options_dialog_close_cb(GncOptionsDialog * optionwin,
|
|
gpointer user_data)
|
|
{
|
|
auto options{static_cast<GncOptionDB *>(user_data)};
|
|
|
|
delete optionwin;
|
|
gnc_option_db_destroy(options);
|
|
}
|
|
|
|
/** Calls gnc_book_option_num_field_source_change to initiate registered
|
|
* callbacks when num_field_source book option changes so that
|
|
* registers/reports can update themselves; sets feature flag */
|
|
void
|
|
gnc_book_option_num_field_source_change_cb (gboolean num_action)
|
|
{
|
|
gnc_suspend_gui_refresh ();
|
|
if (num_action)
|
|
{
|
|
/* Set a feature flag in the book for use of the split action field as number.
|
|
* This will prevent older GnuCash versions that don't support this feature
|
|
* from opening this file. */
|
|
gnc_features_set_used (gnc_get_current_book(),
|
|
GNC_FEATURE_NUM_FIELD_SOURCE);
|
|
}
|
|
gnc_book_option_num_field_source_change (num_action);
|
|
gnc_resume_gui_refresh ();
|
|
}
|
|
|
|
static gboolean
|
|
show_handler (const char *class_name, gint component_id,
|
|
gpointer user_data, gpointer iter_data)
|
|
{
|
|
auto optwin{static_cast<GncOptionsDialog*>(user_data)};
|
|
|
|
if (!optwin)
|
|
return(FALSE);
|
|
|
|
auto widget = optwin->get_widget();
|
|
gtk_window_present(GTK_WINDOW(widget));
|
|
return(TRUE);
|
|
}
|
|
|
|
GtkWidget *
|
|
gnc_book_options_dialog_cb (gboolean modal, gchar *title, GtkWindow* parent)
|
|
{
|
|
auto book = gnc_get_current_book ();
|
|
|
|
auto options = gnc_option_db_new();
|
|
gnc_option_db_book_options(options);
|
|
qof_book_load_options (book, gnc_option_db_load, options);
|
|
gnc_option_db_clean (options);
|
|
|
|
/* Only allow one Book Options dialog if called from file->properties
|
|
menu */
|
|
if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
|
|
show_handler, nullptr))
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto optionwin = new GncOptionsDialog (modal,
|
|
(title ? title : _( "Book Options")),
|
|
DIALOG_BOOK_OPTIONS_CM_CLASS, parent);
|
|
optionwin->build_contents(options);
|
|
optionwin->set_book_help_cb();
|
|
optionwin->set_apply_cb(gnc_book_options_dialog_apply_cb,
|
|
(gpointer)options);
|
|
optionwin->set_close_cb ( gnc_book_options_dialog_close_cb,
|
|
(gpointer)options);
|
|
if (modal)
|
|
gnc_options_dialog_set_new_book_option_values (options);
|
|
return optionwin->get_widget();
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_file_properties (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
gnc_book_options_dialog_cb (FALSE, nullptr, GTK_WINDOW (window));
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_file_close (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
|
|
g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page = priv->current_page;
|
|
gnc_main_window_close_page(page);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_file_quit (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
if (!gnc_main_window_all_finish_pending())
|
|
return;
|
|
|
|
gnc_main_window_quit(window);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_edit_cut (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
|
|
GAction *redirect_action;
|
|
|
|
PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
|
|
|
|
redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
|
|
|
|
if (redirect_action)
|
|
{
|
|
PINFO("Found action %p", redirect_action);
|
|
g_action_activate (redirect_action, nullptr);
|
|
return;
|
|
}
|
|
|
|
if (GTK_IS_EDITABLE(widget))
|
|
{
|
|
gtk_editable_cut_clipboard (GTK_EDITABLE(widget));
|
|
}
|
|
else if (GTK_IS_TEXT_VIEW(widget))
|
|
{
|
|
GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
|
|
GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
|
|
GDK_SELECTION_CLIPBOARD);
|
|
gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
|
|
|
|
if (clipboard)
|
|
gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_edit_copy (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
|
|
GAction *redirect_action;
|
|
|
|
PINFO("Copy action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
|
|
|
|
redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
|
|
|
|
if (redirect_action)
|
|
{
|
|
PINFO("Found action %p", redirect_action);
|
|
g_action_activate (redirect_action, nullptr);
|
|
return;
|
|
}
|
|
|
|
if (GTK_IS_EDITABLE(widget))
|
|
{
|
|
gtk_editable_copy_clipboard (GTK_EDITABLE(widget));
|
|
}
|
|
else if (GTK_IS_TEXT_VIEW(widget))
|
|
{
|
|
GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
|
|
GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
|
|
GDK_SELECTION_CLIPBOARD);
|
|
if (clipboard)
|
|
gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_edit_paste (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
|
|
GAction *redirect_action;
|
|
|
|
PINFO("Paste action_is %p, name is '%s'", simple, g_action_get_name (G_ACTION(simple)));
|
|
|
|
redirect_action = gnc_main_window_get_redirect (window, g_action_get_name (G_ACTION(simple)));
|
|
|
|
if (redirect_action)
|
|
{
|
|
PINFO("Found action %p", redirect_action);
|
|
g_action_activate (redirect_action, nullptr);
|
|
return;
|
|
}
|
|
|
|
if (GTK_IS_EDITABLE(widget))
|
|
{
|
|
gtk_editable_paste_clipboard (GTK_EDITABLE(widget));
|
|
}
|
|
else if (GTK_IS_TEXT_VIEW(widget))
|
|
{
|
|
auto clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
|
|
|
|
if (clipboard)
|
|
{
|
|
auto text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
|
|
auto editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
|
|
gtk_text_buffer_paste_clipboard (text_buffer, clipboard, nullptr,
|
|
editable);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_edit_preferences (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
gnc_preferences_dialog (GTK_WINDOW(window));
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_view_refresh (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_actions_reset_warnings (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
gnc_reset_warnings_dialog(GTK_WINDOW(window));
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_actions_rename_page (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv;
|
|
GncPluginPage *page;
|
|
GtkWidget *label, *entry;
|
|
|
|
ENTER(" ");
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page = priv->current_page;
|
|
if (!page)
|
|
{
|
|
LEAVE("No current page");
|
|
return;
|
|
}
|
|
|
|
if (!main_window_find_tab_items(window, page, &label, &entry))
|
|
{
|
|
LEAVE("can't find required widgets");
|
|
return;
|
|
}
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
|
|
gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
|
|
gtk_widget_hide(label);
|
|
gtk_widget_show(entry);
|
|
gtk_widget_grab_focus(entry);
|
|
LEAVE("opened for editing");
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_view_toolbar (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
GVariant *state = g_action_get_state (G_ACTION(simple));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
|
|
|
|
if (!g_variant_get_boolean (state))
|
|
gtk_widget_show (priv->toolbar);
|
|
else
|
|
gtk_widget_hide (priv->toolbar);
|
|
|
|
g_variant_unref (state);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_view_summary (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
GList *item;
|
|
gboolean visible;
|
|
|
|
visible = gnc_main_window_show_summarybar (window, G_ACTION(simple));
|
|
|
|
g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!visible));
|
|
|
|
for (item = priv->installed_pages; item; item = g_list_next (item))
|
|
{
|
|
gnc_plugin_page_show_summarybar (static_cast<GncPluginPage*>(item->data),
|
|
!visible);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_view_statusbar (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
GVariant *state = g_action_get_state (G_ACTION(simple));
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
g_action_change_state (G_ACTION(simple), g_variant_new_boolean (!g_variant_get_boolean (state)));
|
|
|
|
if (!g_variant_get_boolean (state))
|
|
gtk_widget_show (priv->statusbar);
|
|
else
|
|
gtk_widget_hide (priv->statusbar);
|
|
|
|
g_variant_unref (state);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_window_new (GSimpleAction *simple,
|
|
GVariant *paramter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *new_window;
|
|
|
|
/* Create the new window */
|
|
ENTER(" ");
|
|
new_window = gnc_main_window_new ();
|
|
gtk_widget_show(GTK_WIDGET(new_window));
|
|
LEAVE(" ");
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_window_move_page (GSimpleAction *simple,
|
|
GVariant *paramter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindowPrivate *priv;
|
|
GncMainWindow *new_window;
|
|
GncPluginPage *page;
|
|
GtkNotebook *notebook;
|
|
GtkWidget *tab_widget, *menu_widget;
|
|
|
|
ENTER("action %p, window %p", simple, window);
|
|
|
|
/* Setup */
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
page = priv->current_page;
|
|
if (!page)
|
|
{
|
|
LEAVE("invalid page");
|
|
return;
|
|
}
|
|
if (!page->notebook_page)
|
|
{
|
|
LEAVE("invalid notebook_page");
|
|
return;
|
|
}
|
|
|
|
#ifndef MAC_INTEGRATION
|
|
if (gnc_list_length_cmp (active_windows, gnc_main_window_max_number) == 0)
|
|
gnc_info_dialog (GTK_WINDOW(window), "%s",
|
|
_("The maximum number of window menu entries reached, no more entries will be added."));
|
|
#endif /* !MAC_INTEGRATION */
|
|
|
|
notebook = GTK_NOTEBOOK (priv->notebook);
|
|
tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
|
|
menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
|
|
|
|
// Remove the page_changed signal callback
|
|
gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
|
|
|
|
/* Ref the page components, then remove it from its old window */
|
|
g_object_ref(page);
|
|
g_object_ref(tab_widget);
|
|
g_object_ref(menu_widget);
|
|
g_object_ref(page->notebook_page);
|
|
gnc_main_window_disconnect(window, page);
|
|
|
|
/* Create the new window */
|
|
new_window = gnc_main_window_new ();
|
|
gtk_widget_show(GTK_WIDGET(new_window));
|
|
|
|
/* Now add the page to the new window */
|
|
gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
|
|
|
|
/* Unref the page components now that we're done */
|
|
g_object_unref(page->notebook_page);
|
|
g_object_unref(menu_widget);
|
|
g_object_unref(tab_widget);
|
|
g_object_unref(page);
|
|
|
|
/* just a little debugging. :-) */
|
|
DEBUG("Moved page %p from window %p to new window %p",
|
|
page, window, new_window);
|
|
DEBUG("Old window current is %p, new window current is %p",
|
|
priv->current_page, priv->current_page);
|
|
|
|
LEAVE("page moved");
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_view_tab_position (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
gint item = g_variant_get_int32 (parameter);
|
|
|
|
g_action_change_state (G_ACTION(simple), parameter);
|
|
|
|
if (item < 0 || item > 3)
|
|
return;
|
|
|
|
if (item != 0 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP))
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, FALSE);
|
|
|
|
if (item != 1 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, FALSE);
|
|
|
|
if (item != 2 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, FALSE);
|
|
|
|
if (item != 3 && gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, FALSE);
|
|
|
|
switch (item)
|
|
{
|
|
case 0:
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_TOP, TRUE);
|
|
break;
|
|
|
|
case 1:
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM, TRUE);
|
|
break;
|
|
|
|
case 2:
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT, TRUE);
|
|
break;
|
|
|
|
case 3:
|
|
gnc_prefs_set_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT, TRUE);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
#ifndef MAC_INTEGRATION
|
|
static void
|
|
gnc_main_window_cmd_window_raise (GSimpleAction *simple,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
GncMainWindow *new_window;
|
|
gint item;
|
|
|
|
g_return_if_fail (G_IS_SIMPLE_ACTION(simple));
|
|
g_return_if_fail (GNC_IS_MAIN_WINDOW(window));
|
|
|
|
item = g_variant_get_int32 (parameter);
|
|
|
|
ENTER("action %p, window %p, item %d", simple, window, item);
|
|
|
|
g_action_change_state (G_ACTION(simple), parameter);
|
|
|
|
new_window = static_cast<GncMainWindow*>(g_list_nth_data (active_windows, item));
|
|
gtk_window_present (GTK_WINDOW(new_window));
|
|
|
|
/* revert the change in the radio group
|
|
* impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
|
|
g_idle_add ((GSourceFunc)gnc_main_window_update_radio_button, window);
|
|
LEAVE(" ");
|
|
}
|
|
#endif /* !MAC_INTEGRATION */
|
|
|
|
static void
|
|
gnc_main_window_cmd_help_tutorial (GSimpleAction *simple,
|
|
GVariant *paramter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
gnc_gnome_help (GTK_WINDOW(window), DF_GUIDE, NULL);
|
|
}
|
|
|
|
static void
|
|
gnc_main_window_cmd_help_contents (GSimpleAction *simple,
|
|
GVariant *paramter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
gnc_gnome_help (GTK_WINDOW(window), DF_MANUAL, NULL);
|
|
}
|
|
|
|
/** This is a helper function to find a data file and suck it into
|
|
* memory.
|
|
*
|
|
* @param partial The name of the file relative to the gnucash
|
|
* specific shared data directory.
|
|
*
|
|
* @return The text of the file or nullptr. The caller is responsible
|
|
* for freeing this string.
|
|
*/
|
|
static gchar *
|
|
get_file (const gchar *partial)
|
|
{
|
|
gchar *filename, *text = nullptr;
|
|
gsize length;
|
|
|
|
filename = gnc_filepath_locate_doc_file(partial);
|
|
if (filename && g_file_get_contents(filename, &text, &length, nullptr))
|
|
{
|
|
if (length)
|
|
{
|
|
g_free(filename);
|
|
return text;
|
|
}
|
|
g_free(text);
|
|
}
|
|
g_free (filename);
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/** This is a helper function to find a data file, suck it into
|
|
* memory, and split it into an array of strings.
|
|
*
|
|
* @param partial The name of the file relative to the gnucash
|
|
* specific shared data directory.
|
|
*
|
|
* @return The text of the file as an array of strings, or nullptr. The
|
|
* caller is responsible for freeing all the strings and the array.
|
|
*/
|
|
static gchar **
|
|
get_file_strsplit (const gchar *partial)
|
|
{
|
|
gchar *text, **lines;
|
|
|
|
text = get_file(partial);
|
|
if (!text)
|
|
return nullptr;
|
|
|
|
lines = g_strsplit_set(text, "\r\n", -1);
|
|
g_free(text);
|
|
return lines;
|
|
}
|
|
/** URL activation callback.
|
|
* Use our own function to activate the URL in the users browser
|
|
* instead of gtk_show_uri(), which requires gvfs.
|
|
* Signature described in gtk docs at GtkAboutDialog activate-link signal.
|
|
*/
|
|
|
|
static gboolean
|
|
url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
|
|
{
|
|
gnc_launch_doclink (GTK_WINDOW(dialog), uri);
|
|
return TRUE;
|
|
}
|
|
|
|
#define DEFAULT_MARGIN 5
|
|
|
|
static void
|
|
set_text_cursor (GdkWindow *win, GdkCursorType type)
|
|
{
|
|
if (!win && !type)
|
|
return;
|
|
|
|
GdkCursor *current = gdk_window_get_cursor (win);
|
|
if (type == gdk_cursor_get_cursor_type (current))
|
|
return;
|
|
|
|
GdkCursor *cur = gdk_cursor_new_for_display (gdk_window_get_display (win), type);
|
|
gdk_window_set_cursor (win, cur);
|
|
}
|
|
|
|
static gboolean
|
|
textview_motion_notify_cb (GtkWidget *textview,
|
|
GdkEventMotion *event,
|
|
gpointer user_data)
|
|
{
|
|
if ((event->state & GDK_BUTTON1_MASK) ||
|
|
gtk_text_buffer_get_has_selection (gtk_text_view_get_buffer
|
|
(GTK_TEXT_VIEW(textview))))
|
|
return false;
|
|
|
|
GtkTextIter iter;
|
|
gboolean valid = gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(textview),
|
|
&iter, event->x, event->y);
|
|
|
|
if (valid && (event->y > DEFAULT_MARGIN))
|
|
{
|
|
GSList *tt_list = gtk_text_iter_get_tags (&iter);
|
|
|
|
if (tt_list)
|
|
{
|
|
GtkTextTag *tt = (GtkTextTag*)g_slist_nth_data (tt_list, 0);
|
|
|
|
if (g_object_get_data (G_OBJECT(tt), "link"))
|
|
set_text_cursor (event->window, GDK_HAND1);
|
|
else
|
|
set_text_cursor (event->window, GDK_XTERM);
|
|
|
|
g_slist_free (tt_list);
|
|
}
|
|
else
|
|
set_text_cursor (event->window, GDK_XTERM);
|
|
}
|
|
else
|
|
set_text_cursor (event->window, GDK_XTERM);
|
|
|
|
return true;
|
|
}
|
|
|
|
static gboolean
|
|
textview_url_activate (GtkTextTag *tag,
|
|
GObject *object,
|
|
GdkEvent *event,
|
|
GtkTextIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
GdkEventButton *event_button = (GdkEventButton*)event;
|
|
|
|
if ((event->type == GDK_BUTTON_RELEASE) &&
|
|
(event_button->button == 1) &&
|
|
!gtk_text_buffer_get_has_selection (gtk_text_view_get_buffer
|
|
(GTK_TEXT_VIEW (object))) &&
|
|
(event_button->y > DEFAULT_MARGIN))
|
|
{
|
|
gchar *link = (gchar*)g_object_get_data (G_OBJECT(tag), "link");
|
|
PINFO("Link is '%s'", link);
|
|
gchar *escaped_uri = g_uri_escape_string (link, ":/.\\", true);
|
|
PINFO("Escaped Link is '%s'", escaped_uri);
|
|
gnc_launch_doclink (GTK_WINDOW(user_data), escaped_uri);
|
|
g_free (escaped_uri);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static gint
|
|
get_max_text_width (GtkTextView *textview, std::vector<EnvPaths> ep_vec)
|
|
{
|
|
gint max_text_width = 0;
|
|
for (const auto& ep : ep_vec)
|
|
{
|
|
gint text_width;
|
|
gchar *env_name = g_strconcat (ep.env_name, ":", nullptr);
|
|
|
|
PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(textview),
|
|
env_name);
|
|
|
|
pango_layout_get_pixel_size (layout, &text_width, nullptr);
|
|
g_object_unref (layout);
|
|
g_free (env_name);
|
|
|
|
max_text_width = MAX(max_text_width, text_width);
|
|
}
|
|
return max_text_width;
|
|
}
|
|
|
|
static GtkTextTag *
|
|
create_left_margin_text_tag (GtkTextView *textview,
|
|
gchar *lmargin_tag,
|
|
const gchar *text,
|
|
gint max_width)
|
|
{
|
|
if (!lmargin_tag)
|
|
return nullptr;
|
|
|
|
GtkTextTag *lmargin_tt = gtk_text_tag_new (lmargin_tag);
|
|
int text_width;
|
|
|
|
PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(textview), text);
|
|
pango_layout_get_pixel_size (layout, &text_width, nullptr);
|
|
g_object_unref (layout);
|
|
|
|
g_object_set (G_OBJECT(lmargin_tt), "left_margin",
|
|
DEFAULT_MARGIN + max_width - text_width, nullptr);
|
|
|
|
return lmargin_tt;
|
|
}
|
|
|
|
static GdkRGBA
|
|
get_link_color (void)
|
|
{
|
|
GdkRGBA link_color;
|
|
GtkWidget *dummy_link_button = gtk_link_button_new_with_label ("https://www.gnucash.org", "Dummy");
|
|
GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(dummy_link_button));
|
|
gtk_style_context_get_color (context, GTK_STATE_FLAG_LINK, &link_color);
|
|
|
|
return link_color;
|
|
}
|
|
|
|
static GtkTextTag *
|
|
create_url_text_tag (GtkDialog *dialog,
|
|
GdkRGBA link_color,
|
|
gchar *url_tag,
|
|
const gchar *uri)
|
|
{
|
|
if (!url_tag)
|
|
return nullptr;
|
|
|
|
GtkTextTag *url_tt = gtk_text_tag_new (url_tag);
|
|
g_object_set (G_OBJECT(url_tt), "underline", PANGO_UNDERLINE_SINGLE,
|
|
"underline-set", true, nullptr);
|
|
g_object_set (G_OBJECT(url_tt), "foreground-rgba", &link_color, nullptr);
|
|
|
|
g_object_set_data_full (G_OBJECT(url_tt), "link", g_strdup (uri), g_free);
|
|
|
|
g_signal_connect (G_OBJECT(url_tt), "event",
|
|
G_CALLBACK(textview_url_activate), dialog);
|
|
return url_tt;
|
|
}
|
|
|
|
static void
|
|
add_textview_css_class (GtkTextView *textview)
|
|
{
|
|
GdkRGBA color;
|
|
GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(textview));
|
|
gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
|
|
|
|
if (gnc_is_dark_theme (&color))
|
|
gtk_style_context_add_class (stylectxt, "gnc-class-textview-dark");
|
|
else
|
|
gtk_style_context_add_class (stylectxt, "gnc-class-textview");
|
|
}
|
|
|
|
static void
|
|
add_about_paths (GtkDialog *dialog)
|
|
{
|
|
GtkWidget *page_vbox = gnc_get_dialog_widget_from_id (dialog, "page_vbox");
|
|
|
|
if (!page_vbox)
|
|
{
|
|
PWARN("Unable to find AboutDialog 'page_vbox' Widget");
|
|
return;
|
|
}
|
|
|
|
GtkWidget *textview = gtk_text_view_new ();
|
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(textview));
|
|
GtkTextTagTable *ttt = gtk_text_buffer_get_tag_table (buffer);
|
|
GtkTextIter iter;
|
|
|
|
std::vector<EnvPaths> ep_vec = gnc_list_all_paths ();
|
|
int ep_size = (int)ep_vec.size();
|
|
int row = 1;
|
|
|
|
GdkRGBA link_color = get_link_color ();
|
|
gint max_text_width = get_max_text_width (GTK_TEXT_VIEW(textview), ep_vec);
|
|
|
|
add_textview_css_class (GTK_TEXT_VIEW(textview));
|
|
|
|
gtk_text_view_set_left_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
|
|
gtk_text_view_set_right_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
|
|
gtk_text_view_set_top_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
|
|
gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW(textview), DEFAULT_MARGIN);
|
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(textview), GTK_WRAP_NONE);
|
|
|
|
gtk_text_buffer_create_tag (buffer, "italic", "style", PANGO_STYLE_ITALIC, nullptr);
|
|
|
|
gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
|
|
|
|
for (const auto& ep : ep_vec)
|
|
{
|
|
gchar *env_name = g_strconcat (ep.env_name, ":", nullptr);
|
|
const gchar *uri = gnc_uri_create_uri ("file", nullptr, 0, nullptr, nullptr, ep.env_path);
|
|
gchar *display_uri = gnc_doclink_get_unescaped_just_uri (uri);
|
|
|
|
gchar *url_tag = g_strdup_printf ("%s%d", "url_tag", row);
|
|
gchar *lmargin_tag = g_strdup_printf ("%s%d", "lmargin_tag", row);
|
|
|
|
gtk_text_tag_table_add (ttt, create_left_margin_text_tag (GTK_TEXT_VIEW(textview),
|
|
lmargin_tag,
|
|
env_name,
|
|
max_text_width));
|
|
gtk_text_tag_table_add (ttt, create_url_text_tag (dialog,
|
|
link_color,
|
|
url_tag,
|
|
uri));
|
|
|
|
gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, env_name, -1, lmargin_tag, nullptr);
|
|
gtk_text_buffer_insert (buffer, &iter, " ", -1);
|
|
gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, display_uri, -1, url_tag, nullptr);
|
|
|
|
g_free (url_tag);
|
|
g_free (lmargin_tag);
|
|
g_free (display_uri);
|
|
g_free (env_name);
|
|
|
|
if (ep.modifiable)
|
|
{
|
|
gtk_text_buffer_insert (buffer, &iter, " ", -1);
|
|
gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, _("(User modifiable)"), -1, "italic", nullptr);
|
|
}
|
|
|
|
if (row != ep_size)
|
|
gtk_text_buffer_insert (buffer, &iter, "\n", -1);
|
|
|
|
row++;
|
|
}
|
|
gtk_text_view_set_editable (GTK_TEXT_VIEW(textview), false);
|
|
|
|
g_signal_connect (G_OBJECT(textview), "motion-notify-event",
|
|
G_CALLBACK(textview_motion_notify_cb), nullptr);
|
|
|
|
gtk_container_add_with_properties (GTK_CONTAINER(page_vbox), textview,
|
|
"position", 1, nullptr);
|
|
gtk_widget_show_all (page_vbox);
|
|
}
|
|
|
|
/** Create and display the "about" dialog for gnucash.
|
|
*/
|
|
static void
|
|
gnc_main_window_cmd_help_about (GSimpleAction *simple,
|
|
GVariant *paramter,
|
|
gpointer user_data)
|
|
{
|
|
GncMainWindow *window = (GncMainWindow*)user_data;
|
|
/* Translators: %s will be replaced with the current year */
|
|
gchar *copyright = g_strdup_printf(_("Copyright © 1997-%s The GnuCash contributors."),
|
|
GNC_VCS_REV_YEAR);
|
|
gchar **authors = get_file_strsplit("AUTHORS");
|
|
gchar **documenters = get_file_strsplit("DOCUMENTERS");
|
|
gchar *license = get_file("LICENSE");
|
|
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
|
GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
|
|
GNC_ICON_APP,
|
|
128,
|
|
GTK_ICON_LOOKUP_USE_BUILTIN,
|
|
nullptr);
|
|
gchar *version = g_strdup_printf ("%s: %s\n%s: %s\nFinance::Quote: %s",
|
|
_("Version"), gnc_version(),
|
|
_("Build ID"), gnc_build_id(),
|
|
gnc_quote_source_fq_version ()
|
|
? gnc_quote_source_fq_version ()
|
|
: "-");
|
|
GtkDialog *dialog = GTK_DIALOG (gtk_about_dialog_new ());
|
|
g_object_set(G_OBJECT(dialog), "authors", authors, "documenters",
|
|
documenters, "comments",
|
|
_("Accounting for personal and small business finance."),
|
|
"copyright", copyright, "license", license, "logo", logo,
|
|
"name", "GnuCash",
|
|
/* Translators: the following string will be shown in
|
|
* Help->About->Credits It's intended to be generated
|
|
* automatically before each release so there's
|
|
* usually no need to modify it, but if you do be
|
|
* sure to include a newline (\\n) after each credit
|
|
* so that it displays one per line.
|
|
*/
|
|
"translator-credits", _("translator-credits\n"),
|
|
"version", version,
|
|
"website", PACKAGE_URL,
|
|
"website-label", _("Visit the GnuCash website."),
|
|
nullptr);
|
|
|
|
g_free(version);
|
|
g_free(copyright);
|
|
if (license)
|
|
g_free(license);
|
|
if (documenters)
|
|
g_strfreev(documenters);
|
|
if (authors)
|
|
g_strfreev(authors);
|
|
g_object_unref (logo);
|
|
g_signal_connect (dialog, "activate-link",
|
|
G_CALLBACK (url_signal_cb), nullptr);
|
|
|
|
// Add environment paths
|
|
add_about_paths (dialog);
|
|
|
|
/* Set dialog to resize. */
|
|
gtk_window_set_resizable(GTK_WINDOW (dialog), TRUE);
|
|
|
|
gtk_window_set_transient_for (GTK_WINDOW (dialog),
|
|
GTK_WINDOW (window));
|
|
gtk_dialog_run (dialog);
|
|
gtk_widget_destroy (GTK_WIDGET (dialog));
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* *
|
|
************************************************************/
|
|
|
|
void
|
|
gnc_main_window_show_all_windows(void)
|
|
{
|
|
GList *window_iter;
|
|
#ifdef MAC_INTEGRATION
|
|
auto theApp{static_cast<GtkosxApplication *>(g_object_new(GTKOSX_TYPE_APPLICATION, nullptr))};
|
|
#endif
|
|
for (window_iter = active_windows; window_iter != nullptr; window_iter = window_iter->next)
|
|
{
|
|
gtk_widget_show(GTK_WIDGET(window_iter->data));
|
|
}
|
|
#ifdef MAC_INTEGRATION
|
|
g_signal_connect(theApp, "NSApplicationWillTerminate",
|
|
G_CALLBACK(gnc_quartz_shutdown), nullptr);
|
|
gtkosx_application_ready(theApp);
|
|
g_object_unref (theApp);
|
|
#endif
|
|
}
|
|
|
|
GtkWindow *
|
|
gnc_ui_get_gtk_window (GtkWidget *widget)
|
|
{
|
|
GtkWidget *toplevel;
|
|
|
|
if (!widget)
|
|
return nullptr;
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
if (toplevel && GTK_IS_WINDOW (toplevel))
|
|
return GTK_WINDOW (toplevel);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
GtkWindow *
|
|
gnc_ui_get_main_window (GtkWidget *widget)
|
|
{
|
|
GList *window;
|
|
|
|
GtkWindow *toplevel = gnc_ui_get_gtk_window (widget);
|
|
while (toplevel && !GNC_IS_MAIN_WINDOW (toplevel))
|
|
toplevel = gtk_window_get_transient_for(toplevel);
|
|
|
|
if (toplevel)
|
|
return toplevel;
|
|
|
|
for (window = active_windows; window; window = window->next)
|
|
if (gtk_window_is_active (GTK_WINDOW (window->data)))
|
|
return static_cast<GtkWindow*>(window->data);
|
|
|
|
for (window = active_windows; window; window = window->next)
|
|
if (gtk_widget_get_mapped (GTK_WIDGET(window->data)))
|
|
return static_cast<GtkWindow*>(window->data);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/** Retrieve the gtk window associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window A pointer to a generic window. */
|
|
static GtkWindow *
|
|
gnc_main_window_get_gtk_window (GncWindow *window)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), nullptr);
|
|
return GTK_WINDOW(window);
|
|
}
|
|
|
|
|
|
/** Retrieve the status bar associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GtkWidget *
|
|
gnc_main_window_get_statusbar (GncWindow *window_in)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncMainWindow *window;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
|
|
|
|
window = GNC_MAIN_WINDOW(window_in);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
return priv->statusbar;
|
|
}
|
|
|
|
|
|
/** Retrieve the progress bar associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GtkWidget *
|
|
gnc_main_window_get_progressbar (GncWindow *window_in)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
GncMainWindow *window;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), nullptr);
|
|
|
|
window = GNC_MAIN_WINDOW(window_in);
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
return priv->progressbar;
|
|
}
|
|
|
|
|
|
/** Retrieve the menu bar associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GtkWidget *
|
|
gnc_main_window_get_menubar (GncWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return priv->menubar;
|
|
}
|
|
|
|
/** Retrieve the tool bar associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GtkWidget *
|
|
gnc_main_window_get_toolbar (GncWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return priv->toolbar;
|
|
}
|
|
|
|
/** Retrieve the menubar model associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GMenuModel *
|
|
gnc_main_window_get_menubar_model (GncWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return priv->menubar_model;
|
|
}
|
|
|
|
/** Retrieve the accelerator group associated with a main window object.
|
|
* This function is called via a vector off a generic window
|
|
* interface.
|
|
*
|
|
* @param window_in A pointer to a generic window. */
|
|
static GtkAccelGroup *
|
|
gnc_main_window_get_accel_group (GncWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return priv->accel_group;
|
|
}
|
|
|
|
/** Initialize the generic window interface for a main window.
|
|
*
|
|
* @param iface A pointer to the interface data structure to
|
|
* populate. */
|
|
static void
|
|
gnc_window_main_window_init (GncWindowInterface *iface)
|
|
{
|
|
iface->get_gtk_window = gnc_main_window_get_gtk_window;
|
|
iface->get_statusbar = gnc_main_window_get_statusbar;
|
|
iface->get_progressbar = gnc_main_window_get_progressbar;
|
|
iface->get_menubar = gnc_main_window_get_menubar;
|
|
iface->get_toolbar = gnc_main_window_get_toolbar;
|
|
iface->get_menubar_model = gnc_main_window_get_menubar_model;
|
|
iface->get_accel_group = gnc_main_window_get_accel_group;
|
|
}
|
|
|
|
|
|
/* Set the window where all progressbar updates should occur. This
|
|
* is a wrapper around the gnc_window_set_progressbar_window()
|
|
* function.
|
|
*/
|
|
void
|
|
gnc_main_window_set_progressbar_window (GncMainWindow *window)
|
|
{
|
|
GncWindow *gncwin;
|
|
gncwin = GNC_WINDOW(window);
|
|
gnc_window_set_progressbar_window(gncwin);
|
|
}
|
|
|
|
|
|
/** Popup a contextual menu. This function ends up being called when
|
|
* the user right-clicks in the context of a window, or uses the
|
|
* keyboard context-menu request key combination (Shift-F10 by
|
|
* default).
|
|
*
|
|
* @param page This is the GncPluginPage corresponding to the visible
|
|
* page.
|
|
*
|
|
* @param event The event parameter passed to the "button-press"
|
|
* callback. May be null if there was no event (aka keyboard
|
|
* request).
|
|
*/
|
|
static void
|
|
do_popup_menu (GncPluginPage *page, GdkEventButton *event)
|
|
{
|
|
GtkBuilder *builder;
|
|
GMenuModel *menu_model;
|
|
GtkWidget *menu;
|
|
const gchar *menu_qualifier;
|
|
gchar *popup_menu_name;
|
|
GncWindow* gnc_window;
|
|
GtkWidget *statusbar;
|
|
|
|
g_return_if_fail (GNC_IS_PLUGIN_PAGE(page));
|
|
|
|
ENTER("page %p, event %p", page, event);
|
|
|
|
gnc_window = GNC_WINDOW(GNC_PLUGIN_PAGE(page)->window);
|
|
|
|
statusbar = gnc_window_get_statusbar (gnc_window);
|
|
|
|
builder = gnc_plugin_page_get_builder (page);
|
|
|
|
menu_qualifier = gnc_plugin_page_get_menu_popup_qualifier (page);
|
|
|
|
if (!menu_qualifier)
|
|
menu_qualifier = gnc_plugin_page_get_menu_qualifier (page);
|
|
|
|
if (builder == nullptr)
|
|
{
|
|
LEAVE("no builder");
|
|
return;
|
|
}
|
|
|
|
if (menu_qualifier)
|
|
popup_menu_name = g_strconcat ("mainwin-popup-", menu_qualifier, nullptr);
|
|
else
|
|
popup_menu_name = g_strdup ("mainwin-popup");
|
|
|
|
menu_model = (GMenuModel *)gtk_builder_get_object (builder, popup_menu_name);
|
|
|
|
if (!menu_model)
|
|
menu_model = (GMenuModel *)gtk_builder_get_object (builder, "mainwin-popup");
|
|
|
|
menu = gtk_menu_new_from_model (menu_model);
|
|
|
|
if (!menu)
|
|
{
|
|
LEAVE("no menu");
|
|
return;
|
|
}
|
|
|
|
// add tooltip redirect call backs
|
|
gnc_plugin_add_menu_tooltip_callbacks (menu, menu_model, statusbar);
|
|
|
|
gtk_menu_attach_to_widget (GTK_MENU(menu), GTK_WIDGET(page->window), nullptr);
|
|
gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
|
|
|
|
g_free (popup_menu_name);
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
|
|
/** Callback function invoked when the user requests that Gnucash
|
|
* popup the contextual menu via the keyboard context-menu request
|
|
* key combination (Shift-F10 by default).
|
|
*
|
|
* @param page This is the GncPluginPage corresponding to the visible
|
|
* page.
|
|
*
|
|
* @param widget Whatever widget had focus when the user issued the
|
|
* keyboard context-menu request.
|
|
*
|
|
* @return Always returns TRUE to indicate that the menu request was
|
|
* handled.
|
|
*/
|
|
gboolean
|
|
gnc_main_window_popup_menu_cb (GtkWidget *widget,
|
|
GncPluginPage *page)
|
|
{
|
|
ENTER("widget %p, page %p", widget, page);
|
|
do_popup_menu(page, nullptr);
|
|
LEAVE(" ");
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Callback function invoked when the user clicks in the content of
|
|
* any Gnucash window. If this was a "right-click" then Gnucash will
|
|
* popup the contextual menu.
|
|
*/
|
|
gboolean
|
|
gnc_main_window_button_press_cb (GtkWidget *whatever,
|
|
GdkEventButton *event,
|
|
GncPluginPage *page)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
|
|
|
|
ENTER("widget %p, event %p, page %p", whatever, event, page);
|
|
/* Ignore double-clicks and triple-clicks */
|
|
if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
|
|
{
|
|
do_popup_menu(page, event);
|
|
LEAVE("menu shown");
|
|
return TRUE;
|
|
}
|
|
|
|
LEAVE("other click");
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
gnc_main_window_all_action_set_sensitive (const gchar *action_name,
|
|
gboolean sensitive)
|
|
{
|
|
for (auto tmp = active_windows; tmp; tmp = g_list_next(tmp))
|
|
{
|
|
auto action{gnc_main_window_find_action (static_cast<GncMainWindow*>(tmp->data), action_name)};
|
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
|
|
}
|
|
}
|
|
|
|
GMenuModel *
|
|
gnc_main_window_get_menu_model (GncMainWindow *window)
|
|
{
|
|
GncMainWindowPrivate *priv;
|
|
|
|
g_return_val_if_fail (GNC_IS_MAIN_WINDOW(window), nullptr);
|
|
|
|
priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
|
|
|
|
return priv->menubar_model;
|
|
}
|
|
|
|
gboolean
|
|
gnc_main_window_just_plugin_prefs (GncMainWindow* window)
|
|
{
|
|
return window->just_plugin_prefs;
|
|
}
|
|
|
|
/** @} */
|
|
/** @} */
|