GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-plugin-menu-additions.c
Go to the documentation of this file.
1/*
2 * gnc-plugin-menu-additions.c --
3 * Copyright (C) 2005 David Hampton hampton@employees.org>
4 *
5 * From:
6 * gnc-menu-extensions.c -- functions to build dynamic menus
7 * Copyright (C) 1999 Rob Browning
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, contact:
21 *
22 * Free Software Foundation Voice: +1-617-542-5942
23 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
24 * Boston, MA 02110-1301, USA gnu@gnu.org
25 */
26
36#include <config.h>
37
38#include <gtk/gtk.h>
39#include <string.h>
40#include "swig-runtime.h"
41#include "guile-mappings.h"
42
43#include "gnc-engine.h"
45#include "gnc-window.h"
46#include "gnc-ui.h"
47#include "gnc-menu-extensions.h"
48#include "gnc-gtk-utils.h"
49
50static void gnc_plugin_menu_additions_finalize (GObject *object);
51
52static void gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
53static void gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
54static void gnc_plugin_menu_additions_action_new_cb (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
55
56/* Command callbacks */
57
58/* This static indicates the debugging module that this .o belongs to. */
59static QofLogModule log_module = GNC_MOD_GUI;
60
61#define PLUGIN_ACTIONS_NAME "gnc-plugin-menu-additions-actions"
62
64{
65 GncPlugin gnc_plugin;
66
67 GHashTable *item_hash;
68};
69
72typedef struct _GncPluginMenuAdditionsPerWindow
73{
77 GHashTable *item_hash;
78 GHashTable *build_menu_hash;
79 GMenu *report_menu;
80 GMenu *sub_menu;
82
85static GActionEntry gnc_plugin_actions [] =
86{
87 { "AdditionsAction", gnc_plugin_menu_additions_action_new_cb, "s", NULL, NULL },
88};
90static guint gnc_plugin_n_actions = G_N_ELEMENTS(gnc_plugin_actions);
91
92/************************************************************
93 * Object Implementation *
94 ************************************************************/
95
96G_DEFINE_TYPE(GncPluginMenuAdditions, gnc_plugin_menu_additions, GNC_TYPE_PLUGIN)
97
98static void
99gnc_plugin_menu_additions_class_init (GncPluginMenuAdditionsClass *klass)
100{
101 GObjectClass *object_class = G_OBJECT_CLASS (klass);
102 GncPluginClass *plugin_class = GNC_PLUGIN_CLASS (klass);
103
104 object_class->finalize = gnc_plugin_menu_additions_finalize;
105
106 /* plugin info */
107 plugin_class->plugin_name = GNC_PLUGIN_MENU_ADDITIONS_NAME;
108
109 /* function overrides */
110 plugin_class->add_to_window = gnc_plugin_menu_additions_add_to_window;
111 plugin_class->remove_from_window = gnc_plugin_menu_additions_remove_from_window;
112 plugin_class->actions_name = PLUGIN_ACTIONS_NAME;
113 plugin_class->actions = gnc_plugin_actions;
114 plugin_class->n_actions = gnc_plugin_n_actions;
115}
116
117static void
118gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin)
119{
120 ENTER("plugin %p", plugin);
121 LEAVE("");
122}
123
124static void
125gnc_plugin_menu_additions_finalize (GObject *object)
126{
127 g_return_if_fail (GNC_IS_PLUGIN_MENU_ADDITIONS(object));
128
129 ENTER("plugin %p", object);
130
131 g_hash_table_destroy (GNC_PLUGIN_MENU_ADDITIONS(object)->item_hash);
132
133 G_OBJECT_CLASS (gnc_plugin_menu_additions_parent_class)->finalize (object);
134 LEAVE("");
135}
136
137
138/* Create a new menu_additions plugin. This plugin attaches the menu
139 * items from Scheme code to any window that is opened.
140 *
141 * @return A pointer to the new object.
142 */
143GncPlugin *
145{
146 GncPlugin *plugin_page = NULL;
147
148 ENTER("");
149 plugin_page = GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_MENU_ADDITIONS, NULL));
150 LEAVE("plugin %p", plugin_page);
151 return plugin_page;
152}
153
154/************************************************************
155 * Plugin Function Implementation *
156 ************************************************************/
157
158static SCM
159gnc_main_window_to_scm (GncMainWindow *window)
160{
161 static swig_type_info * main_window_type = NULL;
162
163 if (!window)
164 return SCM_BOOL_F;
165
166 if (!main_window_type)
167 main_window_type = SWIG_TypeQuery("_p_GncMainWindow");
168
169 return SWIG_NewPointerObj(window, main_window_type, 0);
170}
171
172static void
173gnc_plugin_menu_additions_action_new_cb (GSimpleAction *simple,
174 GVariant *parameter,
175 gpointer user_data)
176{
177 SCM extension;
178 gsize length;
179 const gchar *action_name;
180
181 g_return_if_fail (G_IS_SIMPLE_ACTION(simple));
182
183 ENTER("");
184
185 action_name = g_variant_get_string (parameter, &length);
186
187 PINFO("action name is '%s'", action_name);
188
189 GncMainWindowActionData *cb_data = user_data;
190 GncPluginMenuAdditions *plugin = GNC_PLUGIN_MENU_ADDITIONS(cb_data->data);
191 extension = g_hash_table_lookup (plugin->item_hash, action_name);
192
193 if (extension)
194 {
195 PINFO("Found action in table");
196 gnc_extension_invoke_cb (extension, gnc_main_window_to_scm (cb_data->window));
197 }
198 LEAVE("");
199}
200
201
213static gint
214gnc_menu_additions_sort (ExtensionInfo *a, ExtensionInfo *b)
215{
216 if (a->type == b->type)
217 return strcmp(a->sort_key, b->sort_key);
218 else if (a->type == GNC_SUB_MENU_ITEM)
219 return -1;
220 else if (b->type == GNC_SUB_MENU_ITEM)
221 return 1;
222 else
223 return 0;
224}
225
226
232static gpointer
233gnc_menu_additions_init_accel_table (gpointer unused)
234{
235 return g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
236}
237
238
249static void
250gnc_menu_additions_do_preassigned_accel (ExtensionInfo *info, GHashTable *table)
251{
252 gchar *map, *new_map, *accel_key;
253 const gchar *ptr;
254
255 ENTER("Checking %s/%s [%s]", info->path, info->action_label, info->action_name);
256 if (info->accel_assigned)
257 {
258 LEAVE("Already processed");
259 return;
260 }
261
262 if (!g_utf8_validate(info->action_label, -1, NULL))
263 {
264 g_warning ("Extension menu label '%s' is not valid utf8.", info->action_label);
265 info->accel_assigned = TRUE;
266 LEAVE("Label is invalid utf8");
267 return;
268 }
269
270 /* Was an accelerator pre-assigned in the source? */
271 ptr = g_utf8_strchr (info->action_label, -1, '_');
272 if (ptr == NULL)
273 {
274 LEAVE("not preassigned");
275 return;
276 }
277
278 accel_key = g_utf8_strdown(g_utf8_next_char(ptr), 1);
279 DEBUG("Accelerator preassigned: '%s'", accel_key);
280
281 /* Now build a new map. Old one freed automatically. */
282 map = g_hash_table_lookup(table, info->path);
283 if (map == NULL)
284 map = "";
285 new_map = g_strconcat(map, accel_key, (gchar *)NULL);
286 DEBUG("path '%s', map '%s' -> '%s'", info->path, map, new_map);
287 g_hash_table_replace(table, info->path, new_map);
288
289 info->accel_assigned = TRUE;
290 g_free(accel_key);
291 LEAVE("preassigned");
292}
293
294
306static void
307gnc_menu_additions_assign_accel (ExtensionInfo *info, GHashTable *table)
308{
309 gchar *map, *new_map, *new_label, *start, buf[16];
310 const gchar *ptr;
311 gunichar uni;
312 gint len;
313 gboolean map_allocated = FALSE;
314
315 ENTER("Checking %s/%s [%s]", info->path, info->action_label, info->action_name);
316 if (info->accel_assigned)
317 {
318 LEAVE("Already processed");
319 return;
320 }
321
322 /* Get map of used keys */
323 map = g_hash_table_lookup(table, info->path);
324 if (map == NULL)
325 {
326 map = g_strdup("");
327 map_allocated = TRUE;
328 }
329 DEBUG("map '%s', path %s", map, info->path);
330
331 for (ptr = info->action_label; *ptr; ptr = g_utf8_next_char(ptr))
332 {
333 uni = g_utf8_get_char(ptr);
334 if (!g_unichar_isalpha(uni))
335 continue;
336 uni = g_unichar_tolower(uni);
337 len = g_unichar_to_utf8(uni, buf);
338 buf[len] = '\0';
339 DEBUG("Testing character '%s'", buf);
340 if (!g_utf8_strchr(map, -1, uni))
341 break;
342 }
343
344 if (ptr == NULL)
345 {
346 /* Ran out of characters. Nothing to do. */
347 info->accel_assigned = TRUE;
348 if (map_allocated)
349 {
350 g_free(map);
351 }
352 LEAVE("All characters already assigned");
353 return;
354 }
355
356 /* Now build a new string in the form "<start>_<end>". */
357 start = g_strndup (info->action_label, ptr - info->action_label);
358 DEBUG("start %p, len %ld, text '%s'", start, g_utf8_strlen(start, -1), start);
359 new_label = g_strconcat(start, "_", ptr, (gchar *)NULL);
360 g_free(start);
361 DEBUG("label '%s' -> '%s'", info->action_label, new_label);
362
363 /* Save the original action_label before adding accelerator */
364 if (info->type == GNC_SUB_MENU_ITEM)
365 info->action_label_original = g_strdup (info->action_label);
366
367 g_free((gchar *)info->action_label);
368 info->action_label = new_label;
369
370 /* Now build a new map. Old one freed automatically. */
371 new_map = g_strconcat(map, buf, (gchar *)NULL);
372 DEBUG("map '%s' -> '%s'", map, new_map);
373 g_hash_table_replace(table, info->path, new_map);
374
375 info->accel_assigned = TRUE;
376 if (map_allocated)
377 {
378 g_free(map);
379 }
380 LEAVE("assigned");
381}
382
383static GMenuItem *
384setup_gmenu_item_with_tooltip (ExtensionInfo *ext_info)
385{
386 GMenuItem *gmenu_item = NULL;
387
388 if (g_strcmp0 (ext_info->typeStr, "menuitem") == 0)
389 {
390 gmenu_item = g_menu_item_new (ext_info->action_label, NULL);
391 g_menu_item_set_action_and_target_value (gmenu_item, "gnc-plugin-menu-additions-actions.AdditionsAction",
392 g_variant_new_string (ext_info->action_name));
393
394 g_menu_item_set_attribute (gmenu_item, GNC_MENU_ATTRIBUTE_TOOLTIP, "s", ext_info->action_tooltip);
395 }
396
397 if (g_strcmp0 (ext_info->typeStr, "menu") == 0)
398 {
399 GMenuModel *sub_menu = G_MENU_MODEL(g_menu_new ());
400
401 gmenu_item = g_menu_item_new_submenu (ext_info->action_label, sub_menu);
402 g_object_set_data (G_OBJECT(gmenu_item), "sub-menu", sub_menu);
403 }
404 return gmenu_item;
405}
406
416static void
417gnc_menu_additions_menu_setup_one (ExtensionInfo *ext_info,
419{
420 GMenuItem *item_path, *item_with_full_path;
421 gchar *full_path = NULL;
422 GMenuItem *gmenu_item = NULL;
423
424 DEBUG("Adding %s/%s [%s] as [%s]", ext_info->path, ext_info->action_label,
425 ext_info->action_name, ext_info->typeStr );
426
427 g_hash_table_insert (per_window->item_hash, g_strdup (ext_info->action_name), ext_info->extension);
428
429 if (g_str_has_suffix (ext_info->path, _("_Custom")))
430 return;
431
432 full_path = g_strconcat (ext_info->path, "/", ext_info->action_label, NULL);
433
434 item_path = g_hash_table_lookup (per_window->build_menu_hash, ext_info->path);
435 item_with_full_path = g_hash_table_lookup (per_window->build_menu_hash, full_path);
436
437 if (!item_path && !item_with_full_path)
438 {
439 gmenu_item = setup_gmenu_item_with_tooltip (ext_info);
440
441 g_menu_append_item (G_MENU(per_window->report_menu), gmenu_item);
442 }
443
444 if (item_path && !item_with_full_path)
445 {
446 GMenuModel *sub_menu = G_MENU_MODEL(g_object_get_data (G_OBJECT(item_path), "sub-menu"));
447
448 gmenu_item = setup_gmenu_item_with_tooltip (ext_info);
449
450 g_menu_append_item (G_MENU(sub_menu), gmenu_item);
451 }
452 g_hash_table_insert (per_window->build_menu_hash, g_strdup (full_path), gmenu_item);
453
454 /* if the action_label has been changed, we need to add another entry to the table
455 with the original action_label so the gmenu_item can be found */
456 if (ext_info->action_label_original)
457 {
458 gchar *full_path_original = g_strconcat (ext_info->path, "/", ext_info->action_label_original, NULL);
459 g_hash_table_insert (per_window->build_menu_hash, g_strdup (full_path_original), gmenu_item);
460 g_object_ref (gmenu_item);
461 g_free (full_path_original);
462 }
463 g_free (full_path);
464}
465
466
479static void
480gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin,
481 GncMainWindow *window,
482 GQuark type)
483{
485 static GOnce accel_table_init = G_ONCE_INIT;
486 static GHashTable *table;
487 GSList *menu_list;
488 GMenuModel *menubar_model = gnc_main_window_get_menu_model (window);
489 GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
490
491 ENTER(" ");
492
493 GncPluginMenuAdditions *menu_plugin = GNC_PLUGIN_MENU_ADDITIONS (plugin);
494 if (!menu_plugin->item_hash)
495 menu_plugin->item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
496
497 per_window.item_hash = menu_plugin->item_hash;
498 per_window.build_menu_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
499 per_window.report_menu = g_menu_new ();
500
501 menu_list = g_slist_sort (gnc_extensions_get_menu_list(),
502 (GCompareFunc)gnc_menu_additions_sort);
503
504 /* Assign accelerators */
505 table = g_once (&accel_table_init, gnc_menu_additions_init_accel_table, NULL);
506 g_slist_foreach (menu_list,
507 (GFunc)gnc_menu_additions_do_preassigned_accel, table);
508 g_slist_foreach (menu_list, (GFunc)gnc_menu_additions_assign_accel, table);
509
510 /* Create the menu. */
511 g_slist_foreach (menu_list, (GFunc)gnc_menu_additions_menu_setup_one,
512 &per_window);
513
514 // add the report menu to the window
515 gsm->search_action_label = NULL;
516 gsm->search_action_name = "ReportsPlaceholder0";
517 gsm->search_action_target = NULL;
518
519 if (gnc_menubar_model_find_item (menubar_model, gsm))
520 {
521 g_menu_insert_section (G_MENU(gsm->model), gsm->index, NULL, G_MENU_MODEL(per_window.report_menu));
522 }
523 else
524 PERR("Could not find 'ReportsAction' in menu model");
525
526 g_hash_table_destroy (per_window.build_menu_hash);
527
528 g_slist_free (menu_list);
529
530 g_free (gsm);
531
532 LEAVE(" ");
533}
534
535
547static void
548gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin,
549 GncMainWindow *window,
550 GQuark type)
551{
552 GSimpleActionGroup *simple_action_group;
553
554 ENTER(" ");
555
556 /* Have to remove our actions manually. Its only automatic if the
557 * actions name is installed into the plugin class. */
558 simple_action_group = gnc_main_window_get_action_group (window, PLUGIN_ACTIONS_NAME);
559
560 if (simple_action_group && !gnc_main_window_just_plugin_prefs (window))
561 gtk_widget_insert_action_group (GTK_WIDGET(window), PLUGIN_ACTIONS_NAME, NULL);
562
563 LEAVE(" ");
564}
565
All type declarations for the whole Gnucash engine.
gtk helper routines.
Functions providing menu items from scheme code.
Functions that are supported by all types of windows.
GSimpleActionGroup * gnc_main_window_get_action_group(GncMainWindow *window, const gchar *group_name)
Retrieve a specific set of user interface actions from a window.
GMenuModel * gnc_main_window_get_menu_model(GncMainWindow *window)
Return the GMenuModel for the main window menu bar.
gboolean gnc_menubar_model_find_item(GMenuModel *menu_model, GncMenuModelSearch *gsm)
Find a GtkMenu item from the action name.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
GncPlugin * gnc_plugin_menu_additions_new(void)
Create a new menu_additions plugin.
Per-window private data for this plugin.
GHashTable * item_hash
The menu/toolbar action information associated with a specific window.