Files
evolution/shell/e-user-creatable-items-handler.c
Dan Winship 7f2badb024 Change this a lot. Now each component will maintain its own
* e-user-creatable-items-handler.c: Change this a lot. Now each
	component will maintain its own EUserCreatableItemsHandler and
	merge the button and menus in and out of the UI as its controls
	are activated and deactivated. (This lets the connector component
	display the correct default for the New button). Also, update to
	the Product Design Team's new organization (separating object
	types from folder types)

	* e-shell.c: Remove all creatable_items_handler references

	* e-shell-window.c (e_shell_window_new): Remove
	creatable_items_handler reference

	* Makefile.am: Move e-user-creatable-items-handler from evolution
	to libeshell, and make libeshell depend on libemiscwidgets (for
	the combo button)

	* Evolution-Component.idl: add another field to CreatableItemType
	so we can distinguish object types from folder types.

svn path=/trunk/; revision=25379
2004-04-09 15:46:00 +00:00

859 lines
20 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* e-user-creatable-items-handler.c
*
* Copyright (C) 2001-2004 Novell, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ettore Perazzoli <ettore@ximian.com>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-user-creatable-items-handler.h"
#include "e-shell-utils.h"
#include "Evolution.h"
#include "e-util/e-corba-utils.h"
#include "widgets/misc/e-combo-button.h"
#include <gal/util/e-util.h>
#include <bonobo/bonobo-ui-util.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-control.h>
#include <libgnome/gnome-i18n.h>
#include <gtk/gtkaccelgroup.h>
#include <gtk/gtkimage.h>
#include <gtk/gtkimagemenuitem.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkseparatormenuitem.h>
#include <gtk/gtksignal.h>
#include <gtk/gtktooltips.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define PARENT_TYPE G_TYPE_OBJECT
static GObjectClass *parent_class = NULL;
struct _Component {
char *id, *alias;
GNOME_Evolution_Component component;
GNOME_Evolution_CreatableItemTypeList *type_list;
};
typedef struct _Component Component;
/* Representation of a single menu item. */
struct _MenuItem {
const char *label;
char shortcut;
char *verb;
char *tooltip;
char *component;
GdkPixbuf *icon;
};
typedef struct _MenuItem MenuItem;
struct _EUserCreatableItemsHandlerPrivate {
/* This component's alias */
char *this_component;
/* The components that register user creatable items. */
GSList *components; /* Component */
/* The "New ..." menu items. */
GSList *objects; /* MenuItem */
GSList *folders; /* MenuItem */
/* The default item (the mailer's "message" item). To be used when the
component in the view we are in doesn't provide a default user
creatable type. This pointer always points to one of the menu items
in ->objects. */
const MenuItem *default_menu_item;
char *menu_xml;
GtkWidget *new_button, *new_menu;
BonoboControl *new_control;
GtkTooltips *tooltips;
GtkAccelGroup *accel_group;
};
enum {
PROP_THIS_COMPONENT = 1,
LAST_PROP
};
/* Component struct handling. */
static Component *
component_new (const char *id,
const char *component_alias,
GNOME_Evolution_Component component)
{
CORBA_Environment ev;
Component *new;
CORBA_exception_init (&ev);
new = g_new (Component, 1);
new->id = g_strdup (id);
new->alias = g_strdup (component_alias);
new->type_list = GNOME_Evolution_Component__get_userCreatableItems (component, &ev);
if (BONOBO_EX (&ev))
new->type_list = NULL;
new->component = component;
Bonobo_Unknown_ref (new->component, &ev);
if (ev._major != CORBA_NO_EXCEPTION)
new->type_list = NULL;
CORBA_exception_free (&ev);
return new;
}
static void
component_free (Component *component)
{
CORBA_Environment ev;
CORBA_exception_init (&ev);
Bonobo_Unknown_unref (component->component, &ev);
g_free (component->id);
g_free (component->alias);
if (component->type_list != NULL)
CORBA_free (component->type_list);
CORBA_exception_free (&ev);
g_free (component);
}
static const char *component_query =
"repo_ids.has ('IDL:GNOME/Evolution/Component:" BASE_VERSION "')";
static void
get_components_from_bonobo (EUserCreatableItemsHandler *handler)
{
Bonobo_ServerInfoList *info_list;
Bonobo_ActivationProperty *property;
CORBA_Environment ev;
char *iid, *alias;
GNOME_Evolution_Component corba_component;
Component *component;
int i;
CORBA_exception_init (&ev);
info_list = bonobo_activation_query (component_query, NULL, &ev);
if (BONOBO_EX (&ev)) {
char *ex_text = bonobo_exception_get_text (&ev);
g_warning ("Cannot query for components: %s\n", ex_text);
g_free (ex_text);
CORBA_exception_free (&ev);
return;
}
for (i = 0; i < info_list->_length; i++) {
iid = info_list->_buffer[i].iid;
corba_component = bonobo_activation_activate_from_id (iid, Bonobo_ACTIVATION_FLAG_EXISTING_ONLY, NULL, &ev);
if (BONOBO_EX (&ev)) {
CORBA_exception_free (&ev);
continue;
}
property = bonobo_server_info_prop_find (&info_list->_buffer[i],
"evolution:component_alias");
alias = property ? property->v._u.value_string : "unknown";
component = component_new (iid, alias, corba_component);
handler->priv->components = g_slist_prepend (handler->priv->components, component);
}
CORBA_free (info_list);
}
/* Helper functions. */
static gboolean
item_is_default (const MenuItem *item,
const char *component)
{
if (component == NULL)
return FALSE;
if (strcmp (item->component, component) == 0)
return TRUE;
else
return FALSE;
}
static char *
create_verb (EUserCreatableItemsHandler *handler,
int component_num, const char *type_id)
{
return g_strdup_printf ("EUserCreatableItemsHandler-%s:%d:%s",
handler->priv->this_component,
component_num, type_id);
}
/* Setting up menu items for the "File -> New" submenu and the "New" toolbar
button. */
static void
ensure_menu_items (EUserCreatableItemsHandler *handler)
{
EUserCreatableItemsHandlerPrivate *priv;
GSList *objects, *folders;
GSList *p;
int component_num;
const char *default_verb;
priv = handler->priv;
if (priv->objects != NULL)
return;
objects = folders = NULL;
component_num = 0;
default_verb = NULL;
for (p = priv->components; p != NULL; p = p->next) {
const Component *component;
int i;
component = (const Component *) p->data;
if (component->type_list != NULL) {
for (i = 0; i < component->type_list->_length; i ++) {
const GNOME_Evolution_CreatableItemType *corba_item;
MenuItem *item;
corba_item = (const GNOME_Evolution_CreatableItemType *) component->type_list->_buffer + i;
item = g_new (MenuItem, 1);
item->label = corba_item->menuDescription;
item->shortcut = corba_item->menuShortcut;
item->verb = create_verb (handler, component_num, corba_item->id);
item->tooltip = corba_item->tooltip;
item->component = g_strdup (component->alias);
if (strcmp (item->component, "mail") == 0
&& strcmp (corba_item->id, "message") == 0)
default_verb = item->verb;
if (corba_item->iconName == "") {
item->icon = NULL;
} else {
char *icon_path = e_shell_get_icon_path (corba_item->iconName, TRUE);
item->icon = gdk_pixbuf_new_from_file (icon_path, NULL);
g_free (icon_path);
}
if (corba_item->type == GNOME_Evolution_CREATABLE_OBJECT)
objects = g_slist_prepend (objects, item);
else
folders = g_slist_prepend (folders, item);
}
}
component_num ++;
}
priv->objects = g_slist_reverse (objects);
priv->folders = g_slist_reverse (folders);
priv->default_menu_item = NULL;
if (default_verb != NULL) {
for (p = priv->objects; p != NULL; p = p->next) {
const MenuItem *item;
item = (const MenuItem *) p->data;
if (strcmp (item->verb, default_verb) == 0)
priv->default_menu_item = item;
}
}
}
static void
free_menu_items (GSList *menu_items)
{
GSList *p;
if (menu_items == NULL)
return;
for (p = menu_items; p != NULL; p = p->next) {
MenuItem *item;
item = (MenuItem *) p->data;
g_free (item->verb);
if (item->icon != NULL)
g_object_unref (item->icon);
g_free (item->component);
g_free (item);
}
g_slist_free (menu_items);
}
static const MenuItem *
get_default_action_for_view (EUserCreatableItemsHandler *handler)
{
EUserCreatableItemsHandlerPrivate *priv;
const GSList *p;
priv = handler->priv;
for (p = priv->objects; p != NULL; p = p->next) {
const MenuItem *item;
item = (const MenuItem *) p->data;
if (item_is_default (item, priv->this_component))
return item;
}
return priv->default_menu_item;
}
/* Verb handling. */
static void
execute_verb (EUserCreatableItemsHandler *handler,
const char *verb_name)
{
EUserCreatableItemsHandlerPrivate *priv;
const Component *component;
int component_number;
const char *p;
const char *id;
GSList *component_list_item;
int i;
priv = handler->priv;
p = strchr (verb_name, ':');
g_assert (p != NULL);
component_number = atoi (p + 1);
p = strchr (p + 1, ':');
g_assert (p != NULL);
id = p + 1;
component_list_item = g_slist_nth (priv->components, component_number);
g_assert (component_list_item != NULL);
component = (const Component *) component_list_item->data;
if (component->type_list == NULL)
return;
for (i = 0; i < component->type_list->_length; i ++) {
if (strcmp (component->type_list->_buffer[i].id, id) == 0) {
CORBA_Environment ev;
CORBA_exception_init (&ev);
GNOME_Evolution_Component_requestCreateItem (component->component, id, &ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_warning ("Error in requestCreateItem -- %s", BONOBO_EX_REPOID (&ev));
CORBA_exception_free (&ev);
return;
}
}
}
static void
verb_fn (BonoboUIComponent *ui_component,
void *data,
const char *verb_name)
{
EUserCreatableItemsHandler *handler=
E_USER_CREATABLE_ITEMS_HANDLER (data);
execute_verb (handler, verb_name);
}
static void
add_verbs (EUserCreatableItemsHandler *handler,
BonoboUIComponent *ui_component)
{
EUserCreatableItemsHandlerPrivate *priv;
int component_num;
GSList *p;
priv = handler->priv;
component_num = 0;
for (p = priv->components; p != NULL; p = p->next) {
const Component *component;
int i;
component = (const Component *) p->data;
if (component->type_list != NULL) {
for (i = 0; i < component->type_list->_length; i ++) {
char *verb_name;
verb_name = create_verb (handler,
component_num,
component->type_list->_buffer[i].id);
bonobo_ui_component_add_verb (ui_component, verb_name, verb_fn, handler);
g_free (verb_name);
}
}
component_num ++;
}
}
/* Generic menu construction code */
static int
item_types_sort_func (const void *a,
const void *b)
{
const MenuItem *item_a;
const MenuItem *item_b;
const char *p1, *p2;
item_a = (const MenuItem *) a;
item_b = (const MenuItem *) b;
p1 = item_a->label;
p2 = item_b->label;
while (*p1 != '\0' && *p2 != '\0') {
if (*p1 == '_') {
p1 ++;
continue;
}
if (*p2 == '_') {
p2 ++;
continue;
}
if (toupper ((int) *p1) < toupper ((int) *p2))
return -1;
else if (toupper ((int) *p1) > toupper ((int) *p2))
return +1;
p1 ++, p2 ++;
}
if (*p1 == '\0') {
if (*p2 == '\0')
return 0;
else
return -1;
} else {
return +1;
}
}
typedef void (*EUserCreatableItemsHandlerMenuItemFunc) (EUserCreatableItemsHandler *, gpointer, MenuItem *, gboolean);
typedef void (*EUserCreatableItemsHandlerSeparatorFunc) (EUserCreatableItemsHandler *, gpointer, int);
static void
construct_menu (EUserCreatableItemsHandler *handler, gpointer menu,
EUserCreatableItemsHandlerMenuItemFunc menu_item_func,
EUserCreatableItemsHandlerSeparatorFunc separator_func)
{
EUserCreatableItemsHandlerPrivate *priv;
MenuItem *item;
GSList *p, *items;
gboolean first = TRUE;
priv = handler->priv;
/* First add the current component's creatable objects */
for (p = priv->objects; p != NULL; p = p->next) {
item = p->data;
if (item_is_default (item, priv->this_component)) {
menu_item_func (handler, menu, item, first);
first = FALSE;
}
}
/* Then its creatable folders */
for (p = priv->folders; p != NULL; p = p->next) {
item = p->data;
if (item_is_default (item, priv->this_component))
menu_item_func (handler, menu, item, FALSE);
}
/* Then a separator */
separator_func (handler, menu, 1);
/* Then the objects from other components. */
items = NULL;
for (p = priv->objects; p != NULL; p = p->next) {
item = p->data;
if (! item_is_default (item, priv->this_component))
items = g_slist_prepend (items, item);
}
items = g_slist_sort (items, item_types_sort_func);
for (p = items; p != NULL; p = p->next)
menu_item_func (handler, menu, p->data, FALSE);
g_slist_free (items);
/* Another separator */
separator_func (handler, menu, 2);
/* And finally the folders from other components */
items = NULL;
for (p = priv->folders; p != NULL; p = p->next) {
item = p->data;
if (! item_is_default (item, priv->this_component))
items = g_slist_prepend (items, item);
}
items = g_slist_sort (items, item_types_sort_func);
for (p = items; p != NULL; p = p->next)
menu_item_func (handler, menu, p->data, FALSE);
g_slist_free (items);
}
/* The XML description for "File -> New". */
static void
xml_menu_item_func (EUserCreatableItemsHandler *handler, gpointer menu,
MenuItem *item, gboolean first)
{
GString *xml = menu;
char *encoded_label;
char *encoded_tooltip;
encoded_label = bonobo_ui_util_encode_str (item->label);
g_string_append_printf (xml, "<menuitem name=\"New:%s\" verb=\"%s\" label=\"%s\"",
item->verb, item->verb, encoded_label);
if (first)
g_string_append_printf (xml, " accel=\"*Control*N\"");
else if (item->shortcut != '\0')
g_string_append_printf (xml, " accel=\"*Control**Shift*%c\"", item->shortcut);
if (item->icon != NULL) {
char *icon_xml;
icon_xml = bonobo_ui_util_pixbuf_to_xml (item->icon);
g_string_append_printf (xml, " pixtype=\"pixbuf\" pixname=\"%s\"", icon_xml);
g_free (icon_xml);
}
encoded_tooltip = bonobo_ui_util_encode_str (item->tooltip);
g_string_append_printf (xml, " tip=\"%s\"", encoded_tooltip);
g_string_append (xml, "/> ");
g_free (encoded_label);
g_free (encoded_tooltip);
}
static void
xml_separator_func (EUserCreatableItemsHandler *handler, gpointer menu, int nth)
{
GString *xml = menu;
g_string_append_printf (xml, "<separator f=\"\" name=\"EUserCreatableItemsHandlerSeparator%d\"/>", nth);
}
static void
create_menu_xml (EUserCreatableItemsHandler *handler)
{
GString *xml;
xml = g_string_new ("<placeholder name=\"NewMenu\">");
construct_menu (handler, xml, xml_menu_item_func, xml_separator_func);
g_string_append (xml, "</placeholder>");
handler->priv->menu_xml = xml->str;
g_string_free (xml, FALSE);
}
/* The GtkMenu for the toolbar button. */
static void
menuitem_activate (GtkMenuItem *item, gpointer data)
{
EUserCreatableItemsHandler *handler = data;
const char *verb;
verb = g_object_get_data (G_OBJECT (item), "EUserCreatableItemsHandler:verb");
execute_verb (handler, verb);
}
static void
default_activate (EComboButton *combo_button, gpointer data)
{
EUserCreatableItemsHandler *handler = data;
execute_verb (handler, handler->priv->default_menu_item->verb);
}
static void
gtk_menu_item_func (EUserCreatableItemsHandler *handler, gpointer menu,
MenuItem *item, gboolean first)
{
GtkWidget *menuitem, *icon;
menuitem = gtk_image_menu_item_new_with_mnemonic (item->label);
if (item->icon) {
icon = gtk_image_new_from_pixbuf (item->icon);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem),
icon);
}
if (first) {
gtk_widget_add_accelerator (menuitem, "activate",
handler->priv->accel_group,
'n', GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE);
} else {
gtk_widget_add_accelerator (menuitem, "activate",
handler->priv->accel_group,
item->shortcut,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
GTK_ACCEL_VISIBLE);
}
g_object_set_data (G_OBJECT (menuitem), "EUserCreatableItemsHandler:verb", item->verb);
g_signal_connect (menuitem, "activate",
G_CALLBACK (menuitem_activate), handler);
gtk_menu_shell_append (menu, menuitem);
}
static void
gtk_separator_func (EUserCreatableItemsHandler *handler, gpointer menu, int nth)
{
gtk_menu_shell_append (menu, gtk_separator_menu_item_new ());
}
static void
setup_toolbar_button (EUserCreatableItemsHandler *handler)
{
EUserCreatableItemsHandlerPrivate *priv;
const MenuItem *default_menu_item;
priv = handler->priv;
priv->new_button = e_combo_button_new ();
priv->new_menu = gtk_menu_new ();
priv->accel_group = gtk_accel_group_new ();
construct_menu (handler, priv->new_menu,
gtk_menu_item_func, gtk_separator_func);
gtk_widget_show_all (priv->new_menu);
e_combo_button_set_menu (E_COMBO_BUTTON (priv->new_button),
GTK_MENU (priv->new_menu));
e_combo_button_set_label (E_COMBO_BUTTON (priv->new_button), _("New"));
gtk_widget_show (priv->new_button);
g_signal_connect (priv->new_button, "activate_default",
G_CALLBACK (default_activate), handler);
priv->new_control = bonobo_control_new (priv->new_button);
default_menu_item = get_default_action_for_view (handler);
if (!default_menu_item) {
gtk_widget_set_sensitive (priv->new_button, FALSE);
return;
}
gtk_widget_set_sensitive (priv->new_button, TRUE);
e_combo_button_set_icon (E_COMBO_BUTTON (priv->new_button),
default_menu_item->icon);
priv->tooltips = gtk_tooltips_new ();
gtk_object_ref (GTK_OBJECT (priv->tooltips));
gtk_object_sink (GTK_OBJECT (priv->tooltips));
gtk_tooltips_set_tip (priv->tooltips, priv->new_button,
default_menu_item->tooltip, NULL);
}
/* GObject methods. */
static void
impl_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
EUserCreatableItemsHandler *handler =
E_USER_CREATABLE_ITEMS_HANDLER (object);
switch (prop_id) {
case PROP_THIS_COMPONENT:
handler->priv->this_component = g_value_dup_string (value);
get_components_from_bonobo (handler);
ensure_menu_items (handler);
break;
default:
break;
}
}
static void
impl_dispose (GObject *object)
{
EUserCreatableItemsHandler *handler;
EUserCreatableItemsHandlerPrivate *priv;
GSList *p;
handler = E_USER_CREATABLE_ITEMS_HANDLER (object);
priv = handler->priv;
for (p = priv->components; p != NULL; p = p->next)
component_free ((Component *) p->data);
g_slist_free (priv->components);
priv->components = NULL;
if (priv->new_control) {
bonobo_object_unref (priv->new_control);
priv->new_control = NULL;
}
if (priv->tooltips) {
g_object_unref (priv->tooltips);
priv->tooltips = NULL;
}
if (priv->accel_group) {
g_object_unref (priv->accel_group);
priv->accel_group = NULL;
}
(* G_OBJECT_CLASS (parent_class)->dispose) (object);
}
static void
impl_finalize (GObject *object)
{
EUserCreatableItemsHandler *handler;
EUserCreatableItemsHandlerPrivate *priv;
handler = E_USER_CREATABLE_ITEMS_HANDLER (object);
priv = handler->priv;
g_free (priv->this_component);
free_menu_items (priv->objects);
free_menu_items (priv->folders);
g_free (priv);
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
static void
class_init (GObjectClass *object_class)
{
parent_class = g_type_class_ref(PARENT_TYPE);
object_class->dispose = impl_dispose;
object_class->finalize = impl_finalize;
object_class->set_property = impl_set_property;
g_object_class_install_property (
object_class, PROP_THIS_COMPONENT,
g_param_spec_string ("this_component", "Component alias",
"The component_alias of this component",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}
static void
init (EUserCreatableItemsHandler *handler)
{
EUserCreatableItemsHandlerPrivate *priv;
priv = g_new0 (EUserCreatableItemsHandlerPrivate, 1);
handler->priv = priv;
}
EUserCreatableItemsHandler *
e_user_creatable_items_handler_new (const char *component_alias)
{
return g_object_new (e_user_creatable_items_handler_get_type (),
"this_component", component_alias,
NULL);
}
/**
* e_user_creatable_items_handler_activate:
* @handler: the #EUserCreatableItemsHandler
* @ui_component: the #BonoboUIComponent to attach to
*
* Set up the menus and toolbar items for @ui_component.
**/
void
e_user_creatable_items_handler_activate (EUserCreatableItemsHandler *handler,
BonoboUIComponent *ui_component)
{
EUserCreatableItemsHandlerPrivate *priv;
g_return_if_fail (E_IS_USER_CREATABLE_ITEMS_HANDLER (handler));
g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui_component));
priv = handler->priv;
if (!priv->menu_xml) {
create_menu_xml (handler);
setup_toolbar_button (handler);
add_verbs (handler, ui_component);
}
bonobo_ui_component_set (ui_component, "/menu/File/New",
priv->menu_xml, NULL);
bonobo_ui_component_object_set (ui_component,
"/Toolbar/NewComboButton",
BONOBO_OBJREF (priv->new_control),
NULL);
}
E_MAKE_TYPE (e_user_creatable_items_handler, "EUserCreatableItemsHandler", EUserCreatableItemsHandler, class_init, init, PARENT_TYPE)