GnuCash  5.6-150-g038405b370+
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 
50 static void gnc_plugin_menu_additions_finalize (GObject *object);
51 
52 static void gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
53 static void gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
54 static 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. */
59 static 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 
72 typedef struct _GncPluginMenuAdditionsPerWindow
73 {
77  GHashTable *item_hash;
78  GHashTable *build_menu_hash;
79  GMenu *report_menu;
80  GMenu *sub_menu;
82 
85 static GActionEntry gnc_plugin_actions [] =
86 {
87  { "AdditionsAction", gnc_plugin_menu_additions_action_new_cb, "s", NULL, NULL },
88 };
90 static guint gnc_plugin_n_actions = G_N_ELEMENTS(gnc_plugin_actions);
91 
92 /************************************************************
93  * Object Implementation *
94  ************************************************************/
95 
96 G_DEFINE_TYPE(GncPluginMenuAdditions, gnc_plugin_menu_additions, GNC_TYPE_PLUGIN)
97 
98 static void
99 gnc_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 
117 static void
118 gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin)
119 {
120  ENTER("plugin %p", plugin);
121  LEAVE("");
122 }
123 
124 static void
125 gnc_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  */
143 GncPlugin *
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 
158 static SCM
159 gnc_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 
172 static void
173 gnc_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 
213 static gint
214 gnc_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 
232 static gpointer
233 gnc_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 
249 static void
250 gnc_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 
306 static void
307 gnc_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 
383 static GMenuItem *
384 setup_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 
416 static void
417 gnc_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 
479 static void
480 gnc_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 
547 static void
548 gnc_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 
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
gtk helper routines.
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.
Per-window private data for this plugin.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Functions that are supported by all types of windows.
#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
Functions providing menu items from scheme code.
GncPlugin * gnc_plugin_menu_additions_new(void)
Create a new menu_additions plugin.
All type declarations for the whole Gnucash engine.
#define PLUGIN_ACTIONS_NAME
The label given to the main window for this plugin.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
GSimpleActionGroup * gnc_main_window_get_action_group(GncMainWindow *window, const gchar *group_name)
Retrieve a specific set of user interface actions from a window.
GHashTable * item_hash
The menu/toolbar action information associated with a specific window.