1 /*
2 * gnc-main-window.c -- GtkWindow which represents the
3 * GnuCash main window.
4 *
5 * Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
6 * Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, contact:
20 *
21 * Free Software Foundation Voice: +1-617-542-5942
22 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
23 * Boston, MA 02110-1301, USA gnu@gnu.org
24 */
25
26 /** @addtogroup Windows
27 @{ */
28 /** @addtogroup GncMainWindow Main Window functions.
29 @{ */
30 /** @file gnc-main-window.c
31 @brief Functions for adding content to a window.
32 @author Copyright (C) 2003 Jan Arne Petersen <jpetersen@uni-bonn.de>
33 @author Copyright (C) 2003,2005,2006 David Hampton <hampton@employees.org>
34 */
35 #include <config.h>
36
37 #include <glib/gi18n.h>
38 #include <gtk/gtk.h>
39 #include <gdk/gdk.h>
40 #include <gdk/gdkkeysyms.h>
41
42 #include "gnc-plugin.h"
43 #include "gnc-plugin-manager.h"
44 #include "gnc-main-window.h"
45
46 #include "dialog-options.h"
47 #include "dialog-preferences.h"
48 #include "dialog-reset-warnings.h"
49 #include "dialog-transfer.h"
50 #include "dialog-utils.h"
51 #include "engine-helpers.h"
52 #include "file-utils.h"
53 #include "gnc-component-manager.h"
54 #include "gnc-engine.h"
55 #include "gnc-features.h"
56 #include "gnc-file.h"
57 #include "gnc-filepath-utils.h"
58 #include "gnc-gkeyfile-utils.h"
59 #include "gnc-gnome-utils.h"
60 #include "gnc-gobject-utils.h"
61 #include "gnc-gui-query.h"
62 #include "gnc-hooks.h"
63 #include "gnc-icons.h"
64 #include "gnc-session.h"
65 #include "gnc-state.h"
66 #include "gnc-ui.h"
67 #include "gnc-ui-util.h"
68 #include <gnc-glib-utils.h>
69 #include "gnc-uri-utils.h"
70 #include "gnc-version.h"
71 #include "gnc-warnings.h"
72 #include "gnc-window.h"
73 #include "gnc-prefs.h"
74 #include "option-util.h"
75 // +JSLED
76 //#include "gnc-html.h"
77 #include "gnc-autosave.h"
78 #include "print-session.h"
79 #ifdef MAC_INTEGRATION
80 #include <gtkmacintegration/gtkosxapplication.h>
81 #endif
82 #ifdef HAVE_SYS_STAT_H
83 # define __need_system_sys_stat_h //To block Guile-2.0's evil substitute
84 # include <sys/types.h>
85 # include <sys/stat.h> // for stat(2)
86 #endif
87
88 /** Names of signals generated by the main window. */
89 enum
90 {
91 PAGE_ADDED,
92 PAGE_CHANGED,
93 LAST_SIGNAL
94 };
95
96 /** This label is used to provide a mapping from a visible page widget
97 * back to the corresponding GncPluginPage object. */
98 #define PLUGIN_PAGE_LABEL "plugin-page"
99
100 #define PLUGIN_PAGE_CLOSE_BUTTON "close-button"
101 #define PLUGIN_PAGE_TAB_LABEL "label"
102
103 #define GNC_PREF_SHOW_CLOSE_BUTTON "tab-close-buttons"
104 #define GNC_PREF_TAB_NEXT_RECENT "tab-next-recent"
105 #define GNC_PREF_TAB_POSITION_TOP "tab-position-top"
106 #define GNC_PREF_TAB_POSITION_BOTTOM "tab-position-bottom"
107 #define GNC_PREF_TAB_POSITION_LEFT "tab-position-left"
108 #define GNC_PREF_TAB_POSITION_RIGHT "tab-position-right"
109 #define GNC_PREF_TAB_WIDTH "tab-width"
110 #define GNC_PREF_TAB_COLOR "show-account-color-tabs"
111 #define GNC_PREF_SAVE_CLOSE_EXPIRES "save-on-close-expires"
112 #define GNC_PREF_SAVE_CLOSE_WAIT_TIME "save-on-close-wait-time"
113 #define GNC_PREF_TAB_OPEN_ADJACENT "tab-open-adjacent"
114
115 #define GNC_MAIN_WINDOW_NAME "GncMainWindow"
116
117 #define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
118
119 /* Static Globals *******************************************************/
120
121 /** The debugging module that this .o belongs to. */
122 static QofLogModule log_module = GNC_MOD_GUI;
123 /** A pointer to the parent class of an embedded window. */
124 static GObjectClass *parent_class = NULL;
125 /** An identifier that indicates a "main" window. */
126 static GQuark window_type = 0;
127 /** A list of all extant main windows. This is for convenience as the
128 * same information can be obtained from the object tracking code. */
129 static GList *active_windows = NULL;
130 /** Count down timer for the save changes dialog. If the timer reaches zero
131 * any changes will be saved and the save dialog closed automatically */
132 static guint secs_to_save = 0;
133 #define MSG_AUTO_SAVE _("Changes will be saved automatically in %u seconds")
134
135 /* Declarations *********************************************************/
136 static void gnc_main_window_class_init (GncMainWindowClass *klass);
137 static void gnc_main_window_init (GncMainWindow *window,
138 void *data);
139 static void gnc_main_window_finalize (GObject *object);
140 static void gnc_main_window_destroy (GtkWidget *widget);
141
142 static void gnc_main_window_setup_window (GncMainWindow *window);
143 static void gnc_window_main_window_init (GncWindowIface *iface);
144 #ifndef MAC_INTEGRATION
145 static void gnc_main_window_update_all_menu_items (void);
146 #endif
147
148 /* Callbacks */
149 static void gnc_main_window_add_widget (GtkUIManager *merge, GtkWidget *widget, GncMainWindow *window);
150 static void gnc_main_window_switch_page (GtkNotebook *notebook, gpointer *notebook_page, gint pos, GncMainWindow *window);
151 static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window);
152 static void gnc_main_window_plugin_added (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
153 static void gnc_main_window_plugin_removed (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
154 static void gnc_main_window_engine_commit_error_callback( gpointer data, QofBackendError errcode );
155
156 /* Command callbacks */
157 static void gnc_main_window_cmd_page_setup (GtkAction *action, GncMainWindow *window);
158 static void gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window);
159 static void gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window);
160 static void gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window);
161 static void gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window);
162 static void gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window);
163 static void gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window);
164 static void gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window);
165 static void gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window);
166 static void gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window);
167 static void gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window);
168 static void gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window);
169 static void gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window);
170 static void gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window);
171 static void gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window);
172 static void gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window);
173 #ifndef MAC_INTEGRATION
174 static void gnc_main_window_cmd_window_raise (GtkAction *action, GtkRadioAction *current, GncMainWindow *window);
175 #endif
176 static void gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window);
177 static void gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window);
178 static void gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window);
179
180 static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
181 static GtkWidget *gnc_main_window_get_statusbar (GncWindow *window_in);
182 static void statusbar_notification_lastmodified(void);
183 static void gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data);
184 static void gnc_main_window_remove_prefs (GncMainWindow *window);
185
186 #ifdef MAC_INTEGRATION
187 static void gnc_quartz_shutdown(GtkosxApplication *theApp, gpointer data);
188 static gboolean gnc_quartz_should_quit(GtkosxApplication *theApp, GncMainWindow *window);
189 static void gnc_quartz_set_menu(GncMainWindow* window);
190 #endif
191
192 /** The instance private data structure for an embedded window
193 * object. */
194 typedef struct GncMainWindowPrivate
195 {
196 /** The dock (vbox) at the top of the window containing the
197 * menubar and toolbar. These items are generated by the UI
198 * manager and stored here when the UI manager provides them
199 * to the main window. */
200 GtkWidget *menu_dock;
201 /** The toolbar created by the UI manager. This pointer
202 * provides easy access for showing/hiding the toolbar. */
203 GtkWidget *toolbar;
204 /** The notebook containing all the pages in this window. */
205 GtkWidget *notebook;
206 /** Show account color as background on tabs */
207 gboolean show_color_tabs;
208 /** A pointer to the status bar at the bottom edge of the
209 * window. This pointer provides easy access for
210 * updating/showing/hiding the status bar. */
211 GtkWidget *statusbar;
212 /** A pointer to the progress bar at the bottom right of the
213 * window that is contained in the status bar. This pointer
214 * provides easy access for updating the progressbar. */
215 GtkWidget *progressbar;
216
217 /** The group of all actions provided by the main window
218 * itself. This does not include any action provided by menu
219 * or content plugins. */
220 GtkActionGroup *action_group;
221
222 /** A list of all pages that are installed in this window. */
223 GList *installed_pages;
224 /** A list of pages in order of use (most recent -> least recent) */
225 GList *usage_order;
226 /** The currently selected page. */
227 GncPluginPage *current_page;
228 /** The identifier for this window's engine event handler. */
229 gint event_handler_id;
230 /** Array for window position. */
231 gint pos[2];
232 /** A hash table of all action groups that have been installed
233 * into this window. The keys are the name of an action
234 * group, the values are structures of type
235 * MergedActionEntry. */
236 GHashTable *merged_actions_table;
237 /** Set when restoring plugin pages */
238 gboolean restoring_pages;
239 } GncMainWindowPrivate;
240
241 GNC_DEFINE_TYPE_WITH_CODE(GncMainWindow, gnc_main_window, GTK_TYPE_WINDOW,
242 G_ADD_PRIVATE (GncMainWindow)
243 G_IMPLEMENT_INTERFACE (GNC_TYPE_WINDOW,
244 gnc_window_main_window_init))
245
246 #define GNC_MAIN_WINDOW_GET_PRIVATE(o) \
247 ((GncMainWindowPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_MAIN_WINDOW))
248
249 /** This data structure maintains information about one action groups
250 * that has been installed in this window. */
251 typedef struct
252 {
253 /** The merge identifier for this action group. This number
254 * is provided by the UI manager. */
255 guint merge_id;
256 /** The action group itself. This contains all actions added
257 * by a single menu or content plugin. */
258 GtkActionGroup *action_group;
259 } MergedActionEntry;
260
261 /** A holding place for all the signals generated by the main window
262 * code. */
263 static guint main_window_signals[LAST_SIGNAL] = { 0 };
264
265
266 /** An array of all of the actions provided by the main window code.
267 * This includes some placeholder actions for the menus that are
268 * visible in the menu bar but have no action associated with
269 * them. */
270 static GtkActionEntry gnc_menu_actions [] =
271 {
272 /* Toplevel */
273
274 { "FileAction", NULL, N_("_File"), NULL, NULL, NULL, },
275 { "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
276 { "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
277 { "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
278 { "TransactionAction", NULL, N_("Tra_nsaction"), NULL, NULL, NULL },
279 { "ReportsAction", NULL, N_("_Reports"), NULL, NULL, NULL },
280 { "ToolsAction", NULL, N_("_Tools"), NULL, NULL, NULL },
281 { "ExtensionsAction", NULL, N_("E_xtensions"), NULL, NULL, NULL },
282 { "WindowsAction", NULL, N_("_Windows"), NULL, NULL, NULL },
283 { "HelpAction", NULL, N_("_Help"), NULL, NULL, NULL },
284
285 /* File menu */
286
287 { "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL },
288 { "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL },
289 {
290 "FilePrintAction", "document-print", N_("_Print..."), "<primary>p",
291 N_("Print the currently active page"), NULL
292 },
293 #ifndef GTK_STOCK_PAGE_SETUP
294 # define GTK_STOCK_PAGE_SETUP NULL
295 #endif
296 {
297 "FilePageSetupAction", "document-page-setup", N_("Pa_ge Setup..."), "<primary><shift>p",
298 N_("Specify the page size and orientation for printing"),
299 G_CALLBACK (gnc_main_window_cmd_page_setup)
300 },
301 {
302 "FilePropertiesAction", "document-properties", N_("Proper_ties"), "<Alt>Return",
303 N_("Edit the properties of the current file"),
304 G_CALLBACK (gnc_main_window_cmd_file_properties)
305 },
306 {
307 "FileCloseAction", "window-close", N_("_Close"), "<primary>W",
308 N_("Close the currently active page"),
309 G_CALLBACK (gnc_main_window_cmd_file_close)
310 },
311 {
312 "FileQuitAction", "application-exit", N_("_Quit"), "<primary>Q",
313 N_("Quit this application"),
314 G_CALLBACK (gnc_main_window_cmd_file_quit)
315 },
316
317 /* Edit menu */
318
319 {
320 "EditCutAction", "edit-cut", N_("Cu_t"), "<primary>X",
321 N_("Cut the current selection and copy it to clipboard"),
322 G_CALLBACK (gnc_main_window_cmd_edit_cut)
323 },
324 {
325 "EditCopyAction", "edit-copy", N_("_Copy"), "<primary>C",
326 N_("Copy the current selection to clipboard"),
327 G_CALLBACK (gnc_main_window_cmd_edit_copy)
328 },
329 {
330 "EditPasteAction", "edit-paste", N_("_Paste"), "<primary>V",
331 N_("Paste the clipboard content at the cursor position"),
332 G_CALLBACK (gnc_main_window_cmd_edit_paste)
333 },
334 {
335 "EditPreferencesAction", "preferences-system", N_("Pr_eferences"), NULL,
336 N_("Edit the global preferences of GnuCash"),
337 G_CALLBACK (gnc_main_window_cmd_edit_preferences)
338 },
339
340 /* View menu */
341
342 {
343 "ViewSortByAction", NULL, N_("_Sort By..."), NULL,
344 N_("Select sorting criteria for this page view"), NULL
345 },
346 {
347 "ViewFilterByAction", NULL, N_("_Filter By..."), NULL,
348 N_("Select the account types that should be displayed."), NULL
349 },
350 {
351 "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
352 N_("Refresh this window"),
353 G_CALLBACK (gnc_main_window_cmd_view_refresh)
354 },
355
356 /* Actions menu */
357
358 { "ScrubMenuAction", NULL, N_("_Check & Repair"), NULL, NULL, NULL },
359 {
360 "ActionsForgetWarningsAction", NULL, N_("Reset _Warnings..."), NULL,
361 N_("Reset the state of all warning messages so they will be shown again."),
362 G_CALLBACK (gnc_main_window_cmd_actions_reset_warnings)
363 },
364 {
365 "ActionsRenamePageAction", NULL, N_("Re_name Page"), NULL,
366 N_("Rename this page."),
367 G_CALLBACK (gnc_main_window_cmd_actions_rename_page)
368 },
369
370 /* Windows menu */
371
372 {
373 "WindowNewAction", NULL, N_("_New Window"), NULL,
374 N_("Open a new top-level GnuCash window."),
375 G_CALLBACK (gnc_main_window_cmd_window_new)
376 },
377 {
378 "WindowMovePageAction", NULL, N_("New Window with _Page"), NULL,
379 N_("Move the current page to a new top-level GnuCash window."),
380 G_CALLBACK (gnc_main_window_cmd_window_move_page)
381 },
382
383 /* Help menu */
384
385 {
386 "HelpTutorialAction", "help-browser", N_("Tutorial and Concepts _Guide"), "<primary>H",
387 N_("Open the GnuCash Tutorial"),
388 G_CALLBACK (gnc_main_window_cmd_help_tutorial)
389 },
390 {
391 "HelpContentsAction", "help-browser", N_("_Contents"), "F1",
392 N_("Open the GnuCash Help"),
393 G_CALLBACK (gnc_main_window_cmd_help_contents)
394 },
395 {
396 "HelpAboutAction", "help-about", N_("_About"), NULL,
397 N_("About GnuCash"),
398 G_CALLBACK (gnc_main_window_cmd_help_about)
399 },
400 };
401 /** The number of actions provided by the main window. */
402 static guint gnc_menu_n_actions = G_N_ELEMENTS (gnc_menu_actions);
403
404 /** An array of all of the toggle action provided by the main window
405 * code. */
406 static GtkToggleActionEntry toggle_actions [] =
407 {
408 {
409 "ViewToolbarAction", NULL, N_("_Toolbar"), NULL,
410 N_("Show/hide the toolbar on this window"),
411 G_CALLBACK (gnc_main_window_cmd_view_toolbar), TRUE
412 },
413 {
414 "ViewSummaryAction", NULL, N_("Su_mmary Bar"), NULL,
415 N_("Show/hide the summary bar on this window"),
416 G_CALLBACK (gnc_main_window_cmd_view_summary), TRUE
417 },
418 {
419 "ViewStatusbarAction", NULL, N_("Stat_us Bar"), NULL,
420 N_("Show/hide the status bar on this window"),
421 G_CALLBACK (gnc_main_window_cmd_view_statusbar), TRUE
422 },
423 };
424 /** The number of toggle actions provided by the main window. */
425 static guint n_toggle_actions = G_N_ELEMENTS (toggle_actions);
426
427 #ifndef MAC_INTEGRATION
428 /** An array of all of the radio action provided by the main window
429 * code. */
430 static GtkRadioActionEntry radio_entries [] =
431 {
432 { "Window0Action", NULL, N_("Window _1"), NULL, NULL, 0 },
433 { "Window1Action", NULL, N_("Window _2"), NULL, NULL, 1 },
434 { "Window2Action", NULL, N_("Window _3"), NULL, NULL, 2 },
435 { "Window3Action", NULL, N_("Window _4"), NULL, NULL, 3 },
436 { "Window4Action", NULL, N_("Window _5"), NULL, NULL, 4 },
437 { "Window5Action", NULL, N_("Window _6"), NULL, NULL, 5 },
438 { "Window6Action", NULL, N_("Window _7"), NULL, NULL, 6 },
439 { "Window7Action", NULL, N_("Window _8"), NULL, NULL, 7 },
440 { "Window8Action", NULL, N_("Window _9"), NULL, NULL, 8 },
441 { "Window9Action", NULL, N_("Window _0"), NULL, NULL, 9 },
442 };
443
444 /** The number of radio actions provided by the main window. */
445 static guint n_radio_entries = G_N_ELEMENTS (radio_entries);
446 #endif
447
448 /** These are the "important" actions provided by the main window.
449 * Their labels will appear when the toolbar is set to "Icons and
450 * important text" (e.g. GTK_TOOLBAR_BOTH_HORIZ) mode. */
451 static const gchar *gnc_menu_important_actions[] =
452 {
453 "FileCloseAction",
454 NULL,
455 };
456
457
458 /** The following are in the main window so they will always be
459 * present in the menu structure, but they are never sensitive.
460 * These actions should be overridden in child windows where they
461 * have meaning. */
462 static const gchar *always_insensitive_actions[] =
463 {
464 "FilePrintAction",
465 NULL
466 };
467
468
469 /** The following items in the main window should be made insensitive
470 * at startup time. The sensitivity will be changed by some later
471 * event. */
472 static const gchar *initially_insensitive_actions[] =
473 {
474 "FileCloseAction",
475 NULL
476 };
477
478
479 /** The following are in the main window so they will always be
480 * present in the menu structure, but they are always hidden.
481 * These actions should be overridden in child windows where they
482 * have meaning. */
483 static const gchar *always_hidden_actions[] =
484 {
485 "ViewSortByAction",
486 "ViewFilterByAction",
487 NULL
488 };
489
490
491 /** If a page is flagged as immutable, then the following actions
492 * cannot be performed on that page. */
493 static const gchar *immutable_page_actions[] =
494 {
495 "FileCloseAction",
496 NULL
497 };
498
499
500 /** The following actions can only be performed if there are multiple
501 * pages in a window. */
502 static const gchar *multiple_page_actions[] =
503 {
504 "WindowMovePageAction",
505 NULL
506 };
507
508
509 /************************************************************
510 * *
511 ************************************************************/
512 #define WINDOW_COUNT "WindowCount"
513 #define WINDOW_STRING "Window %d"
514 #define WINDOW_GEOMETRY "WindowGeometry"
515 #define WINDOW_POSITION "WindowPosition"
516 #define WINDOW_MAXIMIZED "WindowMaximized"
517 #define TOOLBAR_VISIBLE "ToolbarVisible"
518 #define STATUSBAR_VISIBLE "StatusbarVisible"
519 #define SUMMARYBAR_VISIBLE "SummarybarVisible"
520 #define WINDOW_FIRSTPAGE "FirstPage"
521 #define WINDOW_PAGECOUNT "PageCount"
522 #define WINDOW_PAGEORDER "PageOrder"
523 #define PAGE_TYPE "PageType"
524 #define PAGE_NAME "PageName"
525 #define PAGE_STRING "Page %d"
526
527 typedef struct
528 {
529 GKeyFile *key_file;
530 const gchar *group_name;
531 gint window_num;
532 gint page_num;
533 gint page_offset;
534 } GncMainWindowSaveData;
535
536
537 gboolean
gnc_main_window_is_restoring_pages(GncMainWindow * window)538 gnc_main_window_is_restoring_pages (GncMainWindow *window)
539 {
540 GncMainWindowPrivate *priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
541 return priv->restoring_pages;
542 }
543
544
545 /* Iterator function to walk all pages in all windows, calling the
546 * specified function for each page. */
547 void
gnc_main_window_foreach_page(GncMainWindowPageFunc fn,gpointer user_data)548 gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data)
549 {
550 GncMainWindowPrivate *priv;
551 GncMainWindow *window;
552 GncPluginPage *page;
553 GList *w, *p;
554
555 ENTER(" ");
556 for (w = active_windows; w; w = g_list_next(w))
557 {
558 window = w->data;
559 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
560 for (p = priv->installed_pages; p; p = g_list_next(p))
561 {
562 page = p->data;
563 fn(page, user_data);
564 }
565 }
566 LEAVE(" ");
567 }
568
569
570 /** Restore a single page to a window. This function calls a page
571 * specific function to create the actual page. It then handles all
572 * the common tasks such as insuring the page is installed into a
573 * window, updating the page name, and anything else that might be
574 * common to all pages.
575 *
576 * @param window The GncMainWindow where the new page will be
577 * installed.
578 *
579 * @param data A data structure containing state about the
580 * window/page restoration process. */
581 static void
gnc_main_window_restore_page(GncMainWindow * window,GncMainWindowSaveData * data)582 gnc_main_window_restore_page (GncMainWindow *window,
583 GncMainWindowSaveData *data)
584 {
585 GncMainWindowPrivate *priv;
586 GncPluginPage *page;
587 gchar *page_group, *page_type = NULL, *name = NULL;
588 const gchar *class_type;
589 GError *error = NULL;
590
591 ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
592 window, data, data->key_file, data->window_num, data->page_offset,
593 data->page_num);
594
595 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
596 page_group = g_strdup_printf(PAGE_STRING,
597 data->page_offset + data->page_num);
598 page_type = g_key_file_get_string(data->key_file, page_group,
599 PAGE_TYPE, &error);
600 if (error)
601 {
602 g_warning("error reading group %s key %s: %s",
603 page_group, PAGE_TYPE, error->message);
604 goto cleanup;
605 }
606
607 /* See if the page already exists. */
608 page = g_list_nth_data(priv->installed_pages, data->page_num);
609 if (page)
610 {
611 class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
612 if (strcmp(page_type, class_type) != 0)
613 {
614 g_warning("error: page types don't match: state %s, existing page %s",
615 page_type, class_type);
616 goto cleanup;
617 }
618 }
619 else
620 {
621 /* create and install the page */
622 page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
623 data->key_file, page_group);
624 if (page)
625 {
626 /* Does the page still need to be installed into the window? */
627 if (page->window == NULL)
628 {
629 gnc_plugin_page_set_use_new_window(page, FALSE);
630 gnc_main_window_open_page(window, page);
631 }
632
633 /* Restore the page name */
634 name = g_key_file_get_string(data->key_file, page_group,
635 PAGE_NAME, &error);
636 if (error)
637 {
638 g_warning("error reading group %s key %s: %s",
639 page_group, PAGE_NAME, error->message);
640 /* Fall through and still show the page. */
641 }
642 else
643 {
644 DEBUG("updating page name for %p to %s.", page, name);
645 main_window_update_page_name(page, name);
646 g_free(name);
647 }
648 }
649 }
650
651 LEAVE("ok");
652 cleanup:
653 if (error)
654 g_error_free(error);
655 if (page_type)
656 g_free(page_type);
657 g_free(page_group);
658 }
659
660
661 /** Restore all the pages in a given window. This function restores
662 * all the window specific attributes, then calls a helper function
663 * to restore all the pages that are contained in the window.
664 *
665 * @param window The GncMainWindow whose pages should be restored.
666 *
667 * @param data A data structure containing state about the
668 * window/page restoration process. */
669 static void
gnc_main_window_restore_window(GncMainWindow * window,GncMainWindowSaveData * data)670 gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
671 {
672 GncMainWindowPrivate *priv;
673 GtkAction *action;
674 gint *pos, *geom, *order;
675 gsize length;
676 gboolean max, visible, desired_visibility;
677 gchar *window_group;
678 gint page_start, page_count, i;
679 GError *error = NULL;
680
681 /* Setup */
682 ENTER("window %p, data %p (key file %p, window %d)",
683 window, data, data->key_file, data->window_num);
684 window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);
685
686 /* Deal with the uncommon case that the state file defines a window
687 * but no pages. An example to get in such a situation can be found
688 * here: https://bugs.gnucash.org/show_bug.cgi?id=436479#c3
689 * If this happens on the first window, we will open an account hierarchy
690 * to avoid confusing the user by presenting a completely empty window.
691 * If it happens on a later window, we'll just skip restoring that window.
692 */
693 if (!g_key_file_has_group (data->key_file, window_group) ||
694 !g_key_file_has_key (data->key_file, window_group, WINDOW_PAGECOUNT, &error))
695 {
696 if (window)
697 {
698 gnc_main_window_restore_default_state (window);
699 PINFO ("saved state had an empty first main window\n"
700 "an account hierarchy page was added automatically to avoid confusion");
701 }
702 else
703 PINFO ("saved state had an empty main window, skipping restore");
704
705 goto cleanup;
706 }
707
708
709 /* Get this window's notebook info */
710 page_count = g_key_file_get_integer(data->key_file,
711 window_group, WINDOW_PAGECOUNT, &error);
712 if (error)
713 {
714 g_warning("error reading group %s key %s: %s",
715 window_group, WINDOW_PAGECOUNT, error->message);
716 goto cleanup;
717 }
718 if (page_count == 0)
719 {
720 /* Should never happen, but has during alpha testing. Having this
721 * check doesn't hurt anything. */
722 goto cleanup;
723 }
724 page_start = g_key_file_get_integer(data->key_file,
725 window_group, WINDOW_FIRSTPAGE, &error);
726 if (error)
727 {
728 g_warning("error reading group %s key %s: %s",
729 window_group, WINDOW_FIRSTPAGE, error->message);
730 goto cleanup;
731 }
732
733 /* Build a window if we don't already have one */
734 if (window == NULL)
735 {
736 DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
737 DEBUG("active_windows %p.", active_windows);
738 if (active_windows)
739 DEBUG("first window %p.", active_windows->data);
740 window = gnc_main_window_new();
741 }
742
743 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
744
745 /* Get the window coordinates, etc. */
746 geom = g_key_file_get_integer_list(data->key_file, window_group,
747 WINDOW_GEOMETRY, &length, &error);
748 if (error)
749 {
750 g_warning("error reading group %s key %s: %s",
751 window_group, WINDOW_GEOMETRY, error->message);
752 g_error_free(error);
753 error = NULL;
754 }
755 else if (length != 2)
756 {
757 g_warning("invalid number of values for group %s key %s",
758 window_group, WINDOW_GEOMETRY);
759 }
760 else
761 {
762 gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
763 DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
764 }
765 /* keep the geometry for a test whether the windows position
766 is offscreen */
767
768 pos = g_key_file_get_integer_list(data->key_file, window_group,
769 WINDOW_POSITION, &length, &error);
770 if (error)
771 {
772 g_warning("error reading group %s key %s: %s",
773 window_group, WINDOW_POSITION, error->message);
774 g_error_free(error);
775 error = NULL;
776 }
777 else if (length != 2)
778 {
779 g_warning("invalid number of values for group %s key %s",
780 window_group, WINDOW_POSITION);
781 }
782 /* Prevent restoring coordinates if this would move the window off-screen */
783 else if ((pos[0] + (geom ? geom[0] : 0) < 0) ||
784 (pos[0] > gdk_screen_width()) ||
785 (pos[1] + (geom ? geom[1] : 0) < 0) ||
786 (pos[1] > gdk_screen_height()))
787 {
788 DEBUG("position %dx%d, size%dx%d is offscreen; will not move",
789 pos[0], pos[1], geom ? geom[0] : 0, geom ? geom[1] : 0);
790 }
791 else
792 {
793 gtk_window_move(GTK_WINDOW(window), pos[0], pos[1]);
794 priv->pos[0] = pos[0];
795 priv->pos[1] = pos[1];
796 DEBUG("window (%p) position %dx%d", window, pos[0], pos[1]);
797 }
798 if (geom)
799 {
800 g_free(geom);
801 }
802 if (pos)
803 {
804 g_free(pos);
805 }
806
807 max = g_key_file_get_boolean(data->key_file, window_group,
808 WINDOW_MAXIMIZED, &error);
809 if (error)
810 {
811 g_warning("error reading group %s key %s: %s",
812 window_group, WINDOW_MAXIMIZED, error->message);
813 g_error_free(error);
814 error = NULL;
815 }
816 else if (max)
817 {
818 gtk_window_maximize(GTK_WINDOW(window));
819 }
820
821 /* Common view menu items */
822 action = gnc_main_window_find_action(window, "ViewToolbarAction");
823 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
824 desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
825 TOOLBAR_VISIBLE, &error);
826 if (error)
827 {
828 g_warning("error reading group %s key %s: %s",
829 window_group, TOOLBAR_VISIBLE, error->message);
830 g_error_free(error);
831 error = NULL;
832 }
833 else if (visible != desired_visibility)
834 {
835 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
836 }
837
838 action = gnc_main_window_find_action(window, "ViewSummaryAction");
839 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
840 desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
841 SUMMARYBAR_VISIBLE, &error);
842 if (error)
843 {
844 g_warning("error reading group %s key %s: %s",
845 window_group, TOOLBAR_VISIBLE, error->message);
846 g_error_free(error);
847 error = NULL;
848 }
849 else if (visible != desired_visibility)
850 {
851 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
852 }
853
854 action = gnc_main_window_find_action(window, "ViewStatusbarAction");
855 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
856 desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
857 STATUSBAR_VISIBLE, &error);
858 if (error)
859 {
860 g_warning("error reading group %s key %s: %s",
861 window_group, TOOLBAR_VISIBLE, error->message);
862 g_error_free(error);
863 error = NULL;
864 }
865 else if (visible != desired_visibility)
866 {
867 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
868 }
869 priv->restoring_pages = TRUE;
870 /* Now populate the window with pages. */
871 for (i = 0; i < page_count; i++)
872 {
873 data->page_offset = page_start;
874 data->page_num = i;
875 gnc_main_window_restore_page(window, data);
876
877 /* give the page a chance to display */
878 while (gtk_events_pending ())
879 gtk_main_iteration ();
880 }
881 priv->restoring_pages = FALSE;
882 /* Restore page ordering within the notebook. Use +1 notation so the
883 * numbers in the page order match the page sections, at least for
884 * the one window case. */
885 order = g_key_file_get_integer_list(data->key_file, window_group,
886 WINDOW_PAGEORDER, &length, &error);
887 if (error)
888 {
889 g_warning("error reading group %s key %s: %s",
890 window_group, WINDOW_PAGEORDER, error->message);
891 g_error_free(error);
892 error = NULL;
893 }
894 else if (length != page_count)
895 {
896 g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %d",
897 window_group, WINDOW_PAGEORDER, length, page_count);
898 }
899 else
900 {
901 /* Dump any list that might exist */
902 g_list_free(priv->usage_order);
903 priv->usage_order = NULL;
904 /* Now rebuild the list from the key file. */
905 for (i = 0; i < length; i++)
906 {
907 gpointer page = g_list_nth_data(priv->installed_pages, order[i] - 1);
908 if (page)
909 {
910 priv->usage_order = g_list_append(priv->usage_order, page);
911 }
912 }
913 gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
914 order[0] - 1);
915
916 g_signal_emit_by_name (window, "page_changed",
917 g_list_nth_data (priv->usage_order, 0));
918 }
919 if (order)
920 {
921 g_free(order);
922 }
923
924 LEAVE("window %p", window);
925 cleanup:
926 if (error)
927 g_error_free(error);
928 g_free(window_group);
929 if (window)
930 gtk_widget_show (GTK_WIDGET(window));
931 }
932
933 void
gnc_main_window_restore_all_windows(const GKeyFile * keyfile)934 gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
935 {
936 gint i, window_count;
937 GError *error = NULL;
938 GncMainWindowSaveData data;
939 GncMainWindow *window;
940
941 /* We use the same struct for reading and for writing, so we cast
942 away the const. */
943 data.key_file = (GKeyFile *) keyfile;
944 window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
945 WINDOW_COUNT, &error);
946 if (error)
947 {
948 g_warning("error reading group %s key %s: %s",
949 STATE_FILE_TOP, WINDOW_COUNT, error->message);
950 g_error_free(error);
951 LEAVE("can't read count");
952 return;
953 }
954
955 /* Restore all state information on the open windows. Window
956 numbers in state file are 1-based. GList indices are 0-based. */
957 gnc_set_busy_cursor (NULL, TRUE);
958 for (i = 0; i < window_count; i++)
959 {
960 data.window_num = i;
961 window = g_list_nth_data(active_windows, i);
962 gnc_main_window_restore_window(window, &data);
963 }
964 gnc_unset_busy_cursor (NULL);
965
966 statusbar_notification_lastmodified();
967 }
968
969 void
gnc_main_window_restore_default_state(GncMainWindow * window)970 gnc_main_window_restore_default_state(GncMainWindow *window)
971 {
972 GtkAction *action;
973
974 /* The default state should be to have an Account Tree page open
975 * in the window. */
976 DEBUG("no saved state file");
977 if (!window)
978 window = g_list_nth_data(active_windows, 0);
979 gtk_widget_show (GTK_WIDGET(window));
980 action = gnc_main_window_find_action(window, "ViewAccountTreeAction");
981 gtk_action_activate(action);
982 }
983
984 /** Save the state of a single page to a disk. This function handles
985 * all the common tasks such as saving the page type and name, and
986 * anything else that might be common to all pages. It then calls a
987 * page specific function to save the actual page.
988 *
989 * @param page The GncPluginPage whose state should be saved.
990 *
991 * @param data A data structure containing state about the
992 * window/page saving process. */
993 static void
gnc_main_window_save_page(GncPluginPage * page,GncMainWindowSaveData * data)994 gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
995 {
996 gchar *page_group;
997 const gchar *plugin_name, *page_name;
998
999 ENTER("page %p, data %p (key file %p, window %d, page %d)",
1000 page, data, data->key_file, data->window_num, data->page_num);
1001 plugin_name = gnc_plugin_page_get_plugin_name(page);
1002 page_name = gnc_plugin_page_get_page_name(page);
1003 if (!plugin_name || !page_name)
1004 {
1005 LEAVE("not saving invalid page");
1006 return;
1007 }
1008 page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
1009 g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
1010 g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
1011
1012 gnc_plugin_page_save_page(page, data->key_file, page_group);
1013 g_free(page_group);
1014 LEAVE(" ");
1015 }
1016
1017
1018 /** Saves all the pages in a single window to a disk. This function
1019 * saves all the window specific attributes, then calls a helper
1020 * function to save all the pages that are contained in the window.
1021 *
1022 * @param window The GncMainWindow whose pages should be saved.
1023 *
1024 * @param data A data structure containing state about the
1025 * window/page saving process. */
1026 static void
gnc_main_window_save_window(GncMainWindow * window,GncMainWindowSaveData * data)1027 gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
1028 {
1029 GncMainWindowPrivate *priv;
1030 GtkAction *action;
1031 gint i, num_pages, coords[4], *order;
1032 gboolean maximized, minimized, visible;
1033 gchar *window_group;
1034
1035 /* Setup */
1036 ENTER("window %p, data %p (key file %p, window %d)",
1037 window, data, data->key_file, data->window_num);
1038 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1039
1040 /* Check for bogus window structures. */
1041 num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
1042 if (0 == num_pages)
1043 {
1044 LEAVE("empty window %p", window);
1045 return;
1046 }
1047
1048 /* Save this window's notebook info */
1049 window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
1050 g_key_file_set_integer(data->key_file, window_group,
1051 WINDOW_PAGECOUNT, num_pages);
1052 g_key_file_set_integer(data->key_file, window_group,
1053 WINDOW_FIRSTPAGE, data->page_num);
1054
1055 /* Save page ordering within the notebook. Use +1 notation so the
1056 * numbers in the page order match the page sections, at least for
1057 * the one window case. */
1058 order = g_malloc(sizeof(gint) * num_pages);
1059 for (i = 0; i < num_pages; i++)
1060 {
1061 gpointer page = g_list_nth_data(priv->usage_order, i);
1062 order[i] = g_list_index(priv->installed_pages, page) + 1;
1063 }
1064 g_key_file_set_integer_list(data->key_file, window_group,
1065 WINDOW_PAGEORDER, order, num_pages);
1066 g_free(order);
1067
1068 /* Save the window coordinates, etc. */
1069 gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
1070 gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
1071 maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1072 & GDK_WINDOW_STATE_MAXIMIZED) != 0;
1073 minimized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
1074 & GDK_WINDOW_STATE_ICONIFIED) != 0;
1075
1076 if (minimized)
1077 {
1078 gint *pos = priv->pos;
1079 g_key_file_set_integer_list(data->key_file, window_group,
1080 WINDOW_POSITION, &pos[0], 2);
1081 DEBUG("window minimized (%p) position %dx%d", window, pos[0], pos[1]);
1082 }
1083 else
1084 g_key_file_set_integer_list(data->key_file, window_group,
1085 WINDOW_POSITION, &coords[0], 2);
1086 g_key_file_set_integer_list(data->key_file, window_group,
1087 WINDOW_GEOMETRY, &coords[2], 2);
1088 g_key_file_set_boolean(data->key_file, window_group,
1089 WINDOW_MAXIMIZED, maximized);
1090 DEBUG("window (%p) position %dx%d, size %dx%d, %s", window, coords[0], coords[1],
1091 coords[2], coords[3],
1092 maximized ? "maximized" : "not maximized");
1093
1094 /* Common view menu items */
1095 action = gnc_main_window_find_action(window, "ViewToolbarAction");
1096 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1097 g_key_file_set_boolean(data->key_file, window_group,
1098 TOOLBAR_VISIBLE, visible);
1099 action = gnc_main_window_find_action(window, "ViewSummaryAction");
1100 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1101 g_key_file_set_boolean(data->key_file, window_group,
1102 SUMMARYBAR_VISIBLE, visible);
1103 action = gnc_main_window_find_action(window, "ViewStatusbarAction");
1104 visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1105 g_key_file_set_boolean(data->key_file, window_group,
1106 STATUSBAR_VISIBLE, visible);
1107
1108 /* Save individual pages in this window */
1109 g_list_foreach(priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
1110
1111 g_free(window_group);
1112 LEAVE("window %p", window);
1113 }
1114
1115 void
gnc_main_window_save_all_windows(GKeyFile * keyfile)1116 gnc_main_window_save_all_windows(GKeyFile *keyfile)
1117 {
1118 GncMainWindowSaveData data;
1119
1120 /* Set up the iterator data structures */
1121 data.key_file = keyfile;
1122 data.window_num = 1;
1123 data.page_num = 1;
1124
1125 g_key_file_set_integer(data.key_file,
1126 STATE_FILE_TOP, WINDOW_COUNT,
1127 g_list_length(active_windows));
1128 /* Dump all state information on the open windows */
1129 g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
1130 }
1131
1132
1133 gboolean
gnc_main_window_finish_pending(GncMainWindow * window)1134 gnc_main_window_finish_pending (GncMainWindow *window)
1135 {
1136 GncMainWindowPrivate *priv;
1137 GList *item;
1138
1139 g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
1140
1141 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1142 for (item = priv->installed_pages; item; item = g_list_next(item))
1143 {
1144 if (!gnc_plugin_page_finish_pending(item->data))
1145 {
1146 return FALSE;
1147 }
1148 }
1149 return TRUE;
1150 }
1151
1152
1153 gboolean
gnc_main_window_all_finish_pending(void)1154 gnc_main_window_all_finish_pending (void)
1155 {
1156 const GList *windows, *item;
1157
1158 windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
1159 for (item = windows; item; item = g_list_next(item))
1160 {
1161 if (!gnc_main_window_finish_pending(item->data))
1162 {
1163 return FALSE;
1164 }
1165 }
1166 if (gnc_gui_refresh_suspended ())
1167 {
1168 gnc_warning_dialog (NULL, "%s", "An operation is still running, wait for it to complete before quitting.");
1169 return FALSE;
1170 }
1171 return TRUE;
1172 }
1173
1174
1175 /** See if the page already exists. For each open window, look
1176 * through the list of pages installed in that window and see if the
1177 * specified page is there.
1178 *
1179 * @internal
1180 *
1181 * @param page The page to search for.
1182 *
1183 * @return TRUE if the page is present in the window, FALSE otherwise.
1184 */
1185 static gboolean
gnc_main_window_page_exists(GncPluginPage * page)1186 gnc_main_window_page_exists (GncPluginPage *page)
1187 {
1188 GncMainWindow *window;
1189 GncMainWindowPrivate *priv;
1190 GList *walker;
1191
1192 for (walker = active_windows; walker; walker = g_list_next(walker))
1193 {
1194 window = walker->data;
1195 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1196 if (g_list_find(priv->installed_pages, page))
1197 {
1198 return TRUE;
1199 }
1200 }
1201 return FALSE;
1202 }
1203
auto_save_countdown(GtkWidget * dialog)1204 static gboolean auto_save_countdown (GtkWidget *dialog)
1205 {
1206 GtkWidget *label;
1207 gchar *timeoutstr = NULL;
1208
1209 /* Stop count down if user closed the dialog since the last time we were called */
1210 if (!GTK_IS_DIALOG (dialog))
1211 return FALSE; /* remove timer */
1212
1213 /* Stop count down if count down text can't be updated */
1214 label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
1215 if (!GTK_IS_LABEL (label))
1216 return FALSE; /* remove timer */
1217
1218 /* Protect against rolling over to MAXUINT */
1219 if (secs_to_save)
1220 --secs_to_save;
1221 DEBUG ("Counting down: %d seconds", secs_to_save);
1222
1223 timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1224 gtk_label_set_text (GTK_LABEL (label), timeoutstr);
1225 g_free (timeoutstr);
1226
1227 /* Count down reached 0. Save and close dialog */
1228 if (!secs_to_save)
1229 {
1230 gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1231 return FALSE; /* remove timer */
1232 }
1233
1234 /* Run another cycle */
1235 return TRUE;
1236 }
1237
1238
1239 /** This function prompts the user to save the file with a dialog that
1240 * follows the HIG guidelines.
1241 *
1242 * @internal
1243 *
1244 * @returns This function returns TRUE if the user clicked the Cancel
1245 * button. It returns FALSE if the closing of the window should
1246 * continue.
1247 */
1248 static gboolean
gnc_main_window_prompt_for_save(GtkWidget * window)1249 gnc_main_window_prompt_for_save (GtkWidget *window)
1250 {
1251 QofSession *session;
1252 QofBook *book;
1253 GtkWidget *dialog, *msg_area, *label;
1254 gint response;
1255 const gchar *filename, *tmp;
1256 const gchar *title = _("Save changes to file %s before closing?");
1257 /* This should be the same message as in gnc-file.c */
1258 const gchar *message_hours =
1259 _("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
1260 const gchar *message_days =
1261 _("If you don't save, changes from the past %d days and %d hours will be discarded.");
1262 time64 oldest_change;
1263 gint minutes, hours, days;
1264 if (!gnc_current_session_exist())
1265 return FALSE;
1266 session = gnc_get_current_session();
1267 book = qof_session_get_book(session);
1268 if (!qof_book_session_not_saved(book))
1269 return FALSE;
1270 filename = qof_session_get_url(session);
1271 if (!strlen (filename))
1272 filename = _("<unknown>");
1273 if ((tmp = strrchr(filename, '/')) != NULL)
1274 filename = tmp + 1;
1275
1276 /* Remove any pending auto-save timeouts */
1277 gnc_autosave_remove_timer(book);
1278
1279 dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1280 GTK_DIALOG_MODAL,
1281 GTK_MESSAGE_WARNING,
1282 GTK_BUTTONS_NONE,
1283 title,
1284 filename);
1285 oldest_change = qof_book_get_session_dirty_time(book);
1286 minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
1287 hours = minutes / 60;
1288 minutes = minutes % 60;
1289 days = hours / 24;
1290 hours = hours % 24;
1291 if (days > 0)
1292 {
1293 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1294 message_days, days, hours);
1295 }
1296 else if (hours > 0)
1297 {
1298 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1299 message_hours, hours, minutes);
1300 }
1301 else
1302 {
1303 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1304 ngettext("If you don't save, changes from the past %d minute will be discarded.",
1305 "If you don't save, changes from the past %d minutes will be discarded.",
1306 minutes), minutes);
1307 }
1308 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
1309 _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
1310 _("_Cancel"), GTK_RESPONSE_CANCEL,
1311 _("_Save"), GTK_RESPONSE_APPLY,
1312 NULL);
1313 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
1314
1315 /* If requested by the user, add a timeout to the question to save automatically
1316 * if the user doesn't answer after a chosen number of seconds.
1317 */
1318 if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
1319 {
1320 gchar *timeoutstr = NULL;
1321
1322 secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
1323 timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
1324 label = GTK_WIDGET(gtk_label_new (timeoutstr));
1325 g_free (timeoutstr);
1326 gtk_widget_show (label);
1327
1328 msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
1329 gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
1330 g_object_set (G_OBJECT (label), "xalign", 0.0, NULL);
1331
1332 g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
1333 g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
1334 }
1335
1336 response = gtk_dialog_run (GTK_DIALOG (dialog));
1337 gtk_widget_destroy(dialog);
1338
1339 switch (response)
1340 {
1341 case GTK_RESPONSE_APPLY:
1342 gnc_file_save (GTK_WINDOW (window));
1343 return FALSE;
1344
1345 case GTK_RESPONSE_CLOSE:
1346 qof_book_mark_session_saved(book);
1347 return FALSE;
1348
1349 default:
1350 return TRUE;
1351 }
1352 }
1353
1354
1355 static void
gnc_main_window_add_plugin(gpointer plugin,gpointer window)1356 gnc_main_window_add_plugin (gpointer plugin,
1357 gpointer window)
1358 {
1359 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1360 g_return_if_fail (GNC_IS_PLUGIN (plugin));
1361
1362 ENTER(" ");
1363 gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
1364 GNC_MAIN_WINDOW (window),
1365 window_type);
1366 LEAVE(" ");
1367 }
1368
1369 static void
gnc_main_window_remove_plugin(gpointer plugin,gpointer window)1370 gnc_main_window_remove_plugin (gpointer plugin,
1371 gpointer window)
1372 {
1373 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
1374 g_return_if_fail (GNC_IS_PLUGIN (plugin));
1375
1376 ENTER(" ");
1377 gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
1378 GNC_MAIN_WINDOW (window),
1379 window_type);
1380 LEAVE(" ");
1381 }
1382
1383
1384 static gboolean
gnc_main_window_timed_quit(gpointer dummy)1385 gnc_main_window_timed_quit (gpointer dummy)
1386 {
1387 if (gnc_file_save_in_progress())
1388 return TRUE;
1389
1390 gnc_shutdown (0);
1391 return FALSE;
1392 }
1393
1394 static gboolean
gnc_main_window_quit(GncMainWindow * window)1395 gnc_main_window_quit(GncMainWindow *window)
1396 {
1397 QofSession *session;
1398 gboolean needs_save, do_shutdown = TRUE;
1399 if (gnc_current_session_exist())
1400 {
1401 session = gnc_get_current_session();
1402 needs_save =
1403 qof_book_session_not_saved(qof_session_get_book(session)) &&
1404 !gnc_file_save_in_progress();
1405 do_shutdown = !needs_save ||
1406 (needs_save &&
1407 !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
1408 }
1409 if (do_shutdown)
1410 {
1411 GList *w, *next;
1412
1413 /* This is not a typical list iteration. There is a possability
1414 * that the window maybe removed from the active_windows list so
1415 * we have to cache the 'next' pointer before executing any code
1416 * in the loop. */
1417 for (w = active_windows; w; w = next)
1418 {
1419 GncMainWindowPrivate *priv;
1420 GncMainWindow *wind = w->data;
1421
1422 next = g_list_next (w);
1423
1424 wind->window_quitting = TRUE; // set window_quitting on all windows
1425
1426 priv = GNC_MAIN_WINDOW_GET_PRIVATE(wind);
1427
1428 // if there are no pages destroy window
1429 if (priv->installed_pages == NULL)
1430 gtk_widget_destroy (GTK_WIDGET(wind));
1431 }
1432 /* remove the preference callbacks from the main window */
1433 gnc_main_window_remove_prefs (window);
1434 g_timeout_add(250, gnc_main_window_timed_quit, NULL);
1435 return TRUE;
1436 }
1437 return FALSE;
1438 }
1439
1440 static gboolean
gnc_main_window_delete_event(GtkWidget * window,GdkEvent * event,gpointer user_data)1441 gnc_main_window_delete_event (GtkWidget *window,
1442 GdkEvent *event,
1443 gpointer user_data)
1444 {
1445 static gboolean already_dead = FALSE;
1446
1447 if (already_dead)
1448 return TRUE;
1449
1450 if (gnc_list_length_cmp (active_windows, 1) > 0)
1451 {
1452 gint response;
1453 GtkWidget *dialog;
1454 gchar *message = _("This window is closing and will not be restored.");
1455
1456 dialog = gtk_message_dialog_new (GTK_WINDOW (window),
1457 GTK_DIALOG_DESTROY_WITH_PARENT,
1458 GTK_MESSAGE_QUESTION,
1459 GTK_BUTTONS_NONE,
1460 "%s", _("Close Window?"));
1461 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
1462 "%s", message);
1463
1464 gtk_dialog_add_buttons (GTK_DIALOG(dialog),
1465 _("_Cancel"), GTK_RESPONSE_CANCEL,
1466 _("_OK"), GTK_RESPONSE_YES,
1467 (gchar *)NULL);
1468 gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1469 response = gnc_dialog_run (GTK_DIALOG(dialog), GNC_PREF_WARN_CLOSING_WINDOW_QUESTION);
1470 gtk_widget_destroy (dialog);
1471
1472 if (response == GTK_RESPONSE_CANCEL)
1473 return TRUE;
1474 }
1475
1476 if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
1477 {
1478 /* Don't close the window. */
1479 return TRUE;
1480 }
1481
1482 if (gnc_list_length_cmp (active_windows, 1) > 0)
1483 return FALSE;
1484
1485 already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
1486 return TRUE;
1487 }
1488
1489
1490 /** This function handles any event notifications from the engine.
1491 * The only event it currently cares about is the deletion of a book.
1492 * When a book is deleted, it runs through all installed pages
1493 * looking for pages that reference the just (about to be?) deleted
1494 * book. It closes any page it finds so there are no dangling
1495 * references to the book.
1496 *
1497 * @internal
1498 *
1499 * @param entity The guid the item being added, deleted, etc.
1500 *
1501 * @param type The type of the item being added, deleted, etc. This
1502 * function only cares about a type of GNC_ID_BOOK.
1503 *
1504 * @param event_type The type of the event. This function only cares
1505 * about an event type of QOF_EVENT_DESTROY.
1506 *
1507 * @param user_data A pointer to the window data structure.
1508 */
1509 static void
gnc_main_window_event_handler(QofInstance * entity,QofEventId event_type,gpointer user_data,gpointer event_data)1510 gnc_main_window_event_handler (QofInstance *entity, QofEventId event_type,
1511 gpointer user_data, gpointer event_data)
1512 {
1513 GncMainWindow *window;
1514 GncMainWindowPrivate *priv;
1515 GncPluginPage *page;
1516 GList *item, *next;
1517
1518 /* hard failures */
1519 g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
1520
1521 /* soft failures */
1522 if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
1523 return;
1524 if (event_type != QOF_EVENT_DESTROY)
1525 return;
1526
1527 ENTER("entity %p, event %d, window %p, event data %p",
1528 entity, event_type, user_data, event_data);
1529 window = GNC_MAIN_WINDOW(user_data);
1530 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1531
1532 /* This is not a typical list iteration. We're removing while
1533 * we iterate, so we have to cache the 'next' pointer before
1534 * executing any code in the loop. */
1535 for (item = priv->installed_pages; item; item = next)
1536 {
1537 next = g_list_next(item);
1538 page = GNC_PLUGIN_PAGE(item->data);
1539 if (gnc_plugin_page_has_book (page, (QofBook *)entity))
1540 gnc_main_window_close_page (page);
1541 }
1542
1543 if (GTK_IS_WIDGET(window) && window->window_quitting)
1544 gtk_widget_destroy (GTK_WIDGET(window));
1545
1546 LEAVE(" ");
1547 }
1548
1549
1550 /** Generate a title for this window based upon the Gnome Human
1551 * Interface Guidelines, v2.0. This title will be used as both the
1552 * window title and the title of the "Window" menu item associated
1553 * with the window.
1554 *
1555 * As a side-effect, the save action is set sensitive iff the book
1556 * is dirty, and the immutable_page_actions are set sensitive iff the page is
1557 * mutable.
1558 *
1559 * @param window The window whose title should be generated.
1560 *
1561 * @return The title for the window. It is the callers
1562 * responsibility to free this string.
1563 *
1564 * @internal
1565 */
1566 static gchar *
gnc_main_window_generate_title(GncMainWindow * window)1567 gnc_main_window_generate_title (GncMainWindow *window)
1568 {
1569 GncMainWindowPrivate *priv;
1570 GncPluginPage *page;
1571 QofBook *book;
1572 gboolean immutable;
1573 gchar *filename = NULL;
1574 const gchar *uri = NULL;
1575 const gchar *dirty = "";
1576 const gchar *readonly_text = NULL;
1577 gchar *readonly;
1578 gchar *title;
1579
1580 if (gnc_current_session_exist())
1581 {
1582 uri = qof_session_get_url (gnc_get_current_session ());
1583 book = gnc_get_current_book();
1584 if (qof_book_session_not_saved (book))
1585 dirty = "*";
1586 if (qof_book_is_readonly(book))
1587 {
1588 /* Translators: This string is shown in the window title if this
1589 document is, well, read-only. */
1590 readonly_text = _("(read-only)");
1591 }
1592 }
1593 readonly = (readonly_text != NULL)
1594 ? g_strdup_printf(" %s", readonly_text)
1595 : g_strdup("");
1596
1597 if (!uri || g_strcmp0 (uri, "") == 0)
1598 filename = g_strdup(_("Unsaved Book"));
1599 else
1600 {
1601 if (gnc_uri_targets_local_fs (uri))
1602 {
1603 /* The filename is a true file.
1604 The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
1605 gchar *path = gnc_uri_get_path ( uri );
1606 filename = g_path_get_basename ( path );
1607 g_free ( path );
1608 }
1609 else
1610 {
1611 /* The filename is composed of database connection parameters.
1612 For this we will show access_method://username@database[:port] */
1613 filename = gnc_uri_normalize_uri (uri, FALSE);
1614 }
1615 }
1616
1617 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1618 page = priv->current_page;
1619 if (page)
1620 {
1621 /* The Gnome HIG 2.0 recommends the application name not be used. (p16)
1622 but several developers prefer to use it anyway. */
1623 title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
1624 gnc_plugin_page_get_page_name(page));
1625 }
1626 else
1627 {
1628 title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
1629 }
1630 /* Update the menus based upon whether this is an "immutable" page. */
1631 immutable = page &&
1632 g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
1633 gnc_plugin_update_actions(priv->action_group,
1634 immutable_page_actions,
1635 "sensitive", !immutable);
1636 /* Trigger sensitivity updtates of other actions such as Save/Revert */
1637 g_signal_emit_by_name (window, "page_changed", page);
1638 g_free( filename );
1639 g_free(readonly);
1640
1641 return title;
1642 }
1643
1644
1645 /** Update the title bar on the specified window. This routine uses
1646 * the gnc_main_window_generate_title() function to create the title.
1647 * It is called whenever the user switched pages in a window, as the
1648 * title includes the name of the current page.
1649 *
1650 * @param window The window whose title should be updated.
1651 *
1652 * @internal
1653 */
1654 static void
gnc_main_window_update_title(GncMainWindow * window)1655 gnc_main_window_update_title (GncMainWindow *window)
1656 {
1657 gchar *title;
1658
1659 title = gnc_main_window_generate_title(window);
1660 gtk_window_set_title(GTK_WINDOW(window), title);
1661 g_free(title);
1662 }
1663
1664 static void
gnc_main_window_update_all_titles(void)1665 gnc_main_window_update_all_titles (void)
1666 {
1667 g_list_foreach(active_windows,
1668 (GFunc)gnc_main_window_update_title,
1669 NULL);
1670 }
1671
1672 static void
gnc_main_window_book_dirty_cb(QofBook * book,gboolean dirty,gpointer user_data)1673 gnc_main_window_book_dirty_cb (QofBook *book,
1674 gboolean dirty,
1675 gpointer user_data)
1676 {
1677 gnc_main_window_update_all_titles();
1678
1679 /* Auto-save feature */
1680 gnc_autosave_dirty_handler(book, dirty);
1681 }
1682
1683 static void
gnc_main_window_attach_to_book(QofSession * session)1684 gnc_main_window_attach_to_book (QofSession *session)
1685 {
1686 QofBook *book;
1687
1688 g_return_if_fail(session);
1689
1690 book = qof_session_get_book(session);
1691 qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, NULL);
1692 gnc_main_window_update_all_titles();
1693 #ifndef MAC_INTEGRATION
1694 gnc_main_window_update_all_menu_items();
1695 #endif
1696 }
1697
1698 static guint gnc_statusbar_notification_messageid = 0;
1699 //#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
1700 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1701 /* Removes the statusbar notification again that has been pushed to the
1702 * statusbar by generate_statusbar_lastmodified_message. */
statusbar_notification_off(gpointer user_data_unused)1703 static gboolean statusbar_notification_off(gpointer user_data_unused)
1704 {
1705 GncMainWindow *mainwindow = GNC_MAIN_WINDOW (gnc_ui_get_main_window (NULL));
1706 //g_warning("statusbar_notification_off\n");
1707 if (gnc_statusbar_notification_messageid == 0)
1708 return FALSE;
1709
1710 if (mainwindow)
1711 {
1712 GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1713 gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
1714 gnc_statusbar_notification_messageid = 0;
1715 }
1716 else
1717 {
1718 g_warning("oops, no GncMainWindow obtained\n");
1719 }
1720 return FALSE; // should not be called again
1721 }
1722 #endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
1723
1724 /* Creates a statusbar message stating the last modification time of the opened
1725 * data file. */
generate_statusbar_lastmodified_message()1726 static gchar *generate_statusbar_lastmodified_message()
1727 {
1728 gchar *message = NULL;
1729 const gchar *uri = NULL;
1730
1731 if (gnc_current_session_exist())
1732 {
1733 uri = qof_session_get_url (gnc_get_current_session ());
1734 }
1735
1736 if (!(uri && strlen (uri)))
1737 return NULL;
1738 else
1739 {
1740 if (gnc_uri_targets_local_fs (uri))
1741 {
1742 /* The filename is a true file. */
1743 gchar *filepath = gnc_uri_get_path ( uri );
1744 gchar *filename = g_path_get_basename ( filepath );
1745 GFile *file = g_file_new_for_uri (uri);
1746 GFileInfo *info = g_file_query_info (file,
1747 G_FILE_ATTRIBUTE_TIME_MODIFIED,
1748 G_FILE_QUERY_INFO_NONE,
1749 NULL, NULL);
1750
1751 if (info && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
1752 {
1753 guint64 modtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1754
1755 /* Translators: This is the date and time that is shown in
1756 the status bar after opening a file: The date and time of
1757 last modification. The string is a format string using
1758 boost::date_time's format flags, see the boost docs for an
1759 explanation of the modifiers. */
1760 char *time_string = gnc_print_time64 (modtime,
1761 _("Last modified on %a, %b %d, %Y at %I:%M %p"));
1762 //g_warning("got time %ld, str=%s\n", mtime, time_string);
1763 /* Translators: This message appears in the status bar after opening the file. */
1764 message = g_strdup_printf(_("File %s opened. %s"),
1765 filename, time_string);
1766 free(time_string);
1767 }
1768 else
1769 {
1770 g_warning("Unable to read mtime for file %s\n", filepath);
1771 // message is still NULL
1772 }
1773 g_free(filename);
1774 g_free(filepath);
1775 g_object_unref (info);
1776 g_object_unref (file);
1777 }
1778 // If the URI is not a file but a database, we can maybe also show
1779 // something useful, but I have no idea how to obtain this information.
1780 }
1781 return message;
1782 }
1783
1784 static void
statusbar_notification_lastmodified()1785 statusbar_notification_lastmodified()
1786 {
1787 // First look up the first GncMainWindow to set the statusbar there
1788 GList *iter;
1789 GtkWidget *widget = NULL;
1790 for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
1791 iter = g_list_next(iter))
1792 {
1793 widget = iter->data;
1794 }
1795 if (widget && GNC_IS_MAIN_WINDOW(widget))
1796 {
1797 // Ok, we found a mainwindow where we can set a statusbar message
1798 GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
1799 GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
1800
1801 gchar *msg = generate_statusbar_lastmodified_message();
1802 if (msg)
1803 {
1804 gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
1805 }
1806 g_free(msg);
1807
1808 #ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
1809 // Also register a timeout callback to remove that statusbar
1810 // notification again after 10 seconds
1811 g_timeout_add(10 * 1000, statusbar_notification_off, NULL); // maybe not needed anyway?
1812 #endif
1813 }
1814 else
1815 {
1816 g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
1817 }
1818 }
1819
1820
1821 /** This data structure is used to describe the requested state of a
1822 * GtkRadioAction, and us used to pass data among several
1823 * functions. */
1824 struct menu_update
1825 {
1826 /** The name of the GtkRadioAction to be updated. */
1827 gchar *action_name;
1828
1829 /** The new label for this GtkRadioAction. */
1830 gchar *label;
1831
1832 /** Whether or not the GtkRadioAction should be visible. */
1833 gboolean visible;
1834 };
1835
1836 #ifndef MAC_INTEGRATION
1837 /** Update the label on the specified GtkRadioAction in the specified
1838 * window. This action is displayed as a menu item in the "Windows"
1839 * menu. This function will end up being called whenever the front
1840 * page is changed in any window, or whenever a window is added or
1841 * deleted.
1842 *
1843 * @param window The window whose menu item should be updated.
1844 *
1845 * @param data A data structure containing the name of the
1846 * GtkRadioAction, and describing the new state for this action.
1847 *
1848 * @internal
1849 */
1850 static void
gnc_main_window_update_one_menu_action(GncMainWindow * window,struct menu_update * data)1851 gnc_main_window_update_one_menu_action (GncMainWindow *window,
1852 struct menu_update *data)
1853 {
1854 GncMainWindowPrivate *priv;
1855 GtkAction* action;
1856
1857 ENTER("window %p, action %s, label %s, visible %d", window,
1858 data->action_name, data->label, data->visible);
1859 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1860 action = gtk_action_group_get_action(priv->action_group, data->action_name);
1861 if (action)
1862 g_object_set(G_OBJECT(action),
1863 "label", data->label,
1864 "visible", data->visible,
1865 (char *)NULL);
1866 LEAVE(" ");
1867 }
1868
1869 /** Update the window selection GtkRadioAction for a specific window.
1870 * This is fairly simple since the windows are listed in the same
1871 * order that they appear in the active_windows list, so the index
1872 * from the window list is used to generate the name of the action.
1873 * If the code is ever changed to allow more than ten open windows in
1874 * the menu, then the actions in the menu will need to be dynamically
1875 * generated/deleted and it gets harder.
1876 *
1877 * @param window The window whose menu item should be updated.
1878 *
1879 * @internal
1880 */
1881 static void
gnc_main_window_update_radio_button(GncMainWindow * window)1882 gnc_main_window_update_radio_button (GncMainWindow *window)
1883 {
1884 GncMainWindowPrivate *priv;
1885 GtkAction *action, *first_action;
1886 GSList *action_list;
1887 gchar *action_name;
1888 gint index;
1889
1890 ENTER("window %p", window);
1891
1892 /* Show the new entry in all windows. */
1893 index = g_list_index(active_windows, window);
1894 if (index >= n_radio_entries)
1895 {
1896 LEAVE("window %d, only %d actions", index, n_radio_entries);
1897 return;
1898 }
1899
1900 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
1901 action_name = g_strdup_printf("Window%dAction", index);
1902 action = gtk_action_group_get_action(priv->action_group, action_name);
1903
1904 /* Block the signal so as not to affect window ordering (top to
1905 * bottom) on the screen */
1906 action_list = gtk_radio_action_get_group(GTK_RADIO_ACTION(action));
1907 if (action_list)
1908 {
1909 first_action = g_slist_last(action_list)->data;
1910 g_signal_handlers_block_by_func(G_OBJECT(first_action),
1911 G_CALLBACK(gnc_main_window_cmd_window_raise),
1912 window);
1913 DEBUG("blocked signal on %p, set %p active, window %p", first_action,
1914 action, window);
1915 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
1916 g_signal_handlers_unblock_by_func(G_OBJECT(first_action),
1917 G_CALLBACK(gnc_main_window_cmd_window_raise),
1918 window);
1919 }
1920 g_free(action_name);
1921 LEAVE(" ");
1922 }
1923
1924 /** In every window that the user has open, update the "Window" menu
1925 * item that points to the specified window. This keeps the "Window"
1926 * menu items consistent across all open windows. (These items
1927 * cannot be shared because of the way the GtkUIManager code works.)
1928 *
1929 * This function is called whenever the user switches pages in a
1930 * window, or whenever a window is added or deleted.
1931 *
1932 * @param window The window whose menu item should be updated in all
1933 * open windows.
1934 *
1935 * @internal
1936 */
1937 static void
gnc_main_window_update_menu_item(GncMainWindow * window)1938 gnc_main_window_update_menu_item (GncMainWindow *window)
1939 {
1940 struct menu_update data;
1941 gchar **strings, *title, *expanded;
1942 gint index;
1943
1944 ENTER("window %p", window);
1945 index = g_list_index(active_windows, window);
1946 if (index > n_radio_entries)
1947 {
1948 LEAVE("skip window %d (only %d entries)", index, n_radio_entries);
1949 return;
1950 }
1951
1952 /* Figure out the label name. Add the accelerator if possible. */
1953 title = gnc_main_window_generate_title(window);
1954 strings = g_strsplit(title, "_", 0);
1955 g_free(title);
1956 expanded = g_strjoinv("__", strings);
1957 if (index < 10)
1958 {
1959 data.label = g_strdup_printf("_%d %s", (index + 1) % 10, expanded);
1960 g_free(expanded);
1961 }
1962 else
1963 {
1964 data.label = expanded;
1965 }
1966 g_strfreev(strings);
1967
1968 data.visible = TRUE;
1969 data.action_name = g_strdup_printf("Window%dAction", index);
1970 g_list_foreach(active_windows,
1971 (GFunc)gnc_main_window_update_one_menu_action,
1972 &data);
1973 g_free(data.action_name);
1974 g_free(data.label);
1975
1976 LEAVE(" ");
1977 }
1978 #endif /* !MAC_INTEGRATION */
1979
1980 /** Update all menu entries for all window menu items in all windows.
1981 * This function is called whenever a window is added or deleted.
1982 * The worst case scenario is where the user has deleted the first
1983 * window, so every single visible item needs to be updated.
1984 *
1985 * @internal
1986 */
1987
1988 #ifndef MAC_INTEGRATION
1989 static void
gnc_main_window_update_all_menu_items(void)1990 gnc_main_window_update_all_menu_items (void)
1991 {
1992 struct menu_update data;
1993 gchar *label;
1994 gint i;
1995
1996 ENTER("");
1997 /* First update the entries for all existing windows */
1998 g_list_foreach(active_windows,
1999 (GFunc)gnc_main_window_update_menu_item,
2000 NULL);
2001 g_list_foreach(active_windows,
2002 (GFunc)gnc_main_window_update_radio_button,
2003 NULL);
2004
2005 /* Now hide any entries that aren't being used. */
2006 data.visible = FALSE;
2007 for (i = g_list_length(active_windows); i < n_radio_entries; i++)
2008 {
2009 data.action_name = g_strdup_printf("Window%dAction", i);
2010 label = g_strdup_printf("Window _%d", (i - 1) % 10);
2011 data.label = gettext(label);
2012
2013 g_list_foreach(active_windows,
2014 (GFunc)gnc_main_window_update_one_menu_action,
2015 &data);
2016
2017 g_free(data.action_name);
2018 g_free(label);
2019 }
2020 LEAVE(" ");
2021 }
2022 #endif /* !MAC_INTEGRATION */
2023
2024 /** Show/hide the close box on the tab of a notebook page. This
2025 * function first checks to see if the specified page has a close
2026 * box, and if so, sets its visibility to the requested state.
2027 *
2028 * @internal
2029 *
2030 * @param page The GncPluginPage whose notebook tab should be updated.
2031 *
2032 * @param new_value A pointer to the boolean that indicates whether
2033 * or not the close button should be visible.
2034 */
2035 static void
gnc_main_window_update_tab_close_one_page(GncPluginPage * page,gpointer user_data)2036 gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
2037 gpointer user_data)
2038 {
2039 gboolean *new_value = user_data;
2040 GtkWidget * close_button;
2041
2042 ENTER("page %p, visible %d", page, *new_value);
2043 close_button = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON);
2044 if (!close_button)
2045 {
2046 LEAVE("no close button");
2047 return;
2048 }
2049
2050 if (*new_value)
2051 gtk_widget_show (close_button);
2052 else
2053 gtk_widget_hide (close_button);
2054 LEAVE(" ");
2055 }
2056
2057
2058 /** Show/hide the close box on all pages in all windows. This function
2059 * calls gnc_main_window_update_tab_close() for each plugin page in the
2060 * application.
2061 *
2062 * @internal
2063 *
2064 * @param prefs Unused.
2065 *
2066 * @param pref Unused.
2067 *
2068 * @param user_data Unused.
2069 */
2070 static void
gnc_main_window_update_tab_close(gpointer prefs,gchar * pref,gpointer user_data)2071 gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
2072 {
2073 gboolean new_value;
2074
2075 ENTER(" ");
2076 new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
2077 gnc_main_window_foreach_page(
2078 gnc_main_window_update_tab_close_one_page,
2079 &new_value);
2080 LEAVE(" ");
2081 }
2082
2083
2084 /** Show/hide the account color on the tab of a notebook page.
2085 *
2086 * @internal
2087 *
2088 * @param page The GncPluginPage whose notebook tab should be updated.
2089 *
2090 * @param user_data GncMainWindow.
2091 */
2092 static void
gnc_main_window_update_tab_color_one_page(GncPluginPage * page,gpointer user_data)2093 gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
2094 gpointer user_data)
2095 {
2096 const gchar *color_string;
2097
2098 ENTER("page %p", page);
2099 color_string = gnc_plugin_page_get_page_color(page);
2100 main_window_update_page_color (page, color_string);
2101 LEAVE(" ");
2102 }
2103
2104
2105 /** Show/hide the account color on tabs.
2106 *
2107 * @internal
2108 *
2109 * @param prefs Unused.
2110 *
2111 * @param pref Name of the preference that was changed.
2112 *
2113 * @param user_data GncMainWindow.
2114 */
2115 static void
gnc_main_window_update_tab_color(gpointer gsettings,gchar * pref,gpointer user_data)2116 gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
2117 {
2118 GncMainWindowPrivate *priv;
2119 GncMainWindow *window;
2120
2121 ENTER(" ");
2122 g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
2123 window = user_data;
2124 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2125 if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
2126 priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2127 gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
2128 LEAVE(" ");
2129 }
2130
2131
2132 /** Set the tab label ellipsize value. The special check for a zero
2133 * value handles the case where a user hasn't set a tab width and
2134 * the preference default isn't detected.
2135 *
2136 * @internal
2137 *
2138 * @param label GtkLabel for the tab.
2139 *
2140 * @param tab_width Tab width the user has set in preferences.
2141 *
2142 */
2143 static void
gnc_main_window_set_tab_ellipsize(GtkWidget * label,gint tab_width)2144 gnc_main_window_set_tab_ellipsize (GtkWidget *label, gint tab_width)
2145 {
2146 const gchar *lab_text = gtk_label_get_text (GTK_LABEL(label));
2147
2148 if (tab_width != 0)
2149 {
2150 gint text_length = g_utf8_strlen (lab_text, -1);
2151 if (text_length < tab_width)
2152 {
2153 gtk_label_set_width_chars (GTK_LABEL(label), text_length);
2154 gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2155 }
2156 else
2157 {
2158 gtk_label_set_width_chars (GTK_LABEL(label), tab_width);
2159 gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
2160 }
2161 }
2162 else
2163 {
2164 gtk_label_set_width_chars (GTK_LABEL(label), 15);
2165 gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
2166 }
2167 }
2168
2169
2170 /** Update the width of the label in the tab of a notebook page. This
2171 * function adjusts both the width and the ellipsize mode so that the
2172 * tab label looks correct.
2173 *
2174 * @internal
2175 *
2176 * @param page The GncPluginPage whose notebook tab should be updated.
2177 *
2178 * @param new_value The new width of the label in the tab.
2179 */
2180 static void
gnc_main_window_update_tab_width_one_page(GncPluginPage * page,gpointer user_data)2181 gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
2182 gpointer user_data)
2183 {
2184 gint *new_value = user_data;
2185 GtkWidget *label;
2186
2187 ENTER("page %p, visible %d", page, *new_value);
2188 label = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL);
2189 if (!label)
2190 {
2191 LEAVE("no label");
2192 return;
2193 }
2194 gnc_main_window_set_tab_ellipsize (label, *new_value);
2195 LEAVE(" ");
2196 }
2197
2198
2199 /** Update the tab label width in all pages in all windows. This function
2200 * calls gnc_main_window_update_tab_width() for each plugin page in the
2201 * application.
2202 *
2203 * @internal
2204 *
2205 * @param prefs Unused.
2206 *
2207 * @param pref Unused.
2208 *
2209 * @param user_data Unused.
2210 */
2211 static void
gnc_main_window_update_tab_width(gpointer prefs,gchar * pref,gpointer user_data)2212 gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
2213 {
2214 gint new_value;
2215
2216 ENTER(" ");
2217 new_value = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2218 gnc_main_window_foreach_page(
2219 gnc_main_window_update_tab_width_one_page,
2220 &new_value);
2221 LEAVE(" ");
2222 }
2223
2224
2225 /************************************************************
2226 * Tab Label Implementation *
2227 ************************************************************/
2228 static gboolean
main_window_find_tab_items(GncMainWindow * window,GncPluginPage * page,GtkWidget ** label_p,GtkWidget ** entry_p)2229 main_window_find_tab_items (GncMainWindow *window,
2230 GncPluginPage *page,
2231 GtkWidget **label_p,
2232 GtkWidget **entry_p)
2233 {
2234 GncMainWindowPrivate *priv;
2235 GtkWidget *tab_hbox, *widget, *tab_widget;
2236 GList *children, *tmp;
2237
2238 ENTER("window %p, page %p, label_p %p, entry_p %p",
2239 window, page, label_p, entry_p);
2240 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2241 *label_p = *entry_p = NULL;
2242
2243 if (!page->notebook_page)
2244 {
2245 LEAVE("invalid notebook_page");
2246 return FALSE;
2247 }
2248
2249 tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2250 page->notebook_page);
2251 if (GTK_IS_EVENT_BOX (tab_widget))
2252 tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2253 else if (GTK_IS_BOX (tab_widget))
2254 tab_hbox = tab_widget;
2255 else
2256 {
2257 PWARN ("Unknown widget for tab label %p", tab_widget);
2258 return FALSE;
2259 }
2260
2261 children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
2262 for (tmp = children; tmp; tmp = g_list_next(tmp))
2263 {
2264 widget = tmp->data;
2265 if (GTK_IS_LABEL(widget))
2266 {
2267 *label_p = widget;
2268 }
2269 else if (GTK_IS_ENTRY(widget))
2270 {
2271 *entry_p = widget;
2272 }
2273 }
2274 g_list_free(children);
2275
2276 LEAVE("label %p, entry %p", *label_p, *entry_p);
2277 return (*label_p && *entry_p);
2278 }
2279
2280 static gboolean
main_window_find_tab_widget(GncMainWindow * window,GncPluginPage * page,GtkWidget ** widget_p)2281 main_window_find_tab_widget (GncMainWindow *window,
2282 GncPluginPage *page,
2283 GtkWidget **widget_p)
2284 {
2285 GncMainWindowPrivate *priv;
2286
2287 ENTER("window %p, page %p, widget %p",
2288 window, page, widget_p);
2289 *widget_p = NULL;
2290
2291 if (!page->notebook_page)
2292 {
2293 LEAVE("invalid notebook_page");
2294 return FALSE;
2295 }
2296
2297 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2298 *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
2299 page->notebook_page);
2300
2301 LEAVE("widget %p", *widget_p);
2302 return TRUE;
2303 }
2304
2305 void
main_window_update_page_name(GncPluginPage * page,const gchar * name_in)2306 main_window_update_page_name (GncPluginPage *page,
2307 const gchar *name_in)
2308 {
2309 GncMainWindow *window;
2310 GncMainWindowPrivate *priv;
2311 GtkWidget *label, *entry;
2312 gchar *name, *old_page_name, *old_page_long_name;
2313 gint lab_width;
2314
2315 ENTER(" ");
2316
2317 if ((name_in == NULL) || (*name_in == '\0'))
2318 {
2319 LEAVE("no string");
2320 return;
2321 }
2322 name = g_strstrip(g_strdup(name_in));
2323
2324 /* Optimization, if the name hasn't changed, don't update X. */
2325 if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
2326 {
2327 g_free(name);
2328 LEAVE("empty string or name unchanged");
2329 return;
2330 }
2331
2332 old_page_name = g_strdup( gnc_plugin_page_get_page_name(page));
2333 old_page_long_name = g_strdup( gnc_plugin_page_get_page_long_name(page));
2334
2335 /* Update the plugin */
2336 gnc_plugin_page_set_page_name(page, name);
2337
2338 /* Update the notebook tab */
2339 window = GNC_MAIN_WINDOW(page->window);
2340 if (!window)
2341 {
2342 g_free(old_page_name);
2343 g_free(old_page_long_name);
2344 g_free(name);
2345 LEAVE("no window widget available");
2346 return;
2347 }
2348
2349 if (main_window_find_tab_items(window, page, &label, &entry))
2350 gtk_label_set_text(GTK_LABEL(label), name);
2351
2352 /* Adjust the label width for new text */
2353 lab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
2354 gnc_main_window_update_tab_width_one_page (page, &lab_width);
2355
2356 /* Update Tooltip on notebook Tab */
2357 if (old_page_long_name && old_page_name
2358 && g_strrstr(old_page_long_name, old_page_name) != NULL)
2359 {
2360 gchar *new_page_long_name;
2361 gint string_position;
2362 GtkWidget *tab_widget;
2363
2364 string_position = strlen(old_page_long_name) - strlen(old_page_name);
2365 new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, NULL);
2366
2367 gnc_plugin_page_set_page_long_name(page, new_page_long_name);
2368
2369 if (main_window_find_tab_widget(window, page, &tab_widget))
2370 gtk_widget_set_tooltip_text(tab_widget, new_page_long_name);
2371
2372 g_free(new_page_long_name);
2373 }
2374
2375 /* Update the notebook menu */
2376 if (page->notebook_page)
2377 {
2378 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2379 label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
2380 page->notebook_page);
2381 gtk_label_set_text(GTK_LABEL(label), name);
2382 }
2383
2384 /* Force an update of the window title */
2385 gnc_main_window_update_title(window);
2386 g_free(old_page_long_name);
2387 g_free(old_page_name);
2388 g_free(name);
2389 LEAVE("done");
2390 }
2391
2392
2393 void
main_window_update_page_color(GncPluginPage * page,const gchar * color_in)2394 main_window_update_page_color (GncPluginPage *page,
2395 const gchar *color_in)
2396 {
2397 GncMainWindow *window;
2398 GncMainWindowPrivate *priv;
2399 GtkWidget *tab_widget;
2400 GdkRGBA tab_color;
2401 gchar *color_string = NULL;
2402 gboolean want_color = FALSE;
2403
2404 ENTER(" ");
2405 if (color_in)
2406 color_string = g_strstrip(g_strdup(color_in));
2407
2408 if (color_string && *color_string != '\0')
2409 want_color = TRUE;
2410
2411 /* Update the plugin */
2412 window = GNC_MAIN_WINDOW(page->window);
2413 if (want_color)
2414 gnc_plugin_page_set_page_color(page, color_string);
2415 else
2416 gnc_plugin_page_set_page_color(page, NULL);
2417
2418 /* Update the notebook tab */
2419 main_window_find_tab_widget (window, page, &tab_widget);
2420 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2421
2422 if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
2423 {
2424 GtkCssProvider *provider = gtk_css_provider_new();
2425 GtkStyleContext *stylectxt;
2426 gchar *col_str, *widget_css;
2427
2428 if (!GTK_IS_EVENT_BOX (tab_widget))
2429 {
2430 GtkWidget *event_box = gtk_event_box_new ();
2431 g_object_ref (tab_widget);
2432 gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2433 page->notebook_page, event_box);
2434 gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
2435 g_object_unref (tab_widget);
2436 tab_widget = event_box;
2437 }
2438
2439 stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
2440 col_str = gdk_rgba_to_string (&tab_color);
2441 widget_css = g_strconcat ("*{\n background-color:", col_str, ";\n}\n", NULL);
2442
2443 gtk_css_provider_load_from_data (provider, widget_css, -1, NULL);
2444 gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
2445 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2446 g_object_unref (provider);
2447 g_free (col_str);
2448 g_free (widget_css);
2449 }
2450 else
2451 {
2452 if (GTK_IS_EVENT_BOX (tab_widget))
2453 {
2454 GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
2455 g_object_ref (tab_hbox);
2456 gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
2457 gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
2458 page->notebook_page, tab_hbox);
2459 g_object_unref (tab_hbox);
2460 }
2461 }
2462 g_free(color_string);
2463 LEAVE("done");
2464 }
2465
2466
2467 void
main_window_update_page_set_read_only_icon(GncPluginPage * page,gboolean read_only)2468 main_window_update_page_set_read_only_icon (GncPluginPage *page,
2469 gboolean read_only)
2470 {
2471 GncMainWindow *window;
2472 GncMainWindowPrivate *priv;
2473 GtkWidget *tab_widget;
2474 GtkWidget *image = NULL;
2475 GList *children;
2476 gchar *image_name = NULL;
2477 const gchar *icon_name;
2478
2479 ENTER(" ");
2480
2481 g_return_if_fail(page && page->window && GNC_IS_MAIN_WINDOW(page->window));
2482 window = GNC_MAIN_WINDOW(page->window);
2483
2484 /* Get the notebook tab widget */
2485 main_window_find_tab_widget (window, page, &tab_widget);
2486 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2487
2488 if (!tab_widget)
2489 {
2490 LEAVE("no tab widget");
2491 return;
2492 }
2493
2494 if (GTK_IS_EVENT_BOX(tab_widget))
2495 tab_widget = gtk_bin_get_child (GTK_BIN(tab_widget));
2496
2497 children = gtk_container_get_children (GTK_CONTAINER(tab_widget));
2498 /* For each, walk the list of container children to get image widget */
2499 for (GList *child = children; child; child = g_list_next (child))
2500 {
2501 GtkWidget *widget = child->data;
2502 if (GTK_IS_IMAGE(widget))
2503 image = widget;
2504 }
2505 g_list_free (children);
2506
2507 if (!image)
2508 {
2509 LEAVE("no image to replace");
2510 return;
2511 }
2512
2513 g_object_get (image, "icon-name", &image_name, NULL);
2514
2515 if (read_only)
2516 icon_name = "changes-prevent-symbolic";
2517 else
2518 icon_name = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
2519
2520 if (g_strcmp0 (icon_name, image_name) == 0)
2521 {
2522 LEAVE("page icon the same, no need to replace");
2523 g_free (image_name);
2524 return;
2525 }
2526 gtk_container_remove (GTK_CONTAINER(tab_widget), image);
2527 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
2528 gtk_widget_show (image);
2529
2530 gtk_container_add (GTK_CONTAINER(tab_widget), image);
2531 gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
2532 gtk_box_reorder_child (GTK_BOX(tab_widget), image, 0);
2533
2534 g_free (image_name);
2535 LEAVE("done");
2536 }
2537
2538
2539 static void
gnc_main_window_tab_entry_activate(GtkWidget * entry,GncPluginPage * page)2540 gnc_main_window_tab_entry_activate (GtkWidget *entry,
2541 GncPluginPage *page)
2542 {
2543 GtkWidget *label, *entry2;
2544
2545 g_return_if_fail(GTK_IS_ENTRY(entry));
2546 g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
2547
2548 ENTER("");
2549 if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2550 page, &label, &entry2))
2551 {
2552 LEAVE("can't find required widgets");
2553 return;
2554 }
2555
2556 main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
2557
2558 gtk_widget_hide(entry);
2559 gtk_widget_show(label);
2560 LEAVE("");
2561 }
2562
2563
2564 static gboolean
gnc_main_window_tab_entry_editing_done(GtkWidget * entry,GncPluginPage * page)2565 gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
2566 GncPluginPage *page)
2567 {
2568 ENTER("");
2569 gnc_main_window_tab_entry_activate(entry, page);
2570 LEAVE("");
2571 return FALSE;
2572 }
2573
2574 static gboolean
gnc_main_window_tab_entry_focus_out_event(GtkWidget * entry,GdkEvent * event,GncPluginPage * page)2575 gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
2576 GdkEvent *event,
2577 GncPluginPage *page)
2578 {
2579 ENTER("");
2580 gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
2581 LEAVE("");
2582 return FALSE;
2583 }
2584
2585 static gboolean
gnc_main_window_tab_entry_key_press_event(GtkWidget * entry,GdkEventKey * event,GncPluginPage * page)2586 gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
2587 GdkEventKey *event,
2588 GncPluginPage *page)
2589 {
2590 if (event->keyval == GDK_KEY_Escape)
2591 {
2592 GtkWidget *label, *entry2;
2593
2594 g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
2595 g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
2596
2597 ENTER("");
2598 if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
2599 page, &label, &entry2))
2600 {
2601 LEAVE("can't find required widgets");
2602 return FALSE;
2603 }
2604
2605 gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
2606 gtk_widget_hide(entry);
2607 gtk_widget_show(label);
2608 LEAVE("");
2609 }
2610 return FALSE;
2611 }
2612
2613 /************************************************************
2614 * Widget Implementation *
2615 ************************************************************/
2616
2617
2618
2619 /** Initialize the class for a new gnucash main window. This will set
2620 * up any function pointers that override functions in the parent
2621 * class, and also initialize the signals that this class of widget
2622 * can generate.
2623 *
2624 * @param klass The new class structure created by the object system.
2625 */
2626 static void
gnc_main_window_class_init(GncMainWindowClass * klass)2627 gnc_main_window_class_init (GncMainWindowClass *klass)
2628 {
2629 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2630 GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
2631
2632 parent_class = g_type_class_peek_parent (klass);
2633
2634 window_type = g_quark_from_static_string ("gnc-main-window");
2635
2636 object_class->finalize = gnc_main_window_finalize;
2637
2638 /* GtkWidget signals */
2639 gtkwidget_class->destroy = gnc_main_window_destroy;
2640
2641 /**
2642 * GncMainWindow::page_added:
2643 * @param window: the #GncMainWindow
2644 * @param page: the #GncPluginPage
2645 *
2646 * The "page_added" signal is emitted when a new page is added
2647 * to the notebook of a GncMainWindow. This can be used to
2648 * attach a signal from the page so that menu actions can be
2649 * adjusted based upon events that occur within the page
2650 * (e.g. an account is selected.)
2651 */
2652 main_window_signals[PAGE_ADDED] =
2653 g_signal_new ("page_added",
2654 G_OBJECT_CLASS_TYPE (object_class),
2655 G_SIGNAL_RUN_FIRST,
2656 G_STRUCT_OFFSET (GncMainWindowClass, page_added),
2657 NULL, NULL,
2658 g_cclosure_marshal_VOID__OBJECT,
2659 G_TYPE_NONE, 1,
2660 G_TYPE_OBJECT);
2661
2662 /**
2663 * GncMainWindow::page_changed:
2664 * @param window: the #GncMainWindow
2665 * @param page: the #GncPluginPage
2666 *
2667 * The "page_changed" signal is emitted when a new page is
2668 * selected in the notebook of a GncMainWindow. This can be
2669 * used to adjust menu actions based upon which page is
2670 * currently displayed in a window.
2671 */
2672 main_window_signals[PAGE_CHANGED] =
2673 g_signal_new ("page_changed",
2674 G_OBJECT_CLASS_TYPE (object_class),
2675 G_SIGNAL_RUN_FIRST,
2676 G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
2677 NULL, NULL,
2678 g_cclosure_marshal_VOID__OBJECT,
2679 G_TYPE_NONE, 1,
2680 G_TYPE_OBJECT);
2681
2682 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2683 GNC_PREF_SHOW_CLOSE_BUTTON,
2684 gnc_main_window_update_tab_close,
2685 NULL);
2686 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2687 GNC_PREF_TAB_WIDTH,
2688 gnc_main_window_update_tab_width,
2689 NULL);
2690
2691 gnc_hook_add_dangler(HOOK_BOOK_SAVED,
2692 (GFunc)gnc_main_window_update_all_titles, NULL, NULL);
2693 gnc_hook_add_dangler(HOOK_BOOK_OPENED,
2694 (GFunc)gnc_main_window_attach_to_book, NULL, NULL);
2695
2696 }
2697
2698
2699 /** Initialize a new instance of a gnucash main window. This function
2700 * initializes the object private storage space. It also adds the
2701 * new object to a list (for memory tracking purposes).
2702 *
2703 * @param window The new object instance created by the object system.
2704 *
2705 * @param klass A pointer to the class data structure for this
2706 * object. */
2707 static void
gnc_main_window_init(GncMainWindow * window,void * data)2708 gnc_main_window_init (GncMainWindow *window, void *data)
2709 {
2710 GncMainWindowPrivate *priv;
2711
2712 GncMainWindowClass *klass = (GncMainWindowClass*)data;
2713
2714 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2715 priv->merged_actions_table =
2716 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2717
2718 // Set the name for this dialog so it can be easily manipulated with css
2719 gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-main-window");
2720
2721 priv->event_handler_id =
2722 qof_event_register_handler(gnc_main_window_event_handler, window);
2723
2724 priv->restoring_pages = FALSE;
2725
2726 /* Get the show_color_tabs value preference */
2727 priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
2728
2729 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
2730 GNC_PREF_TAB_COLOR,
2731 gnc_main_window_update_tab_color,
2732 window);
2733
2734 gnc_main_window_setup_window (window);
2735 gnc_gobject_tracking_remember(G_OBJECT(window),
2736 G_OBJECT_CLASS(klass));
2737 }
2738
2739
2740 /** Finalize the GncMainWindow object. This function is called from
2741 * the G_Object level to complete the destruction of the object. It
2742 * should release any memory not previously released by the destroy
2743 * function (i.e. the private data structure), then chain up to the
2744 * parent's destroy function.
2745 *
2746 * @param object The object being destroyed.
2747 *
2748 * @internal
2749 */
2750 static void
gnc_main_window_finalize(GObject * object)2751 gnc_main_window_finalize (GObject *object)
2752 {
2753 g_return_if_fail (object != NULL);
2754 g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
2755
2756 if (active_windows == NULL)
2757 {
2758 /* Oops. User killed last window and we didn't catch it. */
2759 g_idle_add((GSourceFunc)gnc_shutdown, 0);
2760 }
2761
2762 gnc_gobject_tracking_forget(object);
2763 G_OBJECT_CLASS (parent_class)->finalize (object);
2764 }
2765
2766
2767 static void
gnc_main_window_remove_prefs(GncMainWindow * window)2768 gnc_main_window_remove_prefs (GncMainWindow *window)
2769 {
2770 // remove the registered preference callbacks setup in this file.
2771 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2772 GNC_PREF_TAB_COLOR,
2773 gnc_main_window_update_tab_color,
2774 window);
2775
2776 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2777 GNC_PREF_SHOW_CLOSE_BUTTON,
2778 gnc_main_window_update_tab_close,
2779 NULL);
2780
2781 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2782 GNC_PREF_TAB_WIDTH,
2783 gnc_main_window_update_tab_width,
2784 NULL);
2785
2786 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2787 GNC_PREF_TAB_POSITION_TOP,
2788 gnc_main_window_update_tab_position,
2789 window);
2790
2791 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2792 GNC_PREF_TAB_POSITION_BOTTOM,
2793 gnc_main_window_update_tab_position,
2794 window);
2795
2796 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2797 GNC_PREF_TAB_POSITION_LEFT,
2798 gnc_main_window_update_tab_position,
2799 window);
2800
2801 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2802 GNC_PREF_TAB_POSITION_RIGHT,
2803 gnc_main_window_update_tab_position,
2804 window);
2805
2806 // remove the registered negative color preference callback.
2807 if (gnc_prefs_get_reg_negative_color_pref_id() > 0 && window->window_quitting)
2808 {
2809 gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL,
2810 gnc_prefs_get_reg_negative_color_pref_id());
2811 gnc_prefs_set_reg_negative_color_pref_id (0);
2812 }
2813
2814 // remove the registered auto_raise_lists preference callback.
2815 if (gnc_prefs_get_reg_auto_raise_lists_id() > 0 && window->window_quitting)
2816 {
2817 gnc_prefs_remove_cb_by_id (GNC_PREFS_GROUP_GENERAL_REGISTER,
2818 gnc_prefs_get_reg_auto_raise_lists_id());
2819 gnc_prefs_set_reg_auto_raise_lists_id (0);
2820 }
2821 }
2822
2823
2824 static void
gnc_main_window_destroy(GtkWidget * widget)2825 gnc_main_window_destroy (GtkWidget *widget)
2826 {
2827 GncMainWindow *window;
2828 GncMainWindowPrivate *priv;
2829 GncPluginManager *manager;
2830 GList *plugins;
2831
2832 g_return_if_fail (widget != NULL);
2833 g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
2834
2835 window = GNC_MAIN_WINDOW (widget);
2836
2837 active_windows = g_list_remove (active_windows, window);
2838
2839 /* Do these things once */
2840 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
2841 if (priv->merged_actions_table)
2842 {
2843
2844 /* Close any pages in this window */
2845 while (priv->current_page)
2846 gnc_main_window_close_page(priv->current_page);
2847
2848 if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
2849 gnc_window_set_progressbar_window(NULL);
2850 #ifndef MAC_INTEGRATION
2851 /* Update the "Windows" menu in all other windows */
2852 gnc_main_window_update_all_menu_items();
2853 #endif
2854 /* remove the preference callbacks from the main window */
2855 gnc_main_window_remove_prefs (window);
2856
2857 qof_event_unregister_handler(priv->event_handler_id);
2858 priv->event_handler_id = 0;
2859
2860 g_hash_table_destroy (priv->merged_actions_table);
2861 priv->merged_actions_table = NULL;
2862
2863 /* GncPluginManager stuff */
2864 manager = gnc_plugin_manager_get ();
2865 plugins = gnc_plugin_manager_get_plugins (manager);
2866 g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
2867 g_list_free (plugins);
2868 }
2869 GTK_WIDGET_CLASS (parent_class)->destroy (widget);
2870 }
2871
2872
2873 static gboolean
gnc_main_window_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2874 gnc_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
2875 {
2876 GncMainWindowPrivate *priv;
2877 GdkModifierType modifiers;
2878
2879 g_return_val_if_fail (GNC_IS_MAIN_WINDOW(widget), FALSE);
2880
2881 priv = GNC_MAIN_WINDOW_GET_PRIVATE(widget);
2882
2883 modifiers = gtk_accelerator_get_default_mod_mask ();
2884
2885 if ((event->state & modifiers) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) // Ctrl+Alt+
2886 {
2887 const gchar *account_key = C_ ("lower case key for short cut to 'Accounts'", "a");
2888 guint account_keyval = gdk_keyval_from_name (account_key);
2889
2890 if ((account_keyval == event->keyval) || (account_keyval == gdk_keyval_to_lower (event->keyval)))
2891 {
2892 gint page = 0;
2893
2894 for (GList *item = priv->installed_pages; item; item = g_list_next (item))
2895 {
2896 const gchar *pname = gnc_plugin_page_get_plugin_name (GNC_PLUGIN_PAGE(item->data));
2897
2898 if (g_strcmp0 (pname, "GncPluginPageAccountTree") == 0)
2899 {
2900 gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook), page);
2901 return TRUE;
2902 }
2903 page++;
2904 }
2905 }
2906 else if ((GDK_KEY_Menu == event->keyval) || (GDK_KEY_space == event->keyval))
2907 {
2908 GList *menu = gtk_menu_get_for_attach_widget (GTK_WIDGET(priv->notebook));
2909
2910 if (menu)
2911 {
2912 gtk_menu_popup_at_widget (GTK_MENU(menu->data),
2913 GTK_WIDGET(priv->notebook),
2914 GDK_GRAVITY_SOUTH,
2915 GDK_GRAVITY_SOUTH,
2916 NULL);
2917 return TRUE;
2918 }
2919 }
2920 }
2921 return FALSE;
2922 }
2923
2924
2925 /* Create a new gnc main window plugin.
2926 */
2927 GncMainWindow *
gnc_main_window_new(void)2928 gnc_main_window_new (void)
2929 {
2930 GncMainWindow *window;
2931 GtkWindow *old_window;
2932
2933 window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL);
2934 gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
2935
2936 old_window = gnc_ui_get_main_window (NULL);
2937 if (old_window)
2938 {
2939 gint width, height;
2940 gtk_window_get_size (old_window, &width, &height);
2941 gtk_window_resize (GTK_WINDOW (window), width, height);
2942 if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
2943 & GDK_WINDOW_STATE_MAXIMIZED) != 0)
2944 {
2945 gtk_window_maximize (GTK_WINDOW (window));
2946 }
2947 }
2948 active_windows = g_list_append (active_windows, window);
2949 gnc_main_window_update_title(window);
2950 window->window_quitting = FALSE;
2951 window->just_plugin_prefs = FALSE;
2952 #ifdef MAC_INTEGRATION
2953 gnc_quartz_set_menu(window);
2954 #else
2955 gnc_main_window_update_all_menu_items();
2956 #endif
2957 gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
2958
2959 // set up a callback for noteboook navigation
2960 g_signal_connect (G_OBJECT(window), "key-press-event",
2961 G_CALLBACK(gnc_main_window_key_press_event),
2962 NULL);
2963
2964 return window;
2965 }
2966
2967 /************************************************************
2968 * Utility Functions *
2969 ************************************************************/
2970
2971 static void
gnc_main_window_engine_commit_error_callback(gpointer data,QofBackendError errcode)2972 gnc_main_window_engine_commit_error_callback( gpointer data,
2973 QofBackendError errcode )
2974 {
2975 GncMainWindow* window = GNC_MAIN_WINDOW(data);
2976 GtkWidget* dialog;
2977 const gchar *reason = _("Unable to save to database.");
2978 if ( errcode == ERR_BACKEND_READONLY )
2979 reason = _("Unable to save to database: Book is marked read-only.");
2980 dialog = gtk_message_dialog_new( GTK_WINDOW(window),
2981 GTK_DIALOG_DESTROY_WITH_PARENT,
2982 GTK_MESSAGE_ERROR,
2983 GTK_BUTTONS_CLOSE,
2984 "%s",
2985 reason );
2986 gtk_dialog_run(GTK_DIALOG (dialog));
2987 gtk_widget_destroy(dialog);
2988
2989 }
2990
2991 /** Connect a GncPluginPage to the window. This function will insert
2992 * the page in to the window's notebook and its list of active pages.
2993 * It will also emit the "inserted" signal on the page, and the
2994 * "add_page" signal on the window.
2995 *
2996 * @param window The window where the new page should be added.
2997 *
2998 * @param page The GncPluginPage that should be added to the window.
2999 * The visible widget for this plugin must have already been created.
3000 *
3001 * @param tab_hbox The widget that should be added into the notebook
3002 * tab for this page. Generally this is a GtkLabel, but could also
3003 * be a GtkHBox containing an icon and a label.
3004 *
3005 * @param menu_label The widget that should be added into the
3006 * notebook popup menu for this page. This should be a GtkLabel.
3007 */
3008 static void
gnc_main_window_connect(GncMainWindow * window,GncPluginPage * page,GtkWidget * tab_hbox,GtkWidget * menu_label)3009 gnc_main_window_connect (GncMainWindow *window,
3010 GncPluginPage *page,
3011 GtkWidget *tab_hbox,
3012 GtkWidget *menu_label)
3013 {
3014 GncMainWindowPrivate *priv;
3015 GtkNotebook *notebook;
3016 gint current_position = -1;
3017
3018 page->window = GTK_WIDGET(window);
3019 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3020 notebook = GTK_NOTEBOOK (priv->notebook);
3021
3022 if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_OPEN_ADJACENT))
3023 current_position = g_list_index (priv->installed_pages, priv->current_page) + 1;
3024
3025 priv->installed_pages = g_list_insert (priv->installed_pages, page, current_position);
3026 priv->usage_order = g_list_prepend (priv->usage_order, page);
3027 gtk_notebook_insert_page_menu (notebook, page->notebook_page,
3028 tab_hbox, menu_label, current_position);
3029 gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
3030 gnc_plugin_page_inserted (page);
3031 gtk_notebook_set_current_page (notebook, current_position);
3032
3033 if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
3034 (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
3035 g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
3036
3037 g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
3038 G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3039 g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
3040 G_CALLBACK(gnc_main_window_button_press_cb), page);
3041 }
3042
3043
3044 /** Disconnect a GncPluginPage page from the window. If this page is
3045 * currently foremost in the window's notebook, its user interface
3046 * actions will be disconnected and the page's summarybar widget (if
3047 * any) will be removed. The page is then removed from the window's
3048 * notebook and its list of active pages.
3049 *
3050 * @param window The window the page should be removed from.
3051 *
3052 * @param page The GncPluginPage that should be removed from the
3053 * window.
3054 *
3055 * @internal
3056 */
3057 static void
gnc_main_window_disconnect(GncMainWindow * window,GncPluginPage * page)3058 gnc_main_window_disconnect (GncMainWindow *window,
3059 GncPluginPage *page)
3060 {
3061 GncMainWindowPrivate *priv;
3062 GtkNotebook *notebook;
3063 GncPluginPage *new_page;
3064 gint page_num;
3065
3066 /* Disconnect the callbacks */
3067 g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3068 G_CALLBACK(gnc_main_window_popup_menu_cb), page);
3069 g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
3070 G_CALLBACK(gnc_main_window_button_press_cb), page);
3071
3072 // Remove the page_changed signal callback
3073 gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
3074
3075 /* Disconnect the page and summarybar from the window */
3076 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3077 if (priv->current_page == page)
3078 {
3079 gnc_plugin_page_unmerge_actions (page, window->ui_merge);
3080 gnc_plugin_page_unselected (page);
3081 priv->current_page = NULL;
3082 }
3083
3084 /* Remove it from the list of pages in the window */
3085 priv->installed_pages = g_list_remove (priv->installed_pages, page);
3086 priv->usage_order = g_list_remove (priv->usage_order, page);
3087
3088 /* Switch to the last recently used page */
3089 notebook = GTK_NOTEBOOK (priv->notebook);
3090 if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
3091 {
3092 new_page = g_list_nth_data (priv->usage_order, 0);
3093 if (new_page)
3094 {
3095 page_num = gtk_notebook_page_num(notebook, new_page->notebook_page);
3096 gtk_notebook_set_current_page(notebook, page_num);
3097 /* This may have caused WebKit to schedule a timer interrupt which it
3098 sometimes forgets to cancel before deleting the object. See
3099 <https://bugs.webkit.org/show_bug.cgi?id=119003>. Get around this
3100 by flushing all events to get rid of the timer interrupt. */
3101 while (gtk_events_pending())
3102 gtk_main_iteration();
3103 }
3104 }
3105
3106 /* Remove the page from the notebook */
3107 page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3108 gtk_notebook_remove_page (notebook, page_num);
3109
3110 if ( gtk_notebook_get_current_page(notebook) == -1)
3111 {
3112 /* Need to synthesize a page changed signal when the last
3113 * page is removed. The notebook doesn't generate a signal
3114 * for this, therefore the switch_page code in this file
3115 * never gets called to generate this signal. */
3116 gnc_main_window_switch_page(notebook, NULL, -1, window);
3117 //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, NULL);
3118 }
3119
3120 gnc_plugin_page_removed (page);
3121
3122 gtk_ui_manager_ensure_update (window->ui_merge);
3123 gnc_window_set_status (GNC_WINDOW(window), page, NULL);
3124 }
3125
3126
3127 /************************************************************
3128 * *
3129 ************************************************************/
3130
3131
3132 void
gnc_main_window_display_page(GncPluginPage * page)3133 gnc_main_window_display_page (GncPluginPage *page)
3134 {
3135 GncMainWindow *window;
3136 GncMainWindowPrivate *priv;
3137 GtkNotebook *notebook;
3138 gint page_num;
3139
3140 window = GNC_MAIN_WINDOW (page->window);
3141 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3142 notebook = GTK_NOTEBOOK (priv->notebook);
3143 page_num = gtk_notebook_page_num(notebook, page->notebook_page);
3144 gtk_notebook_set_current_page (notebook, page_num);
3145 gtk_window_present(GTK_WINDOW(window));
3146 }
3147
3148
3149 /* Display a data plugin page in a window. If the page already
3150 * exists in any window, then that window will be brought to the
3151 * front and the notebook switch to display the specified page. If
3152 * the page is new then it will be added to the specified window. If
3153 * the window is NULL, the new page will be added to the first
3154 * window.
3155 */
3156 void
gnc_main_window_open_page(GncMainWindow * window,GncPluginPage * page)3157 gnc_main_window_open_page (GncMainWindow *window,
3158 GncPluginPage *page)
3159 {
3160 GncMainWindowPrivate *priv;
3161 GtkWidget *tab_hbox;
3162 GtkWidget *label, *entry;
3163 const gchar *icon, *text, *color_string, *lab_text;
3164 GtkWidget *image;
3165 GList *tmp;
3166 gint width;
3167
3168 ENTER("window %p, page %p", window, page);
3169 if (window)
3170 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3171 g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
3172 g_return_if_fail (gnc_plugin_page_has_books(page));
3173
3174 if (gnc_main_window_page_exists(page))
3175 {
3176 gnc_main_window_display_page(page);
3177 return;
3178 }
3179
3180 /* Does the page want to be in a new window? */
3181 if (gnc_plugin_page_get_use_new_window(page))
3182 {
3183 /* See if there's a blank window. If so, use that. */
3184 for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
3185 {
3186 window = GNC_MAIN_WINDOW(tmp->data);
3187 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3188 if (priv->installed_pages == NULL)
3189 {
3190 break;
3191 }
3192 }
3193 if (tmp == NULL)
3194 window = gnc_main_window_new ();
3195 gtk_widget_show(GTK_WIDGET(window));
3196 }
3197 else if ((window == NULL) && active_windows)
3198 {
3199 window = active_windows->data;
3200 }
3201
3202 page->window = GTK_WIDGET(window);
3203 page->notebook_page = gnc_plugin_page_create_widget (page);
3204 g_object_set_data (G_OBJECT (page->notebook_page),
3205 PLUGIN_PAGE_LABEL, page);
3206
3207 /*
3208 * The page tab.
3209 */
3210 width = gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
3211 icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
3212 lab_text = gnc_plugin_page_get_page_name(page);
3213 label = gtk_label_new (lab_text);
3214 g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
3215
3216 gnc_main_window_set_tab_ellipsize (label, width);
3217
3218 gtk_widget_show (label);
3219
3220 tab_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
3221
3222 if (g_strcmp0 (gnc_plugin_page_get_plugin_name (page), "GncPluginPageAccountTree") == 0)
3223 gtk_widget_set_name (GTK_WIDGET(tab_hbox), "gnc-id-account-page-tab-box");
3224
3225 gtk_box_set_homogeneous (GTK_BOX (tab_hbox), FALSE);
3226 gtk_widget_show (tab_hbox);
3227
3228 if (icon != NULL)
3229 {
3230 image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
3231 gtk_widget_show (image);
3232 gtk_box_pack_start (GTK_BOX (tab_hbox), image, FALSE, FALSE, 0);
3233 gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
3234 gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3235 }
3236 else
3237 gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
3238
3239 text = gnc_plugin_page_get_page_long_name(page);
3240 if (text)
3241 {
3242 gtk_widget_set_tooltip_text(tab_hbox, text);
3243 }
3244
3245 entry = gtk_entry_new();
3246 gtk_widget_hide (entry);
3247 gtk_box_pack_start (GTK_BOX (tab_hbox), entry, TRUE, TRUE, 0);
3248 g_signal_connect(G_OBJECT(entry), "activate",
3249 G_CALLBACK(gnc_main_window_tab_entry_activate), page);
3250 g_signal_connect(G_OBJECT(entry), "focus-out-event",
3251 G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
3252 page);
3253 g_signal_connect(G_OBJECT(entry), "key-press-event",
3254 G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
3255 page);
3256 g_signal_connect(G_OBJECT(entry), "editing-done",
3257 G_CALLBACK(gnc_main_window_tab_entry_editing_done),
3258 page);
3259
3260 /* Add close button - Not for immutable pages */
3261 if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
3262 {
3263 GtkWidget *close_image, *close_button;
3264 GtkRequisition requisition;
3265
3266 close_button = gtk_button_new();
3267 gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
3268 close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
3269 gtk_widget_show(close_image);
3270 gtk_widget_get_preferred_size (close_image, &requisition, NULL);
3271 gtk_widget_set_size_request(close_button, requisition.width + 4,
3272 requisition.height + 2);
3273 gtk_container_add(GTK_CONTAINER(close_button), close_image);
3274 if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
3275 gtk_widget_show (close_button);
3276 else
3277 gtk_widget_hide (close_button);
3278
3279 g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
3280 G_CALLBACK(gnc_main_window_close_page), page);
3281
3282 gtk_box_pack_start (GTK_BOX (tab_hbox), close_button, FALSE, FALSE, 0);
3283 gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
3284 g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
3285 }
3286
3287 /*
3288 * The popup menu
3289 */
3290 label = gtk_label_new (gnc_plugin_page_get_page_name(page));
3291
3292 /*
3293 * Now install it all in the window.
3294 */
3295 gnc_main_window_connect(window, page, tab_hbox, label);
3296
3297 color_string = gnc_plugin_page_get_page_color(page);
3298 main_window_update_page_color (page, color_string);
3299 LEAVE("");
3300 }
3301
3302
3303 /* Remove a data plugin page from a window and display the previous
3304 * page. If the page removed was the last page in the window, and
3305 * there is more than one window open, then the entire window will be
3306 * destroyed.
3307 */
3308 void
gnc_main_window_close_page(GncPluginPage * page)3309 gnc_main_window_close_page (GncPluginPage *page)
3310 {
3311 GncMainWindow *window;
3312 GncMainWindowPrivate *priv;
3313
3314 if (!page || !page->notebook_page)
3315 return;
3316
3317 if (!gnc_plugin_page_finish_pending(page))
3318 return;
3319
3320 if (!GNC_IS_MAIN_WINDOW (page->window))
3321 return;
3322
3323 window = GNC_MAIN_WINDOW (page->window);
3324 if (!window)
3325 {
3326 g_warning("Page is not in a window.");
3327 return;
3328 }
3329
3330 gnc_main_window_disconnect(window, page);
3331 gnc_plugin_page_destroy_widget (page);
3332 g_object_unref(page);
3333
3334 /* If this isn't the last window, go ahead and destroy the window. */
3335 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3336 if (priv->installed_pages == NULL)
3337 {
3338 if (window->window_quitting)
3339 {
3340 GncPluginManager *manager = gnc_plugin_manager_get ();
3341 GList *plugins = gnc_plugin_manager_get_plugins (manager);
3342
3343 /* remove only the preference callbacks from the window plugins */
3344 window->just_plugin_prefs = TRUE;
3345 g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
3346 window->just_plugin_prefs = FALSE;
3347 g_list_free (plugins);
3348
3349 /* remove the preference callbacks from the main window */
3350 gnc_main_window_remove_prefs (window);
3351 }
3352 if (window && (gnc_list_length_cmp (active_windows, 1) > 0))
3353 gtk_widget_destroy (GTK_WIDGET(window));
3354 }
3355 }
3356
3357
3358 /* Retrieve a pointer to the page that is currently at the front of
3359 * the specified window. Any plugin that needs to manipulate its
3360 * menus based upon the currently selected menu page should connect
3361 * to the "page_changed" signal on a window. The callback function
3362 * from that signal can then call this function to obtain a pointer
3363 * to the current page.
3364 */
3365 GncPluginPage *
gnc_main_window_get_current_page(GncMainWindow * window)3366 gnc_main_window_get_current_page (GncMainWindow *window)
3367 {
3368 GncMainWindowPrivate *priv;
3369
3370 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3371 return priv->current_page;
3372 }
3373
3374
3375 /* Manually add a set of actions to the specified window. Plugins
3376 * whose user interface is not hard coded (e.g. the menu-additions
3377 * plugin) must create their actions at run time, then use this
3378 * function to install them into the window.
3379 */
3380 void
gnc_main_window_manual_merge_actions(GncMainWindow * window,const gchar * group_name,GtkActionGroup * group,guint merge_id)3381 gnc_main_window_manual_merge_actions (GncMainWindow *window,
3382 const gchar *group_name,
3383 GtkActionGroup *group,
3384 guint merge_id)
3385 {
3386 GncMainWindowPrivate *priv;
3387 MergedActionEntry *entry;
3388
3389 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3390 g_return_if_fail (group_name != NULL);
3391 g_return_if_fail (GTK_IS_ACTION_GROUP(group));
3392 g_return_if_fail (merge_id > 0);
3393
3394 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3395 entry = g_new0 (MergedActionEntry, 1);
3396 entry->action_group = group;
3397 entry->merge_id = merge_id;
3398 gtk_ui_manager_ensure_update (window->ui_merge);
3399 g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
3400 }
3401
3402
3403 /* Add a set of actions to the specified window. This function
3404 * should not need to be called directly by plugin implementors.
3405 * Correctly assigning values to the GncPluginClass fields during
3406 * plugin initialization will cause this routine to be automatically
3407 * called.
3408 */
3409 void
gnc_main_window_merge_actions(GncMainWindow * window,const gchar * group_name,GtkActionEntry * actions,guint n_actions,GtkToggleActionEntry * toggle_actions,guint n_toggle_actions,const gchar * filename,gpointer user_data)3410 gnc_main_window_merge_actions (GncMainWindow *window,
3411 const gchar *group_name,
3412 GtkActionEntry *actions,
3413 guint n_actions,
3414 GtkToggleActionEntry *toggle_actions,
3415 guint n_toggle_actions,
3416 const gchar *filename,
3417 gpointer user_data)
3418 {
3419 GncMainWindowPrivate *priv;
3420 GncMainWindowActionData *data;
3421 MergedActionEntry *entry;
3422 GError *error = NULL;
3423 gchar *pathname;
3424
3425 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3426 g_return_if_fail (group_name != NULL);
3427 g_return_if_fail (actions != NULL);
3428 g_return_if_fail (n_actions > 0);
3429 g_return_if_fail (filename != NULL);
3430
3431 pathname = gnc_filepath_locate_ui_file (filename);
3432 if (pathname == NULL)
3433 return;
3434
3435 data = g_new0 (GncMainWindowActionData, 1);
3436 data->window = window;
3437 data->data = user_data;
3438
3439 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3440 entry = g_new0 (MergedActionEntry, 1);
3441 entry->action_group = gtk_action_group_new (group_name);
3442 gtk_action_group_set_translation_domain (entry->action_group, PROJECT_NAME);
3443 gtk_action_group_add_actions (entry->action_group, actions, n_actions, data);
3444 if (toggle_actions != NULL && n_toggle_actions > 0)
3445 {
3446 gtk_action_group_add_toggle_actions (entry->action_group,
3447 toggle_actions, n_toggle_actions,
3448 data);
3449 }
3450 gtk_ui_manager_insert_action_group (window->ui_merge, entry->action_group, 0);
3451 entry->merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge, pathname, &error);
3452 g_assert(entry->merge_id || error);
3453 if (entry->merge_id)
3454 {
3455 gtk_ui_manager_ensure_update (window->ui_merge);
3456 g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
3457 }
3458 else
3459 {
3460 g_critical("Failed to load ui file.\n Filename %s\n Error %s",
3461 filename, error->message);
3462 g_error_free(error);
3463 g_free(entry);
3464 }
3465 g_free(pathname);
3466 }
3467
3468
3469 /* Remove a set of actions from the specified window. This function
3470 * should not need to be called directly by plugin implementors. It
3471 * will automatically be called when a plugin is removed from a
3472 * window.
3473 */
3474 void
gnc_main_window_unmerge_actions(GncMainWindow * window,const gchar * group_name)3475 gnc_main_window_unmerge_actions (GncMainWindow *window,
3476 const gchar *group_name)
3477 {
3478 GncMainWindowPrivate *priv;
3479 MergedActionEntry *entry;
3480
3481 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3482 g_return_if_fail (group_name != NULL);
3483
3484 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3485 if (priv->merged_actions_table == NULL)
3486 return;
3487 entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
3488
3489 if (entry == NULL)
3490 return;
3491
3492 gtk_ui_manager_remove_action_group (window->ui_merge, entry->action_group);
3493 gtk_ui_manager_remove_ui (window->ui_merge, entry->merge_id);
3494 gtk_ui_manager_ensure_update (window->ui_merge);
3495
3496 g_hash_table_remove (priv->merged_actions_table, group_name);
3497 }
3498
3499
3500 /* Force a full update of the user interface for the specified
3501 * window. This can be an expensive function, but is needed because
3502 * the gtk ui manager doesn't always seem to update properly when
3503 * actions are changed.
3504 */
3505 void
gnc_main_window_actions_updated(GncMainWindow * window)3506 gnc_main_window_actions_updated (GncMainWindow *window)
3507 {
3508 GtkActionGroup *force;
3509
3510 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
3511
3512 /* Unfortunately gtk_ui_manager_ensure_update doesn't work
3513 * here. Force a full update by adding and removing an empty
3514 * action group.
3515 */
3516 force = gtk_action_group_new("force_update");
3517 gtk_ui_manager_insert_action_group (window->ui_merge, force, 0);
3518 gtk_ui_manager_ensure_update (window->ui_merge);
3519 gtk_ui_manager_remove_action_group (window->ui_merge, force);
3520 g_object_unref(force);
3521 }
3522
3523
3524 GtkAction *
gnc_main_window_find_action(GncMainWindow * window,const gchar * name)3525 gnc_main_window_find_action (GncMainWindow *window, const gchar *name)
3526 {
3527 GtkAction *action = NULL;
3528 const GList *groups, *tmp;
3529
3530 groups = gtk_ui_manager_get_action_groups(window->ui_merge);
3531 for (tmp = groups; tmp; tmp = g_list_next(tmp))
3532 {
3533 action = gtk_action_group_get_action(GTK_ACTION_GROUP(tmp->data), name);
3534 if (action)
3535 break;
3536 }
3537 return action;
3538 }
3539
3540
3541 /* Retrieve a specific set of user interface actions from a window.
3542 * This function can be used to get an group of action to be
3543 * manipulated when the front page of a window has changed.
3544 */
3545 GtkActionGroup *
gnc_main_window_get_action_group(GncMainWindow * window,const gchar * group_name)3546 gnc_main_window_get_action_group (GncMainWindow *window,
3547 const gchar *group_name)
3548 {
3549 GncMainWindowPrivate *priv;
3550 MergedActionEntry *entry;
3551
3552 g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
3553 g_return_val_if_fail (group_name != NULL, NULL);
3554
3555 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3556 if (priv->merged_actions_table == NULL)
3557 return NULL;
3558 entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
3559
3560 if (entry == NULL)
3561 return NULL;
3562
3563 return entry->action_group;
3564 }
3565
3566 static void
gnc_main_window_update_tab_position(gpointer prefs,gchar * pref,gpointer user_data)3567 gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
3568 {
3569 GncMainWindow *window;
3570 GtkPositionType position = GTK_POS_TOP;
3571 GncMainWindowPrivate *priv;
3572
3573 g_return_if_fail (GNC_IS_MAIN_WINDOW(user_data));
3574
3575 window = GNC_MAIN_WINDOW(user_data);
3576
3577 ENTER ("window %p", window);
3578 if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
3579 position = GTK_POS_BOTTOM;
3580 else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
3581 position = GTK_POS_LEFT;
3582 else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
3583 position = GTK_POS_RIGHT;
3584
3585 priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
3586 gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), position);
3587
3588 LEAVE ("");
3589 }
3590
3591 /*
3592 * Based on code from Epiphany (src/ephy-window.c)
3593 */
3594 static void
gnc_main_window_update_edit_actions_sensitivity(GncMainWindow * window,gboolean hide)3595 gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
3596 {
3597 GncMainWindowPrivate *priv;
3598 GncPluginPage *page;
3599 GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
3600 GtkAction *action;
3601 gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;
3602
3603 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3604 page = priv->current_page;
3605 if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
3606 {
3607 (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
3608 return;
3609 }
3610
3611 if (GTK_IS_EDITABLE (widget))
3612 {
3613 gboolean has_selection;
3614
3615 has_selection = gtk_editable_get_selection_bounds
3616 (GTK_EDITABLE (widget), NULL, NULL);
3617
3618 can_copy = has_selection;
3619 can_cut = has_selection;
3620 can_paste = TRUE;
3621 }
3622 else if (GTK_IS_TEXT_VIEW (widget))
3623 {
3624 gboolean has_selection;
3625 GtkTextBuffer *text_buffer;
3626
3627 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
3628 has_selection = gtk_text_buffer_get_selection_bounds
3629 (text_buffer, NULL, NULL);
3630
3631 can_copy = has_selection;
3632 can_cut = has_selection;
3633 can_paste = TRUE;
3634 }
3635 else
3636 {
3637 #ifdef ORIGINAL_EPIPHANY_CODE
3638 /* For now we assume all actions are possible */
3639 can_copy = can_cut = can_paste = TRUE;
3640 #else
3641 /* If its not a GtkEditable, we don't know what to do
3642 * with it. */
3643 can_copy = can_cut = can_paste = FALSE;
3644 #endif
3645 }
3646
3647 action = gnc_main_window_find_action (window, "EditCopyAction");
3648 gtk_action_set_sensitive (action, can_copy);
3649 gtk_action_set_visible (action, !hide || can_copy);
3650 action = gnc_main_window_find_action (window, "EditCutAction");
3651 gtk_action_set_sensitive (action, can_cut);
3652 gtk_action_set_visible (action, !hide || can_cut);
3653 action = gnc_main_window_find_action (window, "EditPasteAction");
3654 gtk_action_set_sensitive (action, can_paste);
3655 gtk_action_set_visible (action, !hide || can_paste);
3656 }
3657
3658 static void
gnc_main_window_enable_edit_actions_sensitivity(GncMainWindow * window)3659 gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
3660 {
3661 GtkAction *action;
3662
3663 action = gnc_main_window_find_action (window, "EditCopyAction");
3664 gtk_action_set_sensitive (action, TRUE);
3665 gtk_action_set_visible (action, TRUE);
3666 action = gnc_main_window_find_action (window, "EditCutAction");
3667 gtk_action_set_sensitive (action, TRUE);
3668 gtk_action_set_visible (action, TRUE);
3669 action = gnc_main_window_find_action (window, "EditPasteAction");
3670 gtk_action_set_sensitive (action, TRUE);
3671 gtk_action_set_visible (action, TRUE);
3672 }
3673
3674 static void
gnc_main_window_edit_menu_show_cb(GtkWidget * menu,GncMainWindow * window)3675 gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
3676 GncMainWindow *window)
3677 {
3678 gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
3679 }
3680
3681 static void
gnc_main_window_edit_menu_hide_cb(GtkWidget * menu,GncMainWindow * window)3682 gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
3683 GncMainWindow *window)
3684 {
3685 gnc_main_window_enable_edit_actions_sensitivity (window);
3686 }
3687
3688 static void
gnc_main_window_init_menu_updaters(GncMainWindow * window)3689 gnc_main_window_init_menu_updaters (GncMainWindow *window)
3690 {
3691 GtkWidget *edit_menu_item, *edit_menu;
3692
3693 edit_menu_item = gtk_ui_manager_get_widget
3694 (window->ui_merge, "/menubar/Edit");
3695 edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (edit_menu_item));
3696
3697 g_signal_connect (edit_menu, "show",
3698 G_CALLBACK (gnc_main_window_edit_menu_show_cb), window);
3699 g_signal_connect (edit_menu, "hide",
3700 G_CALLBACK (gnc_main_window_edit_menu_hide_cb), window);
3701 }
3702
3703 static void
gnc_main_window_window_menu(GncMainWindow * window)3704 gnc_main_window_window_menu (GncMainWindow *window)
3705 {
3706 guint merge_id;
3707 #ifdef MAC_INTEGRATION
3708 gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui-quartz.xml");
3709 #else
3710 gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui.xml");
3711 GncMainWindowPrivate *priv;
3712 #endif
3713 GError *error = NULL;
3714 g_assert(filename);
3715 merge_id = gtk_ui_manager_add_ui_from_file(window->ui_merge, filename,
3716 &error);
3717 g_free(filename);
3718 g_assert(merge_id);
3719 #ifndef MAC_INTEGRATION
3720 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3721 gtk_action_group_add_radio_actions (priv->action_group,
3722 radio_entries, n_radio_entries,
3723 0,
3724 G_CALLBACK(gnc_main_window_cmd_window_raise),
3725 window);
3726 #endif
3727 };
3728
3729 /* This is used to prevent the tab having focus */
3730 static gboolean
gnc_main_window_page_focus_in(GtkWidget * widget,GdkEvent * event,gpointer user_data)3731 gnc_main_window_page_focus_in (GtkWidget *widget, GdkEvent *event,
3732 gpointer user_data)
3733 {
3734 GncMainWindow *window = user_data;
3735 GncPluginPage *page = gnc_main_window_get_current_page (window);
3736
3737 g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
3738 return FALSE;
3739 }
3740
3741 static void
gnc_main_window_setup_window(GncMainWindow * window)3742 gnc_main_window_setup_window (GncMainWindow *window)
3743 {
3744 GncMainWindowPrivate *priv;
3745 GtkWidget *main_vbox;
3746 guint merge_id;
3747 GncPluginManager *manager;
3748 GList *plugins;
3749 GError *error = NULL;
3750 gchar *filename;
3751
3752 ENTER(" ");
3753
3754 /* Catch window manager delete signal */
3755 g_signal_connect (G_OBJECT (window), "delete-event",
3756 G_CALLBACK (gnc_main_window_delete_event), window);
3757
3758 /* Create widgets and add them to the window */
3759 main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
3760 gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
3761 gtk_widget_show (main_vbox);
3762 gtk_container_add (GTK_CONTAINER (window), main_vbox);
3763
3764 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3765 priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
3766 gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
3767 gtk_widget_show (priv->menu_dock);
3768 gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
3769 FALSE, TRUE, 0);
3770
3771 priv->notebook = gtk_notebook_new ();
3772 g_object_set(G_OBJECT(priv->notebook),
3773 "scrollable", TRUE,
3774 "enable-popup", TRUE,
3775 (char *)NULL);
3776 gtk_widget_show (priv->notebook);
3777 g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
3778 G_CALLBACK (gnc_main_window_switch_page), window);
3779 g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
3780 G_CALLBACK (gnc_main_window_page_reordered), window);
3781 g_signal_connect (G_OBJECT (priv->notebook), "focus-in-event",
3782 G_CALLBACK (gnc_main_window_page_focus_in), window);
3783 gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
3784 TRUE, TRUE, 0);
3785
3786 priv->statusbar = gtk_statusbar_new ();
3787 gtk_widget_show (priv->statusbar);
3788 gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
3789 FALSE, TRUE, 0);
3790
3791 priv->progressbar = gtk_progress_bar_new ();
3792 gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(priv->progressbar), TRUE);
3793 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
3794 gtk_widget_show (priv->progressbar);
3795 gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
3796 FALSE, TRUE, 0);
3797 gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
3798 0.01);
3799
3800 window->ui_merge = gtk_ui_manager_new ();
3801
3802 /* Create menu and toolbar information */
3803 priv->action_group = gtk_action_group_new ("MainWindowActions");
3804 gtk_action_group_set_translation_domain (priv->action_group, PROJECT_NAME);
3805 gtk_action_group_add_actions (priv->action_group, gnc_menu_actions,
3806 gnc_menu_n_actions, window);
3807 gtk_action_group_add_toggle_actions (priv->action_group,
3808 toggle_actions, n_toggle_actions,
3809 window);
3810 gnc_plugin_update_actions(priv->action_group,
3811 initially_insensitive_actions,
3812 "sensitive", FALSE);
3813 gnc_plugin_update_actions(priv->action_group,
3814 always_insensitive_actions,
3815 "sensitive", FALSE);
3816 gnc_plugin_update_actions(priv->action_group,
3817 always_hidden_actions,
3818 "visible", FALSE);
3819 gnc_plugin_set_important_actions (priv->action_group,
3820 gnc_menu_important_actions);
3821 gtk_ui_manager_insert_action_group (window->ui_merge, priv->action_group, 0);
3822
3823 g_signal_connect (G_OBJECT (window->ui_merge), "add_widget",
3824 G_CALLBACK (gnc_main_window_add_widget), window);
3825
3826 /* Use the "connect-proxy" signal for tooltip display in the status bar */
3827 g_signal_connect (G_OBJECT (window->ui_merge), "connect-proxy",
3828 G_CALLBACK (gnc_window_connect_proxy), priv->statusbar);
3829
3830 filename = gnc_filepath_locate_ui_file("gnc-main-window-ui.xml");
3831
3832 /* Can't do much without a ui. */
3833 g_assert (filename);
3834
3835 merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge,
3836 filename, &error);
3837 g_assert(merge_id || error);
3838 if (merge_id)
3839 {
3840 gtk_window_add_accel_group (GTK_WINDOW (window),
3841 gtk_ui_manager_get_accel_group(window->ui_merge));
3842 gtk_ui_manager_ensure_update (window->ui_merge);
3843 }
3844 else
3845 {
3846 g_critical("Failed to load ui file.\n Filename %s\n Error %s",
3847 filename, error->message);
3848 g_error_free(error);
3849 g_assert(merge_id != 0);
3850 }
3851 g_free(filename);
3852 gnc_main_window_window_menu(window);
3853 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3854 GNC_PREF_TAB_POSITION_TOP,
3855 gnc_main_window_update_tab_position,
3856 window);
3857 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3858 GNC_PREF_TAB_POSITION_BOTTOM,
3859 gnc_main_window_update_tab_position,
3860 window);
3861 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3862 GNC_PREF_TAB_POSITION_LEFT,
3863 gnc_main_window_update_tab_position,
3864 window);
3865 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
3866 GNC_PREF_TAB_POSITION_RIGHT,
3867 gnc_main_window_update_tab_position,
3868 window);
3869 gnc_main_window_update_tab_position(NULL, NULL, window);
3870
3871 gnc_main_window_init_menu_updaters(window);
3872
3873 /* Testing */
3874 /* Now update the "eXtensions" menu */
3875 if (!gnc_prefs_is_extra_enabled())
3876 {
3877 GtkAction* action;
3878
3879 action = gtk_action_group_get_action(priv->action_group,
3880 "ExtensionsAction");
3881 gtk_action_set_visible(action, FALSE);
3882 }
3883
3884 /* GncPluginManager stuff */
3885 manager = gnc_plugin_manager_get ();
3886 plugins = gnc_plugin_manager_get_plugins (manager);
3887 g_list_foreach (plugins, gnc_main_window_add_plugin, window);
3888 g_list_free (plugins);
3889
3890 g_signal_connect (G_OBJECT (manager), "plugin-added",
3891 G_CALLBACK (gnc_main_window_plugin_added), window);
3892 g_signal_connect (G_OBJECT (manager), "plugin-removed",
3893 G_CALLBACK (gnc_main_window_plugin_removed), window);
3894
3895 LEAVE(" ");
3896 }
3897
3898 #ifdef MAC_INTEGRATION
3899 /* Event handlers for the shutdown process. Gnc_quartz_shutdown is
3900 * connected to NSApplicationWillTerminate, the last chance to do
3901 * anything before quitting. The problem is that it's launched from a
3902 * CFRunLoop, not a g_main_loop, and if we call anything that would
3903 * affect the main_loop we get an assert that we're in a subidiary
3904 * loop.
3905 */
3906 static void
gnc_quartz_shutdown(GtkosxApplication * theApp,gpointer data)3907 gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
3908 {
3909 /* Do Nothing. It's too late. */
3910 }
3911 /* Should quit responds to NSApplicationBlockTermination; returning TRUE means
3912 * "don't terminate", FALSE means "do terminate". gnc_main_window_quit() queues
3913 * a timer that starts an orderly shutdown in 250ms and if we tell macOS it's OK
3914 * to quit GnuCash gets terminated instead of doing its orderly shutdown,
3915 * leaving the book locked.
3916 */
3917 static gboolean
gnc_quartz_should_quit(GtkosxApplication * theApp,GncMainWindow * window)3918 gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
3919 {
3920 if (gnc_main_window_all_finish_pending())
3921 gnc_main_window_quit (window);
3922 return TRUE;
3923 }
3924
3925 static void
gnc_quartz_set_menu(GncMainWindow * window)3926 gnc_quartz_set_menu(GncMainWindow* window)
3927 {
3928 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
3929 GtkWidget *menu;
3930 GtkWidget *item;
3931
3932 menu = gtk_ui_manager_get_widget (window->ui_merge, "/menubar");
3933 if (GTK_IS_MENU_ITEM (menu))
3934 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
3935 gtk_widget_hide(menu);
3936 gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL (menu));
3937
3938 item = gtk_ui_manager_get_widget (window->ui_merge,
3939 "/menubar/File/FileQuit");
3940 if (GTK_IS_MENU_ITEM (item))
3941 gtk_widget_hide (GTK_WIDGET (item));
3942
3943 item = gtk_ui_manager_get_widget (window->ui_merge,
3944 "/menubar/Help/HelpAbout");
3945 if (GTK_IS_MENU_ITEM (item))
3946 {
3947 gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 0);
3948 }
3949
3950 item = gtk_ui_manager_get_widget (window->ui_merge,
3951 "/menubar/Edit/EditPreferences");
3952 if (GTK_IS_MENU_ITEM (item))
3953 {
3954 gtkosx_application_insert_app_menu_item (theApp,
3955 gtk_separator_menu_item_new (), 1);
3956 gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 2);
3957 }
3958
3959 item = gtk_ui_manager_get_widget (window->ui_merge,
3960 "/menubar/Help");
3961 gtkosx_application_set_help_menu(theApp, GTK_MENU_ITEM(item));
3962 item = gtk_ui_manager_get_widget (window->ui_merge,
3963 "/menubar/Windows");
3964 gtkosx_application_set_window_menu(theApp, GTK_MENU_ITEM(item));
3965 g_signal_connect(theApp, "NSApplicationBlockTermination",
3966 G_CALLBACK(gnc_quartz_should_quit), window);
3967 gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
3968 g_object_unref (theApp);
3969
3970 }
3971 #endif //MAC_INTEGRATION
3972
3973 /* Callbacks */
3974 static void
gnc_main_window_add_widget(GtkUIManager * merge,GtkWidget * widget,GncMainWindow * window)3975 gnc_main_window_add_widget (GtkUIManager *merge,
3976 GtkWidget *widget,
3977 GncMainWindow *window)
3978 {
3979 GncMainWindowPrivate *priv;
3980
3981 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
3982 if (GTK_IS_TOOLBAR (widget))
3983 {
3984 priv->toolbar = widget;
3985 gtk_toolbar_set_style (GTK_TOOLBAR(priv->toolbar),
3986 GTK_TOOLBAR_BOTH);
3987 gtk_toolbar_set_icon_size (GTK_TOOLBAR(priv->toolbar),
3988 GTK_ICON_SIZE_SMALL_TOOLBAR);
3989 }
3990
3991 gtk_box_pack_start (GTK_BOX (priv->menu_dock), widget, FALSE, FALSE, 0);
3992 gtk_widget_show (widget);
3993 }
3994
3995 /** Should a summary bar be visible in this window? In order to
3996 * prevent synchronization issues, the "ViewSummaryBar"
3997 * GtkToggleAction is the sole source of information for whether or
3998 * not any summary bar should be visible in a window.
3999 *
4000 * @param window A pointer to the window in question.
4001 *
4002 * @param action If known, a pointer to the "ViewSummaryBar"
4003 * GtkToggleAction. If NULL, the function will look up this action.
4004 *
4005 * @return TRUE if the summarybar should be visible.
4006 */
4007 static gboolean
gnc_main_window_show_summarybar(GncMainWindow * window,GtkAction * action)4008 gnc_main_window_show_summarybar (GncMainWindow *window, GtkAction *action)
4009 {
4010 GncMainWindowPrivate *priv;
4011
4012 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4013 if (action == NULL)
4014 action = gtk_action_group_get_action(priv->action_group,
4015 "ViewSummaryAction");
4016 if (action == NULL)
4017 return TRUE;
4018 return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
4019 }
4020
4021 /** This function is invoked when the GtkNotebook switches pages. It
4022 * is responsible for updating the rest of the window contents
4023 * outside of the notebook. I.E. Updating the user interface, the
4024 * summary bar, etc. This function also emits the "page_changed"
4025 * signal from the window so that any plugin can also learn about the
4026 * fact that the page has changed.
4027 *
4028 * @internal
4029 */
4030 static void
gnc_main_window_switch_page(GtkNotebook * notebook,gpointer * notebook_page,gint pos,GncMainWindow * window)4031 gnc_main_window_switch_page (GtkNotebook *notebook,
4032 gpointer *notebook_page,
4033 gint pos,
4034 GncMainWindow *window)
4035 {
4036 GncMainWindowPrivate *priv;
4037 GtkWidget *child;
4038 GncPluginPage *page;
4039 gboolean visible;
4040
4041 ENTER("Notebook %p, page, %p, index %d, window %p",
4042 notebook, notebook_page, pos, window);
4043 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4044
4045 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4046 if (priv->current_page != NULL)
4047 {
4048 page = priv->current_page;
4049 gnc_plugin_page_unmerge_actions (page, window->ui_merge);
4050 gnc_plugin_page_unselected (page);
4051 }
4052
4053 child = gtk_notebook_get_nth_page (notebook, pos);
4054 if (child)
4055 {
4056 page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
4057 }
4058 else
4059 {
4060 page = NULL;
4061 }
4062
4063 priv->current_page = page;
4064
4065 if (page != NULL)
4066 {
4067 /* Update the user interface (e.g. menus and toolbars */
4068 gnc_plugin_page_merge_actions (page, window->ui_merge);
4069 visible = gnc_main_window_show_summarybar(window, NULL);
4070 gnc_plugin_page_show_summarybar (page, visible);
4071
4072 /* Allow page specific actions */
4073 gnc_plugin_page_selected (page);
4074 gnc_window_update_status (GNC_WINDOW(window), page);
4075
4076 /* Update the page reference info */
4077 priv->usage_order = g_list_remove (priv->usage_order, page);
4078 priv->usage_order = g_list_prepend (priv->usage_order, page);
4079 }
4080
4081 gnc_plugin_update_actions(priv->action_group,
4082 multiple_page_actions,
4083 "sensitive",
4084 g_list_length(priv->installed_pages) > 1);
4085
4086 gnc_main_window_update_title(window);
4087 #ifndef MAC_INTEGRATION
4088 gnc_main_window_update_menu_item(window);
4089 #endif
4090 g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
4091 LEAVE(" ");
4092 }
4093
4094 /** This function is invoked when a GtkNotebook tab gets reordered by
4095 * drag and drop. It adjusts the list installed_pages to reflect the new
4096 * ordering so that GnuCash saves and restores the tabs correctly.
4097 *
4098 * @internal
4099 */
4100 static void
gnc_main_window_page_reordered(GtkNotebook * notebook,GtkWidget * child,guint pos,GncMainWindow * window)4101 gnc_main_window_page_reordered (GtkNotebook *notebook,
4102 GtkWidget *child,
4103 guint pos,
4104 GncMainWindow *window)
4105 {
4106 GncMainWindowPrivate *priv;
4107 GncPluginPage *page;
4108 GList *old_link;
4109
4110 ENTER("Notebook %p, child %p, index %d, window %p",
4111 notebook, child, pos, window);
4112 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4113
4114 if (!child) return;
4115
4116 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4117
4118 page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
4119 if (!page) return;
4120
4121 old_link = g_list_find (priv->installed_pages, page);
4122 if (!old_link) return;
4123
4124 priv->installed_pages = g_list_delete_link (priv->installed_pages,
4125 old_link);
4126 priv->installed_pages = g_list_insert (priv->installed_pages,
4127 page, pos);
4128
4129 LEAVE(" ");
4130 }
4131
4132 static void
gnc_main_window_plugin_added(GncPlugin * manager,GncPlugin * plugin,GncMainWindow * window)4133 gnc_main_window_plugin_added (GncPlugin *manager,
4134 GncPlugin *plugin,
4135 GncMainWindow *window)
4136 {
4137 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4138 g_return_if_fail (GNC_IS_PLUGIN (plugin));
4139
4140 gnc_plugin_add_to_window (plugin, window, window_type);
4141 }
4142
4143 static void
gnc_main_window_plugin_removed(GncPlugin * manager,GncPlugin * plugin,GncMainWindow * window)4144 gnc_main_window_plugin_removed (GncPlugin *manager,
4145 GncPlugin *plugin,
4146 GncMainWindow *window)
4147 {
4148 g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
4149 g_return_if_fail (GNC_IS_PLUGIN (plugin));
4150
4151 gnc_plugin_remove_from_window (plugin, window, window_type);
4152 }
4153
4154
4155 /* Command callbacks */
4156 static void
gnc_main_window_cmd_page_setup(GtkAction * action,GncMainWindow * window)4157 gnc_main_window_cmd_page_setup (GtkAction *action,
4158 GncMainWindow *window)
4159 {
4160 GtkWindow *gtk_window;
4161
4162 g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4163
4164 gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
4165 gnc_ui_page_setup(gtk_window);
4166 }
4167
4168 gboolean
gnc_book_options_dialog_apply_helper(GNCOptionDB * options)4169 gnc_book_options_dialog_apply_helper(GNCOptionDB * options)
4170 {
4171 QofBook *book = gnc_get_current_book ();
4172 gboolean use_split_action_for_num_before =
4173 qof_book_use_split_action_for_num_field (book);
4174 gboolean use_book_currency_before =
4175 gnc_book_use_book_currency (book);
4176 gint use_read_only_threshold_before =
4177 qof_book_get_num_days_autoreadonly (book);
4178 gboolean use_split_action_for_num_after;
4179 gboolean use_book_currency_after;
4180 gint use_read_only_threshold_after;
4181 gboolean return_val = FALSE;
4182 GList *results = NULL, *iter;
4183
4184 if (!options) return return_val;
4185
4186 results = gnc_option_db_commit (options);
4187 for (iter = results; iter; iter = iter->next)
4188 {
4189 GtkWidget *dialog = gtk_message_dialog_new(gnc_ui_get_main_window (NULL),
4190 0,
4191 GTK_MESSAGE_ERROR,
4192 GTK_BUTTONS_OK,
4193 "%s",
4194 (char*)iter->data);
4195 gtk_dialog_run(GTK_DIALOG(dialog));
4196 gtk_widget_destroy(dialog);
4197 g_free (iter->data);
4198 }
4199 g_list_free (results);
4200 qof_book_begin_edit (book);
4201 qof_book_save_options (book, gnc_option_db_save, options, TRUE);
4202 use_split_action_for_num_after =
4203 qof_book_use_split_action_for_num_field (book);
4204 use_book_currency_after = gnc_book_use_book_currency (book);
4205
4206 // mark cached value as invalid so we get new value
4207 book->cached_num_days_autoreadonly_isvalid = FALSE;
4208 use_read_only_threshold_after = qof_book_get_num_days_autoreadonly (book);
4209
4210 if (use_split_action_for_num_before != use_split_action_for_num_after)
4211 {
4212 gnc_book_option_num_field_source_change_cb (
4213 use_split_action_for_num_after);
4214 return_val = TRUE;
4215 }
4216 if (use_book_currency_before != use_book_currency_after)
4217 {
4218 gnc_book_option_book_currency_selected_cb (use_book_currency_after);
4219 return_val = TRUE;
4220 }
4221 if (use_read_only_threshold_before != use_read_only_threshold_after)
4222 return_val = TRUE;
4223
4224 qof_book_commit_edit (book);
4225 return return_val;
4226 }
4227
4228 static void
gnc_book_options_dialog_apply_cb(GNCOptionWin * optionwin,gpointer user_data)4229 gnc_book_options_dialog_apply_cb(GNCOptionWin * optionwin,
4230 gpointer user_data)
4231 {
4232 GNCOptionDB * options = user_data;
4233
4234 if (!options) return;
4235
4236 if (gnc_book_options_dialog_apply_helper (options))
4237 gnc_gui_refresh_all ();
4238 }
4239
4240 static void
gnc_book_options_dialog_close_cb(GNCOptionWin * optionwin,gpointer user_data)4241 gnc_book_options_dialog_close_cb(GNCOptionWin * optionwin,
4242 gpointer user_data)
4243 {
4244 GNCOptionDB * options = user_data;
4245
4246 gnc_options_dialog_destroy(optionwin);
4247 gnc_option_db_destroy(options);
4248 }
4249
4250 /** Calls gnc_book_option_num_field_source_change to initiate registered
4251 * callbacks when num_field_source book option changes so that
4252 * registers/reports can update themselves; sets feature flag */
4253 void
gnc_book_option_num_field_source_change_cb(gboolean num_action)4254 gnc_book_option_num_field_source_change_cb (gboolean num_action)
4255 {
4256 gnc_suspend_gui_refresh ();
4257 if (num_action)
4258 {
4259 /* Set a feature flag in the book for use of the split action field as number.
4260 * This will prevent older GnuCash versions that don't support this feature
4261 * from opening this file. */
4262 gnc_features_set_used (gnc_get_current_book(),
4263 GNC_FEATURE_NUM_FIELD_SOURCE);
4264 }
4265 gnc_book_option_num_field_source_change (num_action);
4266 gnc_resume_gui_refresh ();
4267 }
4268
4269 /** Calls gnc_book_option_book_currency_selected to initiate registered
4270 * callbacks when currency accounting book option changes to book-currency so
4271 * that registers/reports can update themselves; sets feature flag */
4272 void
gnc_book_option_book_currency_selected_cb(gboolean use_book_currency)4273 gnc_book_option_book_currency_selected_cb (gboolean use_book_currency)
4274 {
4275 gnc_suspend_gui_refresh ();
4276 if (use_book_currency)
4277 {
4278 /* Set a feature flag in the book for use of book currency. This will
4279 * prevent older GnuCash versions that don't support this feature from
4280 * opening this file. */
4281 gnc_features_set_used (gnc_get_current_book(),
4282 GNC_FEATURE_BOOK_CURRENCY);
4283 }
4284 gnc_book_option_book_currency_selected (use_book_currency);
4285 gnc_resume_gui_refresh ();
4286 }
4287
4288 static gboolean
show_handler(const char * class_name,gint component_id,gpointer user_data,gpointer iter_data)4289 show_handler (const char *class_name, gint component_id,
4290 gpointer user_data, gpointer iter_data)
4291 {
4292 GNCOptionWin *optwin = user_data;
4293 GtkWidget *widget;
4294
4295 if (!optwin)
4296 return(FALSE);
4297
4298 widget = gnc_options_dialog_widget(optwin);
4299
4300 gtk_window_present(GTK_WINDOW(widget));
4301 return(TRUE);
4302 }
4303
4304 GtkWidget *
gnc_book_options_dialog_cb(gboolean modal,gchar * title,GtkWindow * parent)4305 gnc_book_options_dialog_cb (gboolean modal, gchar *title, GtkWindow* parent)
4306 {
4307 QofBook *book = gnc_get_current_book ();
4308 GNCOptionDB *options;
4309 GNCOptionWin *optionwin;
4310
4311 options = gnc_option_db_new_for_type (QOF_ID_BOOK);
4312 qof_book_load_options (book, gnc_option_db_load, options);
4313 gnc_option_db_clean (options);
4314
4315 /* Only allow one Book Options dialog if called from file->properties
4316 menu */
4317 if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
4318 show_handler, NULL))
4319 {
4320 return NULL;
4321 }
4322 optionwin = gnc_options_dialog_new_modal (
4323 modal,
4324 (title ? title : _( "Book Options")),
4325 DIALOG_BOOK_OPTIONS_CM_CLASS, parent);
4326 gnc_options_dialog_build_contents (optionwin, options);
4327
4328 gnc_options_dialog_set_book_options_help_cb (optionwin);
4329
4330 gnc_options_dialog_set_apply_cb (optionwin,
4331 gnc_book_options_dialog_apply_cb,
4332 (gpointer)options);
4333 gnc_options_dialog_set_close_cb (optionwin,
4334 gnc_book_options_dialog_close_cb,
4335 (gpointer)options);
4336 if (modal)
4337 gnc_options_dialog_set_new_book_option_values (options);
4338 return gnc_options_dialog_widget (optionwin);
4339 }
4340
4341 static void
gnc_main_window_cmd_file_properties(GtkAction * action,GncMainWindow * window)4342 gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window)
4343 {
4344 gnc_book_options_dialog_cb (FALSE, NULL, GTK_WINDOW (window));
4345 }
4346
4347 static void
gnc_main_window_cmd_file_close(GtkAction * action,GncMainWindow * window)4348 gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window)
4349 {
4350 GncMainWindowPrivate *priv;
4351 GncPluginPage *page;
4352
4353 g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
4354
4355 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4356 page = priv->current_page;
4357 gnc_main_window_close_page(page);
4358 }
4359
4360 static void
gnc_main_window_cmd_file_quit(GtkAction * action,GncMainWindow * window)4361 gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window)
4362 {
4363 if (!gnc_main_window_all_finish_pending())
4364 return;
4365
4366 gnc_main_window_quit(window);
4367 }
4368
4369 static void
gnc_main_window_cmd_edit_cut(GtkAction * action,GncMainWindow * window)4370 gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window)
4371 {
4372 GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4373
4374 if (GTK_IS_EDITABLE(widget))
4375 {
4376 gtk_editable_cut_clipboard (GTK_EDITABLE(widget));
4377 }
4378 else if (GTK_IS_TEXT_VIEW(widget))
4379 {
4380 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4381 GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4382 GDK_SELECTION_CLIPBOARD);
4383 gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4384
4385 if (clipboard)
4386 gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
4387 }
4388 }
4389
4390 static void
gnc_main_window_cmd_edit_copy(GtkAction * action,GncMainWindow * window)4391 gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window)
4392 {
4393 GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4394
4395 if (GTK_IS_EDITABLE(widget))
4396 {
4397 gtk_editable_copy_clipboard (GTK_EDITABLE(widget));
4398 }
4399 else if (GTK_IS_TEXT_VIEW(widget))
4400 {
4401 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4402 GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4403 GDK_SELECTION_CLIPBOARD);
4404 if (clipboard)
4405 gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
4406 }
4407 }
4408
4409 static void
gnc_main_window_cmd_edit_paste(GtkAction * action,GncMainWindow * window)4410 gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window)
4411 {
4412 GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW(window));
4413
4414 if (GTK_IS_EDITABLE(widget))
4415 {
4416 gtk_editable_paste_clipboard (GTK_EDITABLE(widget));
4417 }
4418 else if (GTK_IS_TEXT_VIEW(widget))
4419 {
4420 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
4421 GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(widget),
4422 GDK_SELECTION_CLIPBOARD);
4423 gboolean editable = gtk_text_view_get_editable (GTK_TEXT_VIEW(widget));
4424
4425 if (clipboard)
4426 gtk_text_buffer_paste_clipboard (text_buffer, clipboard, NULL, editable);
4427 }
4428 }
4429
4430 static void
gnc_main_window_cmd_edit_preferences(GtkAction * action,GncMainWindow * window)4431 gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window)
4432 {
4433 gnc_preferences_dialog (GTK_WINDOW(window));
4434 }
4435
4436 static void
gnc_main_window_cmd_view_refresh(GtkAction * action,GncMainWindow * window)4437 gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window)
4438 {
4439 }
4440
4441 static void
gnc_main_window_cmd_actions_reset_warnings(GtkAction * action,GncMainWindow * window)4442 gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window)
4443 {
4444 gnc_reset_warnings_dialog(GTK_WINDOW(window));
4445 }
4446
4447 static void
gnc_main_window_cmd_actions_rename_page(GtkAction * action,GncMainWindow * window)4448 gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window)
4449 {
4450 GncMainWindowPrivate *priv;
4451 GncPluginPage *page;
4452 GtkWidget *label, *entry;
4453
4454 ENTER(" ");
4455 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4456 page = priv->current_page;
4457 if (!page)
4458 {
4459 LEAVE("No current page");
4460 return;
4461 }
4462
4463 if (!main_window_find_tab_items(window, page, &label, &entry))
4464 {
4465 LEAVE("can't find required widgets");
4466 return;
4467 }
4468
4469 gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
4470 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
4471 gtk_widget_hide(label);
4472 gtk_widget_show(entry);
4473 gtk_widget_grab_focus(entry);
4474 LEAVE("opened for editing");
4475 }
4476
4477 static void
gnc_main_window_cmd_view_toolbar(GtkAction * action,GncMainWindow * window)4478 gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window)
4479 {
4480 GncMainWindowPrivate *priv;
4481
4482 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4483 if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
4484 {
4485 gtk_widget_show (priv->toolbar);
4486 }
4487 else
4488 {
4489 gtk_widget_hide (priv->toolbar);
4490 }
4491 }
4492
4493 static void
gnc_main_window_cmd_view_summary(GtkAction * action,GncMainWindow * window)4494 gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window)
4495 {
4496 GncMainWindowPrivate *priv;
4497 GList *item;
4498 gboolean visible;
4499
4500 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4501 visible = gnc_main_window_show_summarybar(window, action);
4502 for (item = priv->installed_pages; item; item = g_list_next(item))
4503 {
4504 gnc_plugin_page_show_summarybar(item->data, visible);
4505 }
4506 }
4507
4508 static void
gnc_main_window_cmd_view_statusbar(GtkAction * action,GncMainWindow * window)4509 gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window)
4510 {
4511 GncMainWindowPrivate *priv;
4512
4513 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4514 if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
4515 {
4516 gtk_widget_show (priv->statusbar);
4517 }
4518 else
4519 {
4520 gtk_widget_hide (priv->statusbar);
4521 }
4522 }
4523
4524 static void
gnc_main_window_cmd_window_new(GtkAction * action,GncMainWindow * window)4525 gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window)
4526 {
4527 GncMainWindow *new_window;
4528
4529 /* Create the new window */
4530 ENTER(" ");
4531 new_window = gnc_main_window_new ();
4532 gtk_widget_show(GTK_WIDGET(new_window));
4533 LEAVE(" ");
4534 }
4535
4536 static void
gnc_main_window_cmd_window_move_page(GtkAction * action,GncMainWindow * window)4537 gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window)
4538 {
4539 GncMainWindowPrivate *priv;
4540 GncMainWindow *new_window;
4541 GncPluginPage *page;
4542 GtkNotebook *notebook;
4543 GtkWidget *tab_widget, *menu_widget;
4544
4545 ENTER("action %p,window %p", action, window);
4546
4547 /* Setup */
4548 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4549 page = priv->current_page;
4550 if (!page)
4551 {
4552 LEAVE("invalid page");
4553 return;
4554 }
4555 if (!page->notebook_page)
4556 {
4557 LEAVE("invalid notebook_page");
4558 return;
4559 }
4560
4561 notebook = GTK_NOTEBOOK (priv->notebook);
4562 tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
4563 menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
4564
4565 // Remove the page_changed signal callback
4566 gnc_plugin_page_disconnect_page_changed (GNC_PLUGIN_PAGE(page));
4567
4568 /* Ref the page components, then remove it from its old window */
4569 g_object_ref(page);
4570 g_object_ref(tab_widget);
4571 g_object_ref(menu_widget);
4572 g_object_ref(page->notebook_page);
4573 gnc_main_window_disconnect(window, page);
4574
4575 /* Create the new window */
4576 new_window = gnc_main_window_new ();
4577 gtk_widget_show(GTK_WIDGET(new_window));
4578
4579 /* Now add the page to the new window */
4580 gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
4581
4582 /* Unref the page components now that we're done */
4583 g_object_unref(page->notebook_page);
4584 g_object_unref(menu_widget);
4585 g_object_unref(tab_widget);
4586 g_object_unref(page);
4587
4588 /* just a little debugging. :-) */
4589 DEBUG("Moved page %p from window %p to new window %p",
4590 page, window, new_window);
4591 DEBUG("Old window current is %p, new window current is %p",
4592 priv->current_page, priv->current_page);
4593
4594 LEAVE("page moved");
4595 }
4596
4597 #ifndef MAC_INTEGRATION
4598 static void
gnc_main_window_cmd_window_raise(GtkAction * action,GtkRadioAction * current,GncMainWindow * old_window)4599 gnc_main_window_cmd_window_raise (GtkAction *action,
4600 GtkRadioAction *current,
4601 GncMainWindow *old_window)
4602 {
4603 GncMainWindow *new_window;
4604 gint value;
4605
4606 g_return_if_fail(GTK_IS_ACTION(action));
4607 g_return_if_fail(GTK_IS_RADIO_ACTION(current));
4608 g_return_if_fail(GNC_IS_MAIN_WINDOW(old_window));
4609
4610 ENTER("action %p, current %p, window %p", action, current, old_window);
4611 value = gtk_radio_action_get_current_value(current);
4612 new_window = g_list_nth_data(active_windows, value);
4613 gtk_window_present(GTK_WINDOW(new_window));
4614 /* revert the change in the radio group
4615 * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
4616 g_idle_add((GSourceFunc)gnc_main_window_update_radio_button, old_window);
4617 LEAVE(" ");
4618 }
4619 #endif /* !MAC_INTEGRATION */
4620
4621 static void
gnc_main_window_cmd_help_tutorial(GtkAction * action,GncMainWindow * window)4622 gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window)
4623 {
4624 gnc_gnome_help (GTK_WINDOW(window), HF_GUIDE, NULL);
4625 }
4626
4627 static void
gnc_main_window_cmd_help_contents(GtkAction * action,GncMainWindow * window)4628 gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window)
4629 {
4630 gnc_gnome_help (GTK_WINDOW(window), HF_HELP, NULL);
4631 }
4632
4633 /** This is a helper function to find a data file and suck it into
4634 * memory.
4635 *
4636 * @param partial The name of the file relative to the gnucash
4637 * specific shared data directory.
4638 *
4639 * @return The text of the file or NULL. The caller is responsible
4640 * for freeing this string.
4641 */
4642 static gchar *
get_file(const gchar * partial)4643 get_file (const gchar *partial)
4644 {
4645 gchar *filename, *text = NULL;
4646 gsize length;
4647
4648 filename = gnc_filepath_locate_doc_file(partial);
4649 if (filename && g_file_get_contents(filename, &text, &length, NULL))
4650 {
4651 if (length)
4652 {
4653 g_free(filename);
4654 return text;
4655 }
4656 g_free(text);
4657 }
4658 g_free (filename);
4659 return NULL;
4660 }
4661
4662
4663 /** This is a helper function to find a data file, suck it into
4664 * memory, and split it into an array of strings.
4665 *
4666 * @param partial The name of the file relative to the gnucash
4667 * specific shared data directory.
4668 *
4669 * @return The text of the file as an array of strings, or NULL. The
4670 * caller is responsible for freeing all the strings and the array.
4671 */
4672 static gchar **
get_file_strsplit(const gchar * partial)4673 get_file_strsplit (const gchar *partial)
4674 {
4675 gchar *text, **lines;
4676
4677 text = get_file(partial);
4678 if (!text)
4679 return NULL;
4680
4681 lines = g_strsplit_set(text, "\r\n", -1);
4682 g_free(text);
4683 return lines;
4684 }
4685 /** URL activation callback.
4686 * Use our own function to activate the URL in the users browser
4687 * instead of gtk_show_uri(), which requires gvfs.
4688 * Signature described in gtk docs at GtkAboutDialog activate-link signal.
4689 */
4690
4691 static gboolean
url_signal_cb(GtkAboutDialog * dialog,gchar * uri,gpointer data)4692 url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
4693 {
4694 gnc_launch_doclink (GTK_WINDOW(dialog), uri);
4695 return TRUE;
4696 }
4697
4698 /** Create and display the "about" dialog for gnucash.
4699 *
4700 * @param action The GtkAction for the "about" menu item.
4701 *
4702 * @param window The main window whose menu item was activated.
4703 */
4704 static void
gnc_main_window_cmd_help_about(GtkAction * action,GncMainWindow * window)4705 gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
4706 {
4707 /* Translators: %s will be replaced with the current year */
4708 gchar *copyright = g_strdup_printf(_("Copyright © 1997-%s The GnuCash contributors."),
4709 GNC_VCS_REV_YEAR);
4710 gchar **authors = get_file_strsplit("AUTHORS");
4711 gchar **documenters = get_file_strsplit("DOCUMENTERS");
4712 gchar *license = get_file("LICENSE");
4713 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
4714 GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
4715 GNC_ICON_APP,
4716 128,
4717 GTK_ICON_LOOKUP_USE_BUILTIN,
4718 NULL);
4719 gchar *version = g_strdup_printf ("%s: %s\n%s: %s\nFinance::Quote: %s",
4720 _("Version"), gnc_version(),
4721 _("Build ID"), gnc_build_id(),
4722 gnc_quote_source_fq_version ()
4723 ? gnc_quote_source_fq_version ()
4724 : "-");
4725 GtkDialog *dialog = GTK_DIALOG (gtk_about_dialog_new ());
4726 g_object_set (G_OBJECT (dialog),
4727 "authors", authors,
4728 "documenters", documenters,
4729 "comments", _("Accounting for personal and small business finance."),
4730 "copyright", copyright,
4731 "license", license,
4732 "logo", logo,
4733 "name", "GnuCash",
4734 /* Translators: the following string will be shown in Help->About->Credits
4735 Enter your name or that of your team and an email contact for feedback.
4736 The string can have multiple rows, so you can also add a list of
4737 contributors. */
4738 "translator-credits", _("translator-credits"),
4739 "version", version,
4740 "website", PACKAGE_URL,
4741 "website-label", _("Visit the GnuCash website."),
4742 NULL);
4743
4744 g_free(version);
4745 g_free(copyright);
4746 if (license)
4747 g_free(license);
4748 if (documenters)
4749 g_strfreev(documenters);
4750 if (authors)
4751 g_strfreev(authors);
4752 g_object_unref (logo);
4753 g_signal_connect (dialog, "activate-link",
4754 G_CALLBACK (url_signal_cb), NULL);
4755 /* Set dialog to resize. */
4756 gtk_window_set_resizable(GTK_WINDOW (dialog), TRUE);
4757
4758 gtk_window_set_transient_for (GTK_WINDOW (dialog),
4759 GTK_WINDOW (window));
4760 gtk_dialog_run (dialog);
4761 gtk_widget_destroy (GTK_WIDGET (dialog));
4762 }
4763
4764
4765 /************************************************************
4766 * *
4767 ************************************************************/
4768
4769 void
gnc_main_window_show_all_windows(void)4770 gnc_main_window_show_all_windows(void)
4771 {
4772 GList *window_iter;
4773 #ifdef MAC_INTEGRATION
4774 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
4775 #endif
4776 for (window_iter = active_windows; window_iter != NULL; window_iter = window_iter->next)
4777 {
4778 gtk_widget_show(GTK_WIDGET(window_iter->data));
4779 }
4780 #ifdef MAC_INTEGRATION
4781 g_signal_connect(theApp, "NSApplicationWillTerminate",
4782 G_CALLBACK(gnc_quartz_shutdown), NULL);
4783 gtkosx_application_ready(theApp);
4784 g_object_unref (theApp);
4785 #endif
4786 }
4787
4788 GtkWindow *
gnc_ui_get_gtk_window(GtkWidget * widget)4789 gnc_ui_get_gtk_window (GtkWidget *widget)
4790 {
4791 GtkWidget *toplevel;
4792
4793 if (!widget)
4794 return NULL;
4795
4796 toplevel = gtk_widget_get_toplevel (widget);
4797 if (toplevel && GTK_IS_WINDOW (toplevel))
4798 return GTK_WINDOW (toplevel);
4799 else
4800 return NULL;
4801 }
4802
4803 GtkWindow *
gnc_ui_get_main_window(GtkWidget * widget)4804 gnc_ui_get_main_window (GtkWidget *widget)
4805 {
4806 GList *window;
4807
4808 GtkWindow *toplevel = gnc_ui_get_gtk_window (widget);
4809 while (toplevel && !GNC_IS_MAIN_WINDOW (toplevel))
4810 toplevel = gtk_window_get_transient_for(toplevel);
4811
4812 if (toplevel)
4813 return toplevel;
4814
4815 for (window = active_windows; window; window = window->next)
4816 if (gtk_window_is_active (GTK_WINDOW (window->data)))
4817 return window->data;
4818
4819 for (window = active_windows; window; window = window->next)
4820 if (gtk_widget_get_mapped (GTK_WIDGET(window->data)))
4821 return window->data;
4822
4823 return NULL;
4824 }
4825
4826
4827 /** Retrieve the gtk window associated with a main window object.
4828 * This function is called via a vector off a generic window
4829 * interface.
4830 *
4831 * @param window A pointer to a generic window. */
4832 static GtkWindow *
gnc_main_window_get_gtk_window(GncWindow * window)4833 gnc_main_window_get_gtk_window (GncWindow *window)
4834 {
4835 g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
4836 return GTK_WINDOW(window);
4837 }
4838
4839
4840 /** Retrieve the status bar associated with a main window object.
4841 * This function is called via a vector off a generic window
4842 * interface.
4843 *
4844 * @param window_in A pointer to a generic window. */
4845 static GtkWidget *
gnc_main_window_get_statusbar(GncWindow * window_in)4846 gnc_main_window_get_statusbar (GncWindow *window_in)
4847 {
4848 GncMainWindowPrivate *priv;
4849 GncMainWindow *window;
4850
4851 g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
4852
4853 window = GNC_MAIN_WINDOW(window_in);
4854 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4855 return priv->statusbar;
4856 }
4857
4858
4859 /** Retrieve the progress bar associated with a main window object.
4860 * This function is called via a vector off a generic window
4861 * interface.
4862 *
4863 * @param window_in A pointer to a generic window. */
4864 static GtkWidget *
gnc_main_window_get_progressbar(GncWindow * window_in)4865 gnc_main_window_get_progressbar (GncWindow *window_in)
4866 {
4867 GncMainWindowPrivate *priv;
4868 GncMainWindow *window;
4869
4870 g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
4871
4872 window = GNC_MAIN_WINDOW(window_in);
4873 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4874 return priv->progressbar;
4875 }
4876
4877
4878 static void
gnc_main_window_all_ui_set_sensitive(GncWindow * unused,gboolean sensitive)4879 gnc_main_window_all_ui_set_sensitive (GncWindow *unused, gboolean sensitive)
4880 {
4881 GncMainWindow *window;
4882 GncMainWindowPrivate *priv;
4883 GList *groupp, *groups, *winp, *tmp;
4884 GtkWidget *close_button;
4885
4886 for (winp = active_windows; winp; winp = g_list_next(winp))
4887 {
4888 window = winp->data;
4889 priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
4890
4891 groups = gtk_ui_manager_get_action_groups(window->ui_merge);
4892 for (groupp = groups; groupp; groupp = g_list_next(groupp))
4893 {
4894 gtk_action_group_set_sensitive(GTK_ACTION_GROUP(groupp->data), sensitive);
4895 }
4896
4897 for (tmp = priv->installed_pages; tmp; tmp = g_list_next(tmp))
4898 {
4899 close_button = g_object_get_data(tmp->data, PLUGIN_PAGE_CLOSE_BUTTON);
4900 if (!close_button)
4901 continue;
4902 gtk_widget_set_sensitive (close_button, sensitive);
4903 }
4904 }
4905 }
4906
4907
4908 /** Initialize the generic window interface for a main window.
4909 *
4910 * @param iface A pointer to the interface data structure to
4911 * populate. */
4912 static void
gnc_window_main_window_init(GncWindowIface * iface)4913 gnc_window_main_window_init (GncWindowIface *iface)
4914 {
4915 iface->get_gtk_window = gnc_main_window_get_gtk_window;
4916 iface->get_statusbar = gnc_main_window_get_statusbar;
4917 iface->get_progressbar = gnc_main_window_get_progressbar;
4918 iface->ui_set_sensitive = gnc_main_window_all_ui_set_sensitive;
4919 }
4920
4921
4922 /* Set the window where all progressbar updates should occur. This
4923 * is a wrapper around the gnc_window_set_progressbar_window()
4924 * function.
4925 */
4926 void
gnc_main_window_set_progressbar_window(GncMainWindow * window)4927 gnc_main_window_set_progressbar_window (GncMainWindow *window)
4928 {
4929 GncWindow *gncwin;
4930 gncwin = GNC_WINDOW(window);
4931 gnc_window_set_progressbar_window(gncwin);
4932 }
4933
4934
4935 /** Popup a contextual menu. This function ends up being called when
4936 * the user right-clicks in the context of a window, or uses the
4937 * keyboard context-menu request key combination (Shift-F10 by
4938 * default).
4939 *
4940 * @param page This is the GncPluginPage corresponding to the visible
4941 * page.
4942 *
4943 * @param event The event parameter passed to the "button-press"
4944 * callback. May be null if there was no event (aka keyboard
4945 * request).
4946 */
4947 static void
do_popup_menu(GncPluginPage * page,GdkEventButton * event)4948 do_popup_menu(GncPluginPage *page, GdkEventButton *event)
4949 {
4950 GtkUIManager *ui_merge;
4951 GtkWidget *menu;
4952
4953 g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
4954
4955 ENTER("page %p, event %p", page, event);
4956 ui_merge = gnc_plugin_page_get_ui_merge(page);
4957 if (ui_merge == NULL)
4958 {
4959 LEAVE("no ui merge");
4960 return;
4961 }
4962
4963 menu = gtk_ui_manager_get_widget(ui_merge, "/MainPopup");
4964 if (!menu)
4965 {
4966 LEAVE("no menu");
4967 return;
4968 }
4969 gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
4970
4971 LEAVE(" ");
4972 }
4973
4974
4975 /** Callback function invoked when the user requests that Gnucash
4976 * popup the contextual menu via the keyboard context-menu request
4977 * key combination (Shift-F10 by default).
4978 *
4979 * @param page This is the GncPluginPage corresponding to the visible
4980 * page.
4981 *
4982 * @param widget Whatever widget had focus when the user issued the
4983 * keyboard context-menu request.
4984 *
4985 * @return Always returns TRUE to indicate that the menu request was
4986 * handled.
4987 */
4988 gboolean
gnc_main_window_popup_menu_cb(GtkWidget * widget,GncPluginPage * page)4989 gnc_main_window_popup_menu_cb (GtkWidget *widget,
4990 GncPluginPage *page)
4991 {
4992 ENTER("widget %p, page %p", widget, page);
4993 do_popup_menu(page, NULL);
4994 LEAVE(" ");
4995 return TRUE;
4996 }
4997
4998
4999 /* Callback function invoked when the user clicks in the content of
5000 * any Gnucash window. If this was a "right-click" then Gnucash will
5001 * popup the contextual menu.
5002 */
5003 gboolean
gnc_main_window_button_press_cb(GtkWidget * whatever,GdkEventButton * event,GncPluginPage * page)5004 gnc_main_window_button_press_cb (GtkWidget *whatever,
5005 GdkEventButton *event,
5006 GncPluginPage *page)
5007 {
5008 g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
5009
5010 ENTER("widget %p, event %p, page %p", whatever, event, page);
5011 /* Ignore double-clicks and triple-clicks */
5012 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
5013 {
5014 do_popup_menu(page, event);
5015 LEAVE("menu shown");
5016 return TRUE;
5017 }
5018
5019 LEAVE("other click");
5020 return FALSE;
5021 }
5022
5023 void
gnc_main_window_all_action_set_sensitive(const gchar * action_name,gboolean sensitive)5024 gnc_main_window_all_action_set_sensitive (const gchar *action_name,
5025 gboolean sensitive)
5026 {
5027 GList *tmp;
5028 GtkAction *action;
5029
5030 for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
5031 {
5032 action = gnc_main_window_find_action (tmp->data, action_name);
5033 gtk_action_set_sensitive (action, sensitive);
5034 }
5035 }
5036
gnc_main_window_get_uimanager(GncMainWindow * window)5037 GtkUIManager *gnc_main_window_get_uimanager (GncMainWindow *window)
5038 {
5039 g_assert(window);
5040 return window->ui_merge;
5041 }
5042
5043 /** @} */
5044 /** @} */
5045