GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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 [email protected]>
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 [email protected]
25  */
26 
36 #include "config.h"
37 
38 #include <gtk/gtk.h>
39 #include <string.h>
40 #include "swig-runtime.h"
41 
42 #include "guile-util.h"
43 #include "gnc-engine.h"
44 #include "gnc-main-window.h"
46 #include "gnc-window.h"
47 #include "gnc-ui.h"
48 #include "gnc-menu-extensions.h"
49 
50 static GObjectClass *parent_class = NULL;
51 
52 static void gnc_plugin_menu_additions_class_init (GncPluginMenuAdditionsClass *klass);
53 static void gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin);
54 static void gnc_plugin_menu_additions_finalize (GObject *object);
55 
56 static void gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
57 static void gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
58 
59 /* Command callbacks */
60 
61 /* This static indicates the debugging module that this .o belongs to. */
62 static QofLogModule log_module = GNC_MOD_GUI;
63 
64 #define PLUGIN_ACTIONS_NAME "gnc-plugin-menu-additions-actions"
65 
68 {
69  gpointer dummy;
71 
72 #define GNC_PLUGIN_MENU_ADDITIONS_GET_PRIVATE(o) \
73  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_PLUGIN_MENU_ADDITIONS, GncPluginMenuAdditionsPrivate))
74 
75 
79 {
84  GtkUIManager *ui_manager;
85  GtkActionGroup *group;
86  gint merge_id;
88 
89 /************************************************************
90  * Object Implementation *
91  ************************************************************/
92 
93 GType
95 {
96  static GType gnc_plugin_menu_additions_type = 0;
97 
98  if (gnc_plugin_menu_additions_type == 0)
99  {
100  static const GTypeInfo our_info =
101  {
103  NULL, /* base_init */
104  NULL, /* base_finalize */
105  (GClassInitFunc) gnc_plugin_menu_additions_class_init,
106  NULL, /* class_finalize */
107  NULL, /* class_data */
108  sizeof (GncPluginMenuAdditions),
109  0,
110  (GInstanceInitFunc) gnc_plugin_menu_additions_init
111  };
112 
113  gnc_plugin_menu_additions_type = g_type_register_static (GNC_TYPE_PLUGIN,
114  "GncPluginMenuAdditions",
115  &our_info, 0);
116  }
117 
118  return gnc_plugin_menu_additions_type;
119 }
120 
121 static void
122 gnc_plugin_menu_additions_class_init (GncPluginMenuAdditionsClass *klass)
123 {
124  GObjectClass *object_class = G_OBJECT_CLASS (klass);
125  GncPluginClass *plugin_class = GNC_PLUGIN_CLASS (klass);
126 
127  parent_class = g_type_class_peek_parent (klass);
128 
129  object_class->finalize = gnc_plugin_menu_additions_finalize;
130 
131  /* plugin info */
132  plugin_class->plugin_name = GNC_PLUGIN_MENU_ADDITIONS_NAME;
133 
134  /* function overrides */
135  plugin_class->add_to_window = gnc_plugin_menu_additions_add_to_window;
136  plugin_class->remove_from_window = gnc_plugin_menu_additions_remove_from_window;
137 
138  g_type_class_add_private(klass, sizeof(GncPluginMenuAdditionsPrivate));
139 }
140 
141 static void
142 gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin)
143 {
144  ENTER("plugin %p", plugin);
145  LEAVE("");
146 }
147 
148 static void
149 gnc_plugin_menu_additions_finalize (GObject *object)
150 {
151  g_return_if_fail (GNC_IS_PLUGIN_MENU_ADDITIONS (object));
152 
153  ENTER("plugin %p", object);
154  G_OBJECT_CLASS (parent_class)->finalize (object);
155  LEAVE("");
156 }
157 
158 
159 /* Create a new menu_additions plugin. This plugin attaches the menu
160  * items from Scheme code to any window that is opened.
161  *
162  * @return A pointer to the new object.
163  */
164 GncPlugin *
166 {
167  GncPlugin *plugin_page = NULL;
168 
169  ENTER("");
170  plugin_page = GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_MENU_ADDITIONS, NULL));
171  LEAVE("plugin %p", plugin_page);
172  return plugin_page;
173 }
174 
175 /************************************************************
176  * Plugin Function Implementation *
177  ************************************************************/
178 
179 static SCM
180 gnc_main_window_to_scm (GncMainWindow *window)
181 {
182  static swig_type_info * main_window_type = NULL;
183 
184  if (!window)
185  return SCM_BOOL_F;
186 
187  if (!main_window_type)
188  main_window_type = SWIG_TypeQuery("_p_GncMainWindow");
189 
190  return SWIG_NewPointerObj(window, main_window_type, 0);
191 }
192 
193 
205 static void
206 gnc_plugin_menu_additions_action_cb (GtkAction *action,
208 {
209 
210  g_return_if_fail(GTK_IS_ACTION(action));
211  g_return_if_fail(data != NULL);
212 
213  gnc_extension_invoke_cb(data->data, gnc_main_window_to_scm(data->window));
214 }
215 
216 
228 static gint
229 gnc_menu_additions_sort (ExtensionInfo *a, ExtensionInfo *b)
230 {
231  if (a->type == b->type)
232  return strcmp(a->sort_key, b->sort_key);
233  else if (a->type == GTK_UI_MANAGER_MENU)
234  return -1;
235  else if (b->type == GTK_UI_MANAGER_MENU)
236  return 1;
237  else
238  return 0;
239 }
240 
241 
247 static gpointer
248 gnc_menu_additions_init_accel_table (gpointer unused)
249 {
250  return g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
251 }
252 
253 
264 static void
265 gnc_menu_additions_do_preassigned_accel (ExtensionInfo *info, GHashTable *table)
266 {
267  gchar *map, *new_map, *accel_key;
268  const gchar *ptr;
269 
270  ENTER("Checking %s/%s [%s]", info->path, info->ae.label, info->ae.name);
271  if (info->accel_assigned)
272  {
273  LEAVE("Already processed");
274  return;
275  }
276 
277  if (!g_utf8_validate(info->ae.label, -1, NULL))
278  {
279  g_warning("Extension menu label '%s' is not valid utf8.", info->ae.label);
280  info->accel_assigned = TRUE;
281  LEAVE("Label is invalid utf8");
282  return;
283  }
284 
285  /* Was an accelerator pre-assigned in the source? */
286  ptr = g_utf8_strchr(info->ae.label, -1, '_');
287  if (ptr == NULL)
288  {
289  LEAVE("not preassigned");
290  return;
291  }
292 
293  accel_key = g_utf8_strdown(g_utf8_next_char(ptr), 1);
294  DEBUG("Accelerator preassigned: '%s'", accel_key);
295 
296  /* Now build a new map. Old one freed automatically. */
297  map = g_hash_table_lookup(table, info->path);
298  if (map == NULL)
299  map = "";
300  new_map = g_strconcat(map, accel_key, (gchar *)NULL);
301  DEBUG("path '%s', map '%s' -> '%s'", info->path, map, new_map);
302  g_hash_table_replace(table, info->path, new_map);
303 
304  info->accel_assigned = TRUE;
305  g_free(accel_key);
306  LEAVE("preassigned");
307 }
308 
309 
321 static void
322 gnc_menu_additions_assign_accel (ExtensionInfo *info, GHashTable *table)
323 {
324  gchar *map, *new_map, *new_label, *start, buf[16];
325  const gchar *ptr;
326  gunichar uni;
327  gint len;
328 
329  ENTER("Checking %s/%s [%s]", info->path, info->ae.label, info->ae.name);
330  if (info->accel_assigned)
331  {
332  LEAVE("Already processed");
333  return;
334  }
335 
336  /* Get map of used keys */
337  map = g_hash_table_lookup(table, info->path);
338  if (map == NULL)
339  map = g_strdup("");
340  DEBUG("map '%s', path %s", map, info->path);
341 
342  for (ptr = info->ae.label; *ptr; ptr = g_utf8_next_char(ptr))
343  {
344  uni = g_utf8_get_char(ptr);
345  if (!g_unichar_isalpha(uni))
346  continue;
347  uni = g_unichar_tolower(uni);
348  len = g_unichar_to_utf8(uni, buf);
349  buf[len] = '\0';
350  DEBUG("Testing character '%s'", buf);
351  if (!g_utf8_strchr(map, -1, uni))
352  break;
353  }
354 
355  if (ptr == NULL)
356  {
357  /* Ran out of characters. Nothing to do. */
358  info->accel_assigned = TRUE;
359  LEAVE("All characters already assigned");
360  return;
361  }
362 
363  /* Now build a new string in the form "<start>_<end>". */
364  start = g_strndup(info->ae.label, ptr - info->ae.label);
365  DEBUG("start %p, len %ld, text '%s'", start, g_utf8_strlen(start, -1), start);
366  new_label = g_strconcat(start, "_", ptr, (gchar *)NULL);
367  g_free(start);
368  DEBUG("label '%s' -> '%s'", info->ae.label, new_label);
369  g_free((gchar *)info->ae.label);
370  info->ae.label = new_label;
371 
372  /* Now build a new map. Old one freed automatically. */
373  new_map = g_strconcat(map, buf, (gchar *)NULL);
374  DEBUG("map '%s' -> '%s'", map, new_map);
375  g_hash_table_replace(table, info->path, new_map);
376 
377  info->accel_assigned = TRUE;
378  LEAVE("assigned");
379 }
380 
381 
391 static void
392 gnc_menu_additions_menu_setup_one (ExtensionInfo *ext_info,
394 {
395  GncMainWindowActionData *cb_data;
396 
397  DEBUG( "Adding %s/%s [%s] as [%s]", ext_info->path, ext_info->ae.label,
398  ext_info->ae.name, ext_info->typeStr );
399 
400  cb_data = g_new0 (GncMainWindowActionData, 1);
401  cb_data->window = per_window->window;
402  cb_data->data = ext_info->extension;
403 
404  if (ext_info->type == GTK_UI_MANAGER_MENUITEM)
405  ext_info->ae.callback = (GCallback)gnc_plugin_menu_additions_action_cb;
406 
407  gtk_action_group_add_actions_full(per_window->group, &ext_info->ae, 1,
408  cb_data, g_free);
409  gtk_ui_manager_add_ui(per_window->ui_manager, per_window->merge_id,
410  ext_info->path, ext_info->ae.label, ext_info->ae.name,
411  ext_info->type, FALSE);
412  gtk_ui_manager_ensure_update(per_window->ui_manager);
413 }
414 
415 
428 static void
429 gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin,
430  GncMainWindow *window,
431  GQuark type)
432 {
434  static GOnce accel_table_init = G_ONCE_INIT;
435  static GHashTable *table;
436  GSList *menu_list;
437 
438  ENTER(" ");
439 
440  per_window.window = window;
441  per_window.ui_manager = window->ui_merge;
442  per_window.group = gtk_action_group_new ("MenuAdditions" );
443  gnc_gtk_action_group_set_translation_domain (per_window.group, GETTEXT_PACKAGE);
444  per_window.merge_id = gtk_ui_manager_new_merge_id(window->ui_merge);
445  gtk_ui_manager_insert_action_group(window->ui_merge, per_window.group, 0);
446 
447  menu_list = g_slist_sort(gnc_extensions_get_menu_list(),
448  (GCompareFunc)gnc_menu_additions_sort);
449 
450  /* Assign accelerators */
451  table = g_once(&accel_table_init, gnc_menu_additions_init_accel_table, NULL);
452  g_slist_foreach(menu_list,
453  (GFunc)gnc_menu_additions_do_preassigned_accel, table);
454  g_slist_foreach(menu_list, (GFunc)gnc_menu_additions_assign_accel, table);
455 
456  /* Add to window. */
457  g_slist_foreach(menu_list, (GFunc)gnc_menu_additions_menu_setup_one,
458  &per_window);
459 
460  /* Tell the window code about the actions that were just added
461  * behind its back (so to speak) */
463  per_window.group, per_window.merge_id);
464 
465  g_slist_free(menu_list);
466  LEAVE(" ");
467 }
468 
469 
481 static void
482 gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin,
483  GncMainWindow *window,
484  GQuark type)
485 {
486  GtkActionGroup *group;
487 
488  ENTER(" ");
489 
490  /* Have to remove our actions manually. Its only automatic if the
491  * actions name is installed into the plugin class. */
493  if (group)
494  gtk_ui_manager_remove_action_group(window->ui_merge, group);
495 
496  /* Note: This code does not clean up the per-callback data structures
497  * that are created by the gnc_menu_additions_menu_setup_one()
498  * function. Its not much memory and shouldn't be a problem. */
499 
500  LEAVE(" ");
501 }
502 
#define DEBUG(format, args...)
Definition: qoflog.h:255
Functions that are supported by all types of windows.
#define ENTER(format, args...)
Definition: qoflog.h:261
Functions for adding content to a window.
struct GncPluginMenuAdditionsPrivate GncPluginMenuAdditionsPrivate
void(* remove_from_window)(GncPlugin *plugin, GncMainWindow *window, GQuark type)
Definition: gnc-plugin.h:171
Functions providing menu items from scheme code.
GtkUIManager * ui_merge
struct _GncPluginMenuAdditionsPerWindow GncPluginMenuAdditionsPerWindow
GType gnc_plugin_menu_additions_get_type(void)
GncPlugin * gnc_plugin_menu_additions_new(void)
All type declarations for the whole Gnucash engine.
GtkActionGroup * gnc_main_window_get_action_group(GncMainWindow *window, const gchar *group_name)
const gchar * plugin_name
Definition: gnc-plugin.h:112
void gnc_main_window_manual_merge_actions(GncMainWindow *window, const gchar *group_name, GtkActionGroup *group, guint merge_id)
#define PLUGIN_ACTIONS_NAME
#define LEAVE(format, args...)
Definition: qoflog.h:271
void(* add_to_window)(GncPlugin *plugin, GncMainWindow *window, GQuark type)
Definition: gnc-plugin.h:155
void gnc_gtk_action_group_set_translation_domain(GtkActionGroup *action_group, const gchar *domain)
const gchar * QofLogModule
Definition: qofid.h:89