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  g_free((gchar *)info->action_label);
363  info->action_label = new_label;
364 
365  /* Now build a new map. Old one freed automatically. */
366  new_map = g_strconcat(map, buf, (gchar *)NULL);
367  DEBUG("map '%s' -> '%s'", map, new_map);
368  g_hash_table_replace(table, info->path, new_map);
369 
370  info->accel_assigned = TRUE;
371  if (map_allocated)
372  {
373  g_free(map);
374  }
375  LEAVE("assigned");
376 }
377 
378 static GMenuItem *
379 setup_gmenu_item_with_tooltip (ExtensionInfo *ext_info)
380 {
381  GMenuItem *gmenu_item = NULL;
382 
383  if (g_strcmp0 (ext_info->typeStr, "menuitem") == 0)
384  {
385  gmenu_item = g_menu_item_new (ext_info->action_label, NULL);
386  g_menu_item_set_action_and_target_value (gmenu_item, "gnc-plugin-menu-additions-actions.AdditionsAction",
387  g_variant_new_string (ext_info->action_name));
388 
389  g_menu_item_set_attribute (gmenu_item, GNC_MENU_ATTRIBUTE_TOOLTIP, "s", ext_info->action_tooltip);
390  }
391 
392  if (g_strcmp0 (ext_info->typeStr, "menu") == 0)
393  {
394  GMenuModel *sub_menu = G_MENU_MODEL(g_menu_new ());
395 
396  gmenu_item = g_menu_item_new_submenu (ext_info->action_label, sub_menu);
397  g_object_set_data (G_OBJECT(gmenu_item), "sub-menu", sub_menu);
398  }
399  return gmenu_item;
400 }
401 
411 static void
412 gnc_menu_additions_menu_setup_one (ExtensionInfo *ext_info,
414 {
415  GMenuItem *item_path, *item_with_full_path;
416  gchar *full_path = NULL;
417  GMenuItem *gmenu_item = NULL;
418 
419  DEBUG("Adding %s/%s [%s] as [%s]", ext_info->path, ext_info->action_label,
420  ext_info->action_name, ext_info->typeStr );
421 
422  g_hash_table_insert (per_window->item_hash, g_strdup (ext_info->action_name), ext_info->extension);
423 
424  if (g_str_has_suffix (ext_info->path, _("_Custom")))
425  return;
426 
427  full_path = g_strconcat (ext_info->path, "/", ext_info->action_label, NULL);
428 
429  item_path = g_hash_table_lookup (per_window->build_menu_hash, ext_info->path);
430  item_with_full_path = g_hash_table_lookup (per_window->build_menu_hash, full_path);
431 
432  if (!item_path && !item_with_full_path)
433  {
434  gmenu_item = setup_gmenu_item_with_tooltip (ext_info);
435 
436  g_menu_append_item (G_MENU(per_window->report_menu), gmenu_item);
437  }
438 
439  if (item_path && !item_with_full_path)
440  {
441  GMenuModel *sub_menu = G_MENU_MODEL(g_object_get_data (G_OBJECT(item_path), "sub-menu"));
442 
443  gmenu_item = setup_gmenu_item_with_tooltip (ext_info);
444 
445  g_menu_append_item (G_MENU(sub_menu), gmenu_item);
446  }
447  g_hash_table_insert (per_window->build_menu_hash, g_strdup (full_path), gmenu_item);
448 
449  g_free (full_path);
450 }
451 
452 
465 static void
466 gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin,
467  GncMainWindow *window,
468  GQuark type)
469 {
471  static GOnce accel_table_init = G_ONCE_INIT;
472  static GHashTable *table;
473  GSList *menu_list;
474  GMenuModel *menubar_model = gnc_main_window_get_menu_model (window);
475  GncMenuModelSearch *gsm = g_new0 (GncMenuModelSearch, 1);
476 
477  ENTER(" ");
478 
479  GncPluginMenuAdditions *menu_plugin = GNC_PLUGIN_MENU_ADDITIONS (plugin);
480  if (!menu_plugin->item_hash)
481  menu_plugin->item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
482 
483  per_window.item_hash = menu_plugin->item_hash;
484  per_window.build_menu_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
485  per_window.report_menu = g_menu_new ();
486 
487  menu_list = g_slist_sort (gnc_extensions_get_menu_list(),
488  (GCompareFunc)gnc_menu_additions_sort);
489 
490  /* Assign accelerators */
491  table = g_once (&accel_table_init, gnc_menu_additions_init_accel_table, NULL);
492  g_slist_foreach (menu_list,
493  (GFunc)gnc_menu_additions_do_preassigned_accel, table);
494  g_slist_foreach (menu_list, (GFunc)gnc_menu_additions_assign_accel, table);
495 
496  /* Create the menu. */
497  g_slist_foreach (menu_list, (GFunc)gnc_menu_additions_menu_setup_one,
498  &per_window);
499 
500  // add the report menu to the window
501  gsm->search_action_label = NULL;
502  gsm->search_action_name = "ReportsPlaceholder0";
503  gsm->search_action_target = NULL;
504 
505  if (gnc_menubar_model_find_item (menubar_model, gsm))
506  {
507  g_menu_insert_section (G_MENU(gsm->model), gsm->index, NULL, G_MENU_MODEL(per_window.report_menu));
508  }
509  else
510  PERR("Could not find 'ReportsAction' in menu model");
511 
512  g_hash_table_destroy (per_window.build_menu_hash);
513 
514  g_slist_free (menu_list);
515 
516  g_free (gsm);
517 
518  LEAVE(" ");
519 }
520 
521 
533 static void
534 gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin,
535  GncMainWindow *window,
536  GQuark type)
537 {
538  GSimpleActionGroup *simple_action_group;
539 
540  ENTER(" ");
541 
542  /* Have to remove our actions manually. Its only automatic if the
543  * actions name is installed into the plugin class. */
544  simple_action_group = gnc_main_window_get_action_group (window, PLUGIN_ACTIONS_NAME);
545 
546  if (simple_action_group && !window->just_plugin_prefs)
547  gtk_widget_insert_action_group (GTK_WIDGET(window), PLUGIN_ACTIONS_NAME, NULL);
548 
549  LEAVE(" ");
550 }
551 
#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.
The instance data structure for a main window object.