GnuCash 2.4.99
gnc-plugin-menu-additions.c
Go to the documentation of this file.
00001 /*
00002  * gnc-plugin-menu-additions.c --
00003  * Copyright (C) 2005 David Hampton hampton@employees.org>
00004  *
00005  * From:
00006  * gnc-menu-extensions.c -- functions to build dynamic menus
00007  * Copyright (C) 1999 Rob Browning
00008  *
00009  * This program is free software; you can redistribute it and/or
00010  * modify it under the terms of the GNU General Public License as
00011  * published by the Free Software Foundation; either version 2 of
00012  * the License, or (at your option) any later version.
00013  *
00014  * This program is distributed in the hope that it will be useful,
00015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  * GNU General Public License for more details.
00018  *
00019  * You should have received a copy of the GNU General Public License
00020  * along with this program; if not, contact:
00021  *
00022  * Free Software Foundation           Voice:  +1-617-542-5942
00023  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
00024  * Boston, MA  02110-1301,  USA       gnu@gnu.org
00025  */
00026 
00036 #include "config.h"
00037 
00038 #include <gtk/gtk.h>
00039 #include <string.h>
00040 #include "swig-runtime.h"
00041 
00042 #include "guile-util.h"
00043 #include "gnc-engine.h"
00044 #include "gnc-main-window.h"
00045 #include "gnc-plugin-menu-additions.h"
00046 #include "gnc-window.h"
00047 #include "gnc-gconf-utils.h"
00048 #include "gnc-ui.h"
00049 #include "gnc-menu-extensions.h"
00050 
00051 static GObjectClass *parent_class = NULL;
00052 
00053 static void gnc_plugin_menu_additions_class_init (GncPluginMenuAdditionsClass *klass);
00054 static void gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin);
00055 static void gnc_plugin_menu_additions_finalize (GObject *object);
00056 
00057 static void gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
00058 static void gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin, GncMainWindow *window, GQuark type);
00059 
00060 /* Command callbacks */
00061 
00062 /* This static indicates the debugging module that this .o belongs to.  */
00063 static QofLogModule log_module = GNC_MOD_GUI;
00064 
00065 #define PLUGIN_ACTIONS_NAME "gnc-plugin-menu-additions-actions"
00066 
00068 typedef struct GncPluginMenuAdditionsPrivate
00069 {
00070     gpointer dummy;
00071 } GncPluginMenuAdditionsPrivate;
00072 
00073 #define GNC_PLUGIN_MENU_ADDITIONS_GET_PRIVATE(o)  \
00074    (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_PLUGIN_MENU_ADDITIONS, GncPluginMenuAdditionsPrivate))
00075 
00076 
00079 typedef struct _GncPluginMenuAdditionsPerWindow
00080 {
00084     GncMainWindow  *window;
00085     GtkUIManager   *ui_manager;
00086     GtkActionGroup *group;
00087     gint merge_id;
00088 } GncPluginMenuAdditionsPerWindow;
00089 
00090 /************************************************************
00091  *                  Object Implementation                   *
00092  ************************************************************/
00093 
00094 GType
00095 gnc_plugin_menu_additions_get_type (void)
00096 {
00097     static GType gnc_plugin_menu_additions_type = 0;
00098 
00099     if (gnc_plugin_menu_additions_type == 0)
00100     {
00101         static const GTypeInfo our_info =
00102         {
00103             sizeof (GncPluginMenuAdditionsClass),
00104             NULL,               /* base_init */
00105             NULL,               /* base_finalize */
00106             (GClassInitFunc) gnc_plugin_menu_additions_class_init,
00107             NULL,               /* class_finalize */
00108             NULL,               /* class_data */
00109             sizeof (GncPluginMenuAdditions),
00110             0,
00111             (GInstanceInitFunc) gnc_plugin_menu_additions_init
00112         };
00113 
00114         gnc_plugin_menu_additions_type = g_type_register_static (GNC_TYPE_PLUGIN,
00115                                          "GncPluginMenuAdditions",
00116                                          &our_info, 0);
00117     }
00118 
00119     return gnc_plugin_menu_additions_type;
00120 }
00121 
00122 static void
00123 gnc_plugin_menu_additions_class_init (GncPluginMenuAdditionsClass *klass)
00124 {
00125     GObjectClass *object_class = G_OBJECT_CLASS (klass);
00126     GncPluginClass *plugin_class = GNC_PLUGIN_CLASS (klass);
00127 
00128     parent_class = g_type_class_peek_parent (klass);
00129 
00130     object_class->finalize = gnc_plugin_menu_additions_finalize;
00131 
00132     /* plugin info */
00133     plugin_class->plugin_name   = GNC_PLUGIN_MENU_ADDITIONS_NAME;
00134 
00135     /* function overrides */
00136     plugin_class->add_to_window = gnc_plugin_menu_additions_add_to_window;
00137     plugin_class->remove_from_window = gnc_plugin_menu_additions_remove_from_window;
00138 
00139     g_type_class_add_private(klass, sizeof(GncPluginMenuAdditionsPrivate));
00140 }
00141 
00142 static void
00143 gnc_plugin_menu_additions_init (GncPluginMenuAdditions *plugin)
00144 {
00145     ENTER("plugin %p", plugin);
00146     LEAVE("");
00147 }
00148 
00149 static void
00150 gnc_plugin_menu_additions_finalize (GObject *object)
00151 {
00152     GncPluginMenuAdditions *plugin;
00153     GncPluginMenuAdditionsPrivate *priv;
00154 
00155     g_return_if_fail (GNC_IS_PLUGIN_MENU_ADDITIONS (object));
00156 
00157     ENTER("plugin %p", object);
00158     plugin = GNC_PLUGIN_MENU_ADDITIONS (object);
00159     priv = GNC_PLUGIN_MENU_ADDITIONS_GET_PRIVATE (plugin);
00160 
00161     G_OBJECT_CLASS (parent_class)->finalize (object);
00162     LEAVE("");
00163 }
00164 
00165 
00166 /*  Create a new menu_additions plugin.  This plugin attaches the menu
00167  *  items from Scheme code to any window that is opened.
00168  *
00169  *  @return A pointer to the new object.
00170  */
00171 GncPlugin *
00172 gnc_plugin_menu_additions_new (void)
00173 {
00174     GncPlugin *plugin_page = NULL;
00175 
00176     ENTER("");
00177     plugin_page = GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_MENU_ADDITIONS, NULL));
00178     LEAVE("plugin %p", plugin_page);
00179     return plugin_page;
00180 }
00181 
00182 /************************************************************
00183  *              Plugin Function Implementation              *
00184  ************************************************************/
00185 
00186 static SCM
00187 gnc_main_window_to_scm (GncMainWindow *window)
00188 {
00189     static swig_type_info * main_window_type = NULL;
00190 
00191     if (!window)
00192         return SCM_BOOL_F;
00193 
00194     if (!main_window_type)
00195         main_window_type = SWIG_TypeQuery("_p_GncMainWindow");
00196 
00197     return SWIG_NewPointerObj(window, main_window_type, 0);
00198 }
00199 
00200 
00212 static void
00213 gnc_plugin_menu_additions_action_cb (GtkAction *action,
00214                                      GncMainWindowActionData *data)
00215 {
00216 
00217     g_return_if_fail(GTK_IS_ACTION(action));
00218     g_return_if_fail(data != NULL);
00219 
00220     gnc_extension_invoke_cb(data->data, gnc_main_window_to_scm(data->window));
00221 }
00222 
00223 
00235 static gint
00236 gnc_menu_additions_sort (ExtensionInfo *a, ExtensionInfo *b)
00237 {
00238     if (a->type == b->type)
00239         return strcmp(a->sort_key, b->sort_key);
00240     else if (a->type == GTK_UI_MANAGER_MENU)
00241         return -1;
00242     else if (b->type == GTK_UI_MANAGER_MENU)
00243         return 1;
00244     else
00245         return 0;
00246 }
00247 
00248 
00254 static gpointer
00255 gnc_menu_additions_init_accel_table (gpointer unused)
00256 {
00257     return g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
00258 }
00259 
00260 
00271 static void
00272 gnc_menu_additions_do_preassigned_accel (ExtensionInfo *info, GHashTable *table)
00273 {
00274     gchar *map, *new_map, *accel_key;
00275     const gchar *ptr;
00276 
00277     ENTER("Checking %s/%s [%s]", info->path, info->ae.label, info->ae.name);
00278     if (info->accel_assigned)
00279     {
00280         LEAVE("Already processed");
00281         return;
00282     }
00283 
00284     if (!g_utf8_validate(info->ae.label, -1, NULL))
00285     {
00286         g_warning("Extension menu label '%s' is not valid utf8.", info->ae.label);
00287         info->accel_assigned = TRUE;
00288         LEAVE("Label is invalid utf8");
00289         return;
00290     }
00291 
00292     /* Was an accelerator pre-assigned in the source? */
00293     ptr = g_utf8_strchr(info->ae.label, -1, '_');
00294     if (ptr == NULL)
00295     {
00296         LEAVE("not preassigned");
00297         return;
00298     }
00299 
00300     accel_key = g_utf8_strdown(g_utf8_next_char(ptr), 1);
00301     DEBUG("Accelerator preassigned: '%s'", accel_key);
00302 
00303     /* Now build a new map. Old one freed automatically. */
00304     map = g_hash_table_lookup(table, info->path);
00305     if (map == NULL)
00306         map = "";
00307     new_map = g_strconcat(map, accel_key, (gchar *)NULL);
00308     DEBUG("path '%s', map '%s' -> '%s'", info->path, map, new_map);
00309     g_hash_table_replace(table, info->path, new_map);
00310 
00311     info->accel_assigned = TRUE;
00312     g_free(accel_key);
00313     LEAVE("preassigned");
00314 }
00315 
00316 
00328 static void
00329 gnc_menu_additions_assign_accel (ExtensionInfo *info, GHashTable *table)
00330 {
00331     gchar *map, *new_map, *new_label, *start, buf[16];
00332     const gchar *ptr;
00333     gunichar uni;
00334     gint len;
00335 
00336     ENTER("Checking %s/%s [%s]", info->path, info->ae.label, info->ae.name);
00337     if (info->accel_assigned)
00338     {
00339         LEAVE("Already processed");
00340         return;
00341     }
00342 
00343     /* Get map of used keys */
00344     map = g_hash_table_lookup(table, info->path);
00345     if (map == NULL)
00346         map = g_strdup("");
00347     DEBUG("map '%s', path %s", map, info->path);
00348 
00349     for (ptr = info->ae.label; *ptr; ptr = g_utf8_next_char(ptr))
00350     {
00351         uni = g_utf8_get_char(ptr);
00352         if (!g_unichar_isalpha(uni))
00353             continue;
00354         uni = g_unichar_tolower(uni);
00355         len = g_unichar_to_utf8(uni, buf);
00356         buf[len] = '\0';
00357         DEBUG("Testing character '%s'", buf);
00358         if (!g_utf8_strchr(map, -1, uni))
00359             break;
00360     }
00361 
00362     if (ptr == NULL)
00363     {
00364         /* Ran out of characters. Nothing to do. */
00365         info->accel_assigned = TRUE;
00366         LEAVE("All characters already assigned");
00367         return;
00368     }
00369 
00370     /* Now build a new string in the form "<start>_<end>". */
00371     start = g_strndup(info->ae.label, ptr - info->ae.label);
00372     DEBUG("start %p, len %ld, text '%s'", start, g_utf8_strlen(start, -1), start);
00373     new_label = g_strconcat(start, "_", ptr, (gchar *)NULL);
00374     g_free(start);
00375     DEBUG("label '%s' -> '%s'", info->ae.label, new_label);
00376     g_free((gchar *)info->ae.label);
00377     info->ae.label = new_label;
00378 
00379     /* Now build a new map. Old one freed automatically. */
00380     new_map = g_strconcat(map, buf, (gchar *)NULL);
00381     DEBUG("map '%s' -> '%s'", map, new_map);
00382     g_hash_table_replace(table, info->path, new_map);
00383 
00384     info->accel_assigned = TRUE;
00385     LEAVE("assigned");
00386 }
00387 
00388 
00398 static void
00399 gnc_menu_additions_menu_setup_one (ExtensionInfo *ext_info,
00400                                    GncPluginMenuAdditionsPerWindow *per_window)
00401 {
00402     GncMainWindowActionData *cb_data;
00403 
00404     DEBUG( "Adding %s/%s [%s] as [%s]", ext_info->path, ext_info->ae.label,
00405            ext_info->ae.name, ext_info->typeStr );
00406 
00407     cb_data = g_new0 (GncMainWindowActionData, 1);
00408     cb_data->window = per_window->window;
00409     cb_data->data = ext_info->extension;
00410 
00411     if (ext_info->type == GTK_UI_MANAGER_MENUITEM)
00412         ext_info->ae.callback = (GCallback)gnc_plugin_menu_additions_action_cb;
00413 
00414     gtk_action_group_add_actions_full(per_window->group, &ext_info->ae, 1,
00415                                       cb_data, g_free);
00416     gtk_ui_manager_add_ui(per_window->ui_manager, per_window->merge_id,
00417                           ext_info->path, ext_info->ae.label, ext_info->ae.name,
00418                           ext_info->type, FALSE);
00419     gtk_ui_manager_ensure_update(per_window->ui_manager);
00420 }
00421 
00422 
00435 static void
00436 gnc_plugin_menu_additions_add_to_window (GncPlugin *plugin,
00437         GncMainWindow *window,
00438         GQuark type)
00439 {
00440     GncPluginMenuAdditionsPerWindow per_window;
00441     static GOnce accel_table_init = G_ONCE_INIT;
00442     static GHashTable *table;
00443     GSList *menu_list;
00444 
00445     ENTER(" ");
00446 
00447     per_window.window = window;
00448     per_window.ui_manager = window->ui_merge;
00449     per_window.group = gtk_action_group_new ("MenuAdditions" );
00450     gnc_gtk_action_group_set_translation_domain (per_window.group, GETTEXT_PACKAGE);
00451     per_window.merge_id = gtk_ui_manager_new_merge_id(window->ui_merge);
00452     gtk_ui_manager_insert_action_group(window->ui_merge, per_window.group, 0);
00453 
00454     menu_list = g_slist_sort(gnc_extensions_get_menu_list(),
00455                              (GCompareFunc)gnc_menu_additions_sort);
00456 
00457     /* Assign accelerators */
00458     table = g_once(&accel_table_init, gnc_menu_additions_init_accel_table, NULL);
00459     g_slist_foreach(menu_list,
00460                     (GFunc)gnc_menu_additions_do_preassigned_accel, table);
00461     g_slist_foreach(menu_list, (GFunc)gnc_menu_additions_assign_accel, table);
00462 
00463     /* Add to window. */
00464     g_slist_foreach(menu_list, (GFunc)gnc_menu_additions_menu_setup_one,
00465                     &per_window);
00466 
00467     /* Tell the window code about the actions that were just added
00468      * behind its back (so to speak) */
00469     gnc_main_window_manual_merge_actions (window, PLUGIN_ACTIONS_NAME,
00470                                           per_window.group, per_window.merge_id);
00471 
00472     g_slist_free(menu_list);
00473     LEAVE(" ");
00474 }
00475 
00476 
00488 static void
00489 gnc_plugin_menu_additions_remove_from_window (GncPlugin *plugin,
00490         GncMainWindow *window,
00491         GQuark type)
00492 {
00493     GtkActionGroup *group;
00494 
00495     ENTER(" ");
00496 
00497     /* Have to remove our actions manually. Its only automatic if the
00498      * actions name is installed into the plugin class. */
00499     group = gnc_main_window_get_action_group(window, PLUGIN_ACTIONS_NAME);
00500     if (group)
00501         gtk_ui_manager_remove_action_group(window->ui_merge, group);
00502 
00503     /* Note: This code does not clean up the per-callback data structures
00504      * that are created by the gnc_menu_additions_menu_setup_one()
00505      * function. Its not much memory and shouldn't be a problem. */
00506 
00507     LEAVE(" ");
00508 }
00509 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines