diff options
Diffstat (limited to 'modules/calendar')
55 files changed, 22570 insertions, 0 deletions
diff --git a/modules/calendar/Makefile.am b/modules/calendar/Makefile.am new file mode 100644 index 0000000000..8422fb1e7f --- /dev/null +++ b/modules/calendar/Makefile.am @@ -0,0 +1,89 @@ +module_LTLIBRARIES = module-calendar.la + +module_calendar_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DG_LOG_DOMAIN=\"calendar-modules\" \ + -DEVOLUTION_BINDIR=\""$(bindir)"\" \ + -DEVOLUTION_PRIVLIBEXECDIR=\""$(PRIVLIBEXECDIR)"\" \ + -I$(top_srcdir) \ + -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(GTKHTML_CFLAGS) + +module_calendar_la_SOURCES = \ + evolution-module-calendar.c \ + e-calendar-preferences.c \ + e-calendar-preferences.h \ + e-cal-attachment-handler.c \ + e-cal-attachment-handler.h \ + e-cal-config-hook.c \ + e-cal-config-hook.h \ + e-cal-event-hook.c \ + e-cal-event-hook.h \ + e-cal-shell-backend.c \ + e-cal-shell-backend.h \ + e-cal-shell-content.c \ + e-cal-shell-content.h \ + e-cal-shell-migrate.c \ + e-cal-shell-migrate.h \ + e-cal-shell-sidebar.c \ + e-cal-shell-sidebar.h \ + e-cal-shell-view.c \ + e-cal-shell-view.h \ + e-cal-shell-view-actions.c \ + e-cal-shell-view-actions.h \ + e-cal-shell-view-memopad.c \ + e-cal-shell-view-private.c \ + e-cal-shell-view-private.h \ + e-cal-shell-view-taskpad.c \ + e-memo-shell-backend.c \ + e-memo-shell-backend.h \ + e-memo-shell-content.c \ + e-memo-shell-content.h \ + e-memo-shell-migrate.c \ + e-memo-shell-migrate.h \ + e-memo-shell-sidebar.c \ + e-memo-shell-sidebar.h \ + e-memo-shell-view.c \ + e-memo-shell-view.h \ + e-memo-shell-view-actions.c \ + e-memo-shell-view-actions.h \ + e-memo-shell-view-private.c \ + e-memo-shell-view-private.h \ + e-task-shell-backend.c \ + e-task-shell-backend.h \ + e-task-shell-content.c \ + e-task-shell-content.h \ + e-task-shell-migrate.c \ + e-task-shell-migrate.h \ + e-task-shell-sidebar.c \ + e-task-shell-sidebar.h \ + e-task-shell-view.c \ + e-task-shell-view.h \ + e-task-shell-view-actions.c \ + e-task-shell-view-actions.h \ + e-task-shell-view-private.c \ + e-task-shell-view-private.h + +module_calendar_la_LIBADD = \ + $(top_builddir)/shell/libevolution-shell.la \ + $(top_builddir)/calendar/gui/libevolution-calendar.la \ + $(top_builddir)/calendar/importers/libevolution-calendar-importers.la \ + $(top_builddir)/mail/libevolution-mail.la \ + $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ + $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ + $(top_builddir)/e-util/libevolution-util.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(GTKHTML_LIBS) + +module_calendar_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +ui_DATA = e-calendar-preferences.ui + +EXTRA_DIST = \ + $(ui_DATA) + +-include $(top_srcdir)/git.mk diff --git a/modules/calendar/e-cal-attachment-handler.c b/modules/calendar/e-cal-attachment-handler.c new file mode 100644 index 0000000000..8613fbf169 --- /dev/null +++ b/modules/calendar/e-cal-attachment-handler.c @@ -0,0 +1,577 @@ +/* + * e-cal-attachment-handler.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-attachment-handler.h" + +#include <glib/gi18n.h> +#include <libical/ical.h> +#include <camel/camel.h> +#include <libecal/libecal.h> + +#include <shell/e-shell.h> + +#define E_CAL_ATTACHMENT_HANDLER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerPrivate)) + +typedef struct _ImportContext ImportContext; + +struct _ECalAttachmentHandlerPrivate { + gint placeholder; +}; + +struct _ImportContext { + ECalClient *client; + icalcomponent *component; + ECalClientSourceType source_type; +}; + +static gpointer parent_class; +static GType cal_attachment_handler_type; + +static const gchar *ui = +"<ui>" +" <popup name='context'>" +" <placeholder name='custom-actions'>" +" <menuitem action='import-to-calendar'/>" +" <menuitem action='import-to-tasks'/>" +" </placeholder>" +" </popup>" +"</ui>"; + +static icalcomponent * +attachment_handler_get_component (EAttachment *attachment) +{ + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + GByteArray *buffer; + icalcomponent *component; + const gchar *key = "__icalcomponent__"; + + component = g_object_get_data (G_OBJECT (attachment), key); + if (component != NULL) + return component; + + if (e_attachment_get_loading (attachment) || + e_attachment_get_saving (attachment)) + return NULL; + + mime_part = e_attachment_ref_mime_part (attachment); + if (mime_part == NULL) + return NULL; + + buffer = g_byte_array_new (); + stream = camel_stream_mem_new (); + camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer); + wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + camel_data_wrapper_decode_to_stream_sync (wrapper, stream, NULL, NULL); + g_object_unref (stream); + + g_object_unref (mime_part); + + if (buffer->len > 0) { + const gchar *str; + + /* ensure string being null-terminated */ + g_byte_array_append (buffer, (const guint8 *) "", 1); + + str = (const gchar *) buffer->data; + while (*str && g_ascii_isspace (*str)) + str++; + + if (g_ascii_strncasecmp (str, "BEGIN:", 6) == 0) + component = e_cal_util_parse_ics_string (str); + } + + g_byte_array_free (buffer, TRUE); + + if (component == NULL) + return NULL; + + g_object_set_data_full ( + G_OBJECT (attachment), key, component, + (GDestroyNotify) icalcomponent_free); + + return component; +} + +static gboolean +attachment_handler_update_objects (ECalClient *client, + icalcomponent *component) +{ + icalcomponent_kind kind; + icalcomponent *vcalendar; + gboolean success; + GError *error = NULL; + + kind = icalcomponent_isa (component); + + switch (kind) { + case ICAL_VTODO_COMPONENT: + case ICAL_VEVENT_COMPONENT: + vcalendar = e_cal_util_new_top_level (); + if (icalcomponent_get_method (component) == ICAL_METHOD_CANCEL) + icalcomponent_set_method (vcalendar, ICAL_METHOD_CANCEL); + else + icalcomponent_set_method (vcalendar, ICAL_METHOD_PUBLISH); + icalcomponent_add_component ( + vcalendar, icalcomponent_new_clone (component)); + break; + + case ICAL_VCALENDAR_COMPONENT: + vcalendar = icalcomponent_new_clone (component); + if (!icalcomponent_get_first_property (vcalendar, ICAL_METHOD_PROPERTY)) + icalcomponent_set_method (vcalendar, ICAL_METHOD_PUBLISH); + break; + + default: + return FALSE; + } + + success = e_cal_client_receive_objects_sync ( + client, vcalendar, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to receive objects: %s", + G_STRFUNC, error->message); + g_error_free (error); + } + + icalcomponent_free (vcalendar); + + return success; +} + +static void +attachment_handler_import_event (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EAttachment *attachment = user_data; + EClient *client; + icalcomponent *component; + icalcomponent *subcomponent; + icalcompiter iter; + GError *error = NULL; + + client = e_cal_client_connect_finish (result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (error != NULL) { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_object_unref (attachment); + g_error_free (error); + return; + } + + component = attachment_handler_get_component (attachment); + g_return_if_fail (component != NULL); + + iter = icalcomponent_begin_component (component, ICAL_ANY_COMPONENT); + + while ((subcomponent = icalcompiter_deref (&iter)) != NULL) { + icalcomponent_kind kind; + + kind = icalcomponent_isa (subcomponent); + icalcompiter_next (&iter); + + if (kind == ICAL_VEVENT_COMPONENT) + continue; + + if (kind == ICAL_VTIMEZONE_COMPONENT) + continue; + + icalcomponent_remove_component (component, subcomponent); + icalcomponent_free (subcomponent); + } + + /* XXX Do something with the return value. */ + attachment_handler_update_objects (E_CAL_CLIENT (client), component); + + g_object_unref (attachment); + g_object_unref (client); +} + +static void +attachment_handler_import_todo (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EAttachment *attachment = user_data; + EClient *client; + icalcomponent *component; + icalcomponent *subcomponent; + icalcompiter iter; + GError *error = NULL; + + client = e_cal_client_connect_finish (result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (error != NULL) { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_object_unref (attachment); + g_error_free (error); + return; + } + + component = attachment_handler_get_component (attachment); + g_return_if_fail (component != NULL); + + iter = icalcomponent_begin_component (component, ICAL_ANY_COMPONENT); + + while ((subcomponent = icalcompiter_deref (&iter)) != NULL) { + icalcomponent_kind kind; + + kind = icalcomponent_isa (subcomponent); + icalcompiter_next (&iter); + + if (kind == ICAL_VTODO_COMPONENT) + continue; + + if (kind == ICAL_VTIMEZONE_COMPONENT) + continue; + + icalcomponent_remove_component (component, subcomponent); + icalcomponent_free (subcomponent); + } + + /* XXX Do something with the return value. */ + attachment_handler_update_objects (E_CAL_CLIENT (client), component); + + g_object_unref (attachment); + g_object_unref (client); +} + +static void +attachment_handler_row_activated_cb (GtkDialog *dialog) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_OK); +} + +static void +attachment_handler_run_dialog (GtkWindow *parent, + EAttachment *attachment, + ECalClientSourceType source_type, + const gchar *title) +{ + EShell *shell; + GtkWidget *dialog; + GtkWidget *container; + GtkWidget *widget; + ESourceRegistry *registry; + ESourceSelector *selector; + ESource *source; + const gchar *extension_name; + icalcomponent *component; + + switch (source_type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + default: + g_return_if_reached (); + } + + component = attachment_handler_get_component (attachment); + g_return_if_fail (component != NULL); + + dialog = gtk_dialog_new_with_buttons ( + title, parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); + + widget = gtk_button_new_with_mnemonic (_("I_mport")); + gtk_button_set_image ( + GTK_BUTTON (widget), gtk_image_new_from_icon_name ( + "stock_mail-import", GTK_ICON_SIZE_MENU)); + gtk_dialog_add_action_widget ( + GTK_DIALOG (dialog), widget, GTK_RESPONSE_OK); + gtk_widget_show (widget); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 400); + + container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + shell = e_shell_get_default (); + registry = e_shell_get_registry (shell); + widget = e_source_selector_new (registry, extension_name); + selector = E_SOURCE_SELECTOR (widget); + e_source_selector_set_show_toggles (selector, FALSE); + gtk_container_add (GTK_CONTAINER (container), widget); + gtk_widget_show (widget); + + g_signal_connect_swapped ( + widget, "row-activated", + G_CALLBACK (attachment_handler_row_activated_cb), dialog); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) + goto exit; + + source = e_source_selector_ref_primary_selection (selector); + if (source == NULL) + goto exit; + + switch (source_type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + e_cal_client_connect ( + source, source_type, NULL, + attachment_handler_import_event, + g_object_ref (attachment)); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + e_cal_client_connect ( + source, source_type, NULL, + attachment_handler_import_todo, + g_object_ref (attachment)); + break; + default: + break; + } + + g_object_unref (source); + + exit: + gtk_widget_destroy (dialog); +} + +static void +attachment_handler_import_to_calendar (GtkAction *action, + EAttachmentHandler *handler) +{ + EAttachment *attachment; + EAttachmentView *view; + GList *selected; + gpointer parent; + + view = e_attachment_handler_get_view (handler); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + selected = e_attachment_view_get_selected_attachments (view); + g_return_if_fail (g_list_length (selected) == 1); + attachment = E_ATTACHMENT (selected->data); + + attachment_handler_run_dialog ( + parent, attachment, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + _("Select a Calendar")); + + g_object_unref (attachment); + g_list_free (selected); +} + +static void +attachment_handler_import_to_tasks (GtkAction *action, + EAttachmentHandler *handler) +{ + EAttachment *attachment; + EAttachmentView *view; + GList *selected; + gpointer parent; + + view = e_attachment_handler_get_view (handler); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + selected = e_attachment_view_get_selected_attachments (view); + g_return_if_fail (g_list_length (selected) == 1); + attachment = E_ATTACHMENT (selected->data); + + attachment_handler_run_dialog ( + parent, attachment, + E_CAL_CLIENT_SOURCE_TYPE_TASKS, + _("Select a Task List")); + + g_object_unref (attachment); + g_list_free (selected); +} + +static GtkActionEntry standard_entries[] = { + + { "import-to-calendar", + "stock_mail-import", + N_("I_mport to Calendar"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (attachment_handler_import_to_calendar) }, + + { "import-to-tasks", + "stock_mail-import", + N_("I_mport to Tasks"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (attachment_handler_import_to_tasks) } +}; + +static void +cal_attachment_handler_update_actions (EAttachmentView *view) +{ + EAttachment *attachment; + GtkAction *action; + GList *selected; + icalcomponent *component; + icalcomponent *subcomponent; + icalcomponent_kind kind; + gboolean is_vevent = FALSE; + gboolean is_vtodo = FALSE; + + selected = e_attachment_view_get_selected_attachments (view); + + if (g_list_length (selected) != 1) + goto exit; + + attachment = E_ATTACHMENT (selected->data); + component = attachment_handler_get_component (attachment); + + if (component == NULL) + goto exit; + + subcomponent = icalcomponent_get_inner (component); + + if (subcomponent == NULL) + goto exit; + + kind = icalcomponent_isa (subcomponent); + is_vevent = (kind == ICAL_VEVENT_COMPONENT); + is_vtodo = (kind == ICAL_VTODO_COMPONENT); + +exit: + action = e_attachment_view_get_action (view, "import-to-calendar"); + gtk_action_set_visible (action, is_vevent); + + action = e_attachment_view_get_action (view, "import-to-tasks"); + gtk_action_set_visible (action, is_vtodo); + + g_list_foreach (selected, (GFunc) g_object_unref, NULL); + g_list_free (selected); +} + +static void +cal_attachment_handler_constructed (GObject *object) +{ + EAttachmentHandler *handler; + EAttachmentView *view; + GtkActionGroup *action_group; + GtkUIManager *ui_manager; + GError *error = NULL; + + handler = E_ATTACHMENT_HANDLER (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (parent_class)->constructed (object); + + view = e_attachment_handler_get_view (handler); + + action_group = e_attachment_view_add_action_group (view, "calendar"); + gtk_action_group_add_actions ( + action_group, standard_entries, + G_N_ELEMENTS (standard_entries), handler); + + ui_manager = e_attachment_view_get_ui_manager (view); + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); + + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_signal_connect ( + view, "update_actions", + G_CALLBACK (cal_attachment_handler_update_actions), + NULL); +} + +static void +cal_attachment_handler_class_init (ECalAttachmentHandlerClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (ECalAttachmentHandlerPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = cal_attachment_handler_constructed; +} + +static void +cal_attachment_handler_init (ECalAttachmentHandler *handler) +{ + handler->priv = E_CAL_ATTACHMENT_HANDLER_GET_PRIVATE (handler); +} + +GType +e_cal_attachment_handler_get_type (void) +{ + return cal_attachment_handler_type; +} + +void +e_cal_attachment_handler_register_type (GTypeModule *type_module) +{ + static const GTypeInfo type_info = { + sizeof (ECalAttachmentHandlerClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) cal_attachment_handler_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (ECalAttachmentHandler), + 0, /* n_preallocs */ + (GInstanceInitFunc) cal_attachment_handler_init, + NULL /* value_table */ + }; + + cal_attachment_handler_type = g_type_module_register_type ( + type_module, E_TYPE_ATTACHMENT_HANDLER, + "ECalAttachmentHandler", &type_info, 0); +} diff --git a/modules/calendar/e-cal-attachment-handler.h b/modules/calendar/e-cal-attachment-handler.h new file mode 100644 index 0000000000..549199ec35 --- /dev/null +++ b/modules/calendar/e-cal-attachment-handler.h @@ -0,0 +1,67 @@ +/* + * e-cal-attachment-handler.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_ATTACHMENT_HANDLER_H +#define E_CAL_ATTACHMENT_HANDLER_H + +#include <e-util/e-util.h> + +/* Standard GObject macros */ +#define E_TYPE_CAL_ATTACHMENT_HANDLER \ + (e_cal_attachment_handler_get_type ()) +#define E_CAL_ATTACHMENT_HANDLER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandler)) +#define E_CAL_ATTACHMENT_HANDLER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerClass)) +#define E_IS_CAL_ATTACHMENT_HANDLER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER)) +#define E_IS_CAL_ATTACHMENT_HANDLER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_ATTACHMENT_HANDLER)) +#define E_CAL_ATTACHMENT_HANDLER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_ATTACHMENT_HANDLER, ECalAttachmentHandlerClass)) + +G_BEGIN_DECLS + +typedef struct _ECalAttachmentHandler ECalAttachmentHandler; +typedef struct _ECalAttachmentHandlerClass ECalAttachmentHandlerClass; +typedef struct _ECalAttachmentHandlerPrivate ECalAttachmentHandlerPrivate; + +struct _ECalAttachmentHandler { + EAttachmentHandler parent; + ECalAttachmentHandlerPrivate *priv; +}; + +struct _ECalAttachmentHandlerClass { + EAttachmentHandlerClass parent_class; +}; + +GType e_cal_attachment_handler_get_type (void); +void e_cal_attachment_handler_register_type + (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_CAL_ATTACHMENT_HANDLER_H */ diff --git a/modules/calendar/e-cal-config-hook.c b/modules/calendar/e-cal-config-hook.c new file mode 100644 index 0000000000..57fb8b8cc5 --- /dev/null +++ b/modules/calendar/e-cal-config-hook.c @@ -0,0 +1,75 @@ +/* + * e-cal-config-hook.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-config-hook.h" + +#include "calendar/gui/e-cal-config.h" + +static const EConfigHookTargetMask no_masks[] = { + { NULL } +}; + +static const EConfigHookTargetMap targets[] = { + { "source", EC_CONFIG_TARGET_SOURCE, no_masks }, + { "prefs", EC_CONFIG_TARGET_PREFS, no_masks }, + { NULL } +}; + +static void +cal_config_hook_class_init (EConfigHookClass *class) +{ + EPluginHookClass *plugin_hook_class; + gint ii; + + plugin_hook_class = E_PLUGIN_HOOK_CLASS (class); + plugin_hook_class->id = "org.gnome.evolution.calendar.config:1.0"; + + class->config_class = g_type_class_ref (e_cal_config_get_type ()); + + for (ii = 0; targets[ii].type != NULL; ii++) + e_config_hook_class_add_target_map ( + (EConfigHookClass *) class, &targets[ii]); +} + +void +e_cal_config_hook_register_type (GTypeModule *type_module) +{ + const GTypeInfo type_info = { + sizeof (EConfigHookClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) cal_config_hook_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EConfigHook), + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL, + NULL /* value_table */ + }; + + g_type_module_register_type ( + type_module, e_config_hook_get_type (), + "ECalConfigHook", &type_info, 0); +} diff --git a/modules/calendar/e-cal-config-hook.h b/modules/calendar/e-cal-config-hook.h new file mode 100644 index 0000000000..a22ec56bbc --- /dev/null +++ b/modules/calendar/e-cal-config-hook.h @@ -0,0 +1,33 @@ +/* + * e-cal-config-hook.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_CONFIG_HOOK_H +#define E_CAL_CONFIG_HOOK_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_cal_config_hook_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_CAL_CONFIG_HOOK_H */ diff --git a/modules/calendar/e-cal-event-hook.c b/modules/calendar/e-cal-event-hook.c new file mode 100644 index 0000000000..9f38e7005b --- /dev/null +++ b/modules/calendar/e-cal-event-hook.c @@ -0,0 +1,75 @@ +/* + * e-cal-event-hook.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-event-hook.h" + +#include "calendar/gui/e-cal-event.h" + +static const EEventHookTargetMask masks[] = { + { "migration", E_CAL_EVENT_MODULE_MIGRATION }, + { NULL } +}; + +static const EEventHookTargetMap targets[] = { + { "module", E_CAL_EVENT_TARGET_BACKEND, masks }, + { NULL } +}; + +static void +cal_event_hook_class_init (EEventHookClass *class) +{ + EPluginHookClass *plugin_hook_class; + gint ii; + + plugin_hook_class = E_PLUGIN_HOOK_CLASS (class); + plugin_hook_class->id = "org.gnome.evolution.calendar.events:1.0"; + + class->event = (EEvent *) e_cal_event_peek (); + + for (ii = 0; targets[ii].type != NULL; ii++) + e_event_hook_class_add_target_map ( + (EEventHookClass *) class, &targets[ii]); +} + +void +e_cal_event_hook_register_type (GTypeModule *type_module) +{ + const GTypeInfo type_info = { + sizeof (EEventHookClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) cal_event_hook_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EEventHook), + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL, + NULL /* value_table */ + }; + + g_type_module_register_type ( + type_module, e_event_hook_get_type (), + "ECalEventHook", &type_info, 0); +} diff --git a/modules/calendar/e-cal-event-hook.h b/modules/calendar/e-cal-event-hook.h new file mode 100644 index 0000000000..9dde31f900 --- /dev/null +++ b/modules/calendar/e-cal-event-hook.h @@ -0,0 +1,33 @@ +/* + * e-cal-event-hook.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_EVENT_HOOK_H +#define E_CAL_EVENT_HOOK_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +void e_cal_event_hook_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_CAL_EVENT_HOOK_H */ diff --git a/modules/calendar/e-cal-shell-backend.c b/modules/calendar/e-cal-shell-backend.c new file mode 100644 index 0000000000..6a9bc1aac8 --- /dev/null +++ b/modules/calendar/e-cal-shell-backend.c @@ -0,0 +1,858 @@ +/* + * e-cal-shell-backend.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-backend.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libecal/libecal.h> + +#include "shell/e-shell.h" +#include "shell/e-shell-backend.h" +#include "shell/e-shell-window.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/dialogs/event-editor.h" +#include "calendar/gui/e-calendar-view.h" +#include "calendar/gui/gnome-cal.h" +#include "calendar/importers/evolution-calendar-importer.h" + +#include "e-cal-shell-content.h" +#include "e-cal-shell-migrate.h" +#include "e-cal-shell-sidebar.h" +#include "e-cal-shell-view.h" + +#include "e-calendar-preferences.h" + +#define E_CAL_SHELL_BACKEND_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendPrivate)) + +struct _ECalShellBackendPrivate { + gint placeholder; +}; + +G_DEFINE_DYNAMIC_TYPE ( + ECalShellBackend, + e_cal_shell_backend, + E_TYPE_SHELL_BACKEND) + +static void +cal_shell_backend_new_event (ECalClient *cal_client, + EShell *shell, + CompEditorFlags flags, + gboolean all_day) +{ + ECalComponent *comp; + GSettings *settings; + CompEditor *editor; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + editor = event_editor_new (cal_client, shell, flags); + comp = cal_comp_event_new_with_current_time ( + cal_client, all_day, + g_settings_get_boolean (settings, "use-default-reminder"), + g_settings_get_int (settings, "default-reminder-interval"), + g_settings_get_enum (settings, "default-reminder-units")); + e_cal_component_commit_sequence (comp); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); + + g_object_unref (settings); +} + +static void +cal_shell_backend_event_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + gboolean all_day = FALSE; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + flags |= COMP_EDITOR_USER_ORG; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + cal_shell_backend_new_event ( + E_CAL_CLIENT (client), shell, flags, all_day); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +cal_shell_backend_event_all_day_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + gboolean all_day = TRUE; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + flags |= COMP_EDITOR_USER_ORG; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + cal_shell_backend_new_event ( + E_CAL_CLIENT (client), shell, flags, all_day); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +cal_shell_backend_event_meeting_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + gboolean all_day = FALSE; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + flags |= COMP_EDITOR_USER_ORG; + flags |= COMP_EDITOR_MEETING; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + cal_shell_backend_new_event ( + E_CAL_CLIENT (client), shell, flags, all_day); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +action_event_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + EShellView *shell_view; + EShellBackend *shell_backend; + ESource *source; + ESourceRegistry *registry; + EClientCache *client_cache; + const gchar *action_name; + + shell = e_shell_window_get_shell (shell_window); + client_cache = e_shell_get_client_cache (shell); + + action_name = gtk_action_get_name (action); + + /* With a 'calendar' active shell view pass the new appointment + * request to it, thus the event will inherit selected time from + * the view. */ + shell_view = e_shell_window_peek_shell_view (shell_window, "calendar"); + if (shell_view != NULL) { + EShellWindow *shell_window; + EShellContent *shell_content; + GnomeCalendar *gcal; + GnomeCalendarViewType view_type; + ECalendarView *view; + + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + e_shell_backend_set_prefer_new_item ( + shell_backend, action_name); + + /* This forces the shell window to update the "New" toolbar + * button menu, and the toolbar button will then update its + * button image to reflect the "preferred new item" we just + * set on the shell backend. */ + g_object_notify (G_OBJECT (shell_window), "active-view"); + + gcal = e_cal_shell_content_get_calendar ( + E_CAL_SHELL_CONTENT (shell_content)); + + view_type = gnome_calendar_get_view (gcal); + view = gnome_calendar_get_calendar_view (gcal, view_type); + + if (view != NULL) { + e_calendar_view_new_appointment_full ( + view, + g_str_equal (action_name, "event-all-day-new"), + g_str_equal (action_name, "event-meeting-new"), + TRUE); + + return; + } + } + + /* This callback is used for both appointments and meetings. */ + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_default_calendar (registry); + + shell_backend = e_shell_get_backend_by_name (shell, "calendar"); + e_shell_backend_set_prefer_new_item (shell_backend, action_name); + + /* Use a callback function appropriate for the action. */ + if (strcmp (action_name, "event-all-day-new") == 0) + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_CALENDAR, + NULL, + cal_shell_backend_event_all_day_new_cb, + g_object_ref (shell)); + else if (strcmp (action_name, "event-meeting-new") == 0) + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_CALENDAR, + NULL, + cal_shell_backend_event_meeting_new_cb, + g_object_ref (shell)); + else + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_CALENDAR, + NULL, + cal_shell_backend_event_new_cb, + g_object_ref (shell)); + + g_object_unref (source); +} + +static void +action_calendar_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Calendar")); + + gtk_widget_show (dialog); +} + +static GtkActionEntry item_entries[] = { + + { "event-new", + "appointment-new", + NC_("New", "_Appointment"), + "<Shift><Control>a", + N_("Create a new appointment"), + G_CALLBACK (action_event_new_cb) }, + + { "event-all-day-new", + "stock_new-24h-appointment", + NC_("New", "All Day A_ppointment"), + NULL, + N_("Create a new all-day appointment"), + G_CALLBACK (action_event_new_cb) }, + + { "event-meeting-new", + "stock_new-meeting", + NC_("New", "M_eeting"), + "<Shift><Control>e", + N_("Create a new meeting request"), + G_CALLBACK (action_event_new_cb) } +}; + +static GtkActionEntry source_entries[] = { + + { "calendar-new", + "x-office-calendar", + NC_("New", "Cale_ndar"), + NULL, + N_("Create a new calendar"), + G_CALLBACK (action_calendar_new_cb) } +}; + +static void +cal_shell_backend_init_importers (void) +{ + EImportClass *import_class; + EImportImporter *importer; + + import_class = g_type_class_ref (e_import_get_type ()); + + importer = gnome_calendar_importer_peek (); + e_import_class_add_importer (import_class, importer, NULL, NULL); + + importer = ical_importer_peek (); + e_import_class_add_importer (import_class, importer, NULL, NULL); + + importer = vcal_importer_peek (); + e_import_class_add_importer (import_class, importer, NULL, NULL); +} + +static void +populate_g_date (GDate *date, + time_t utc_time, + icaltimezone *zone) +{ + struct icaltimetype icaltm; + + g_return_if_fail (date != NULL); + + if ((gint) utc_time == -1) + return; + + if (zone) + icaltm = icaltime_from_timet_with_zone (utc_time, FALSE, zone); + else + icaltm = icaltime_from_timet (utc_time, FALSE); + + if (icaltime_is_null_time (icaltm) || + !icaltime_is_valid_time (icaltm)) + return; + + g_date_set_dmy (date, icaltm.day, icaltm.month, icaltm.year); +} + +static gboolean +cal_shell_backend_handle_uri_cb (EShellBackend *shell_backend, + const gchar *uri) +{ + EShell *shell; + CompEditor *editor; + CompEditorFlags flags = 0; + EClient *client; + EClientCache *client_cache; + ECalComponent *comp; + ESource *source; + ESourceRegistry *registry; + GSettings *settings; + SoupURI *soup_uri; + icalcomponent *icalcomp; + icalproperty *icalprop; + const gchar *cp; + gchar *source_uid = NULL; + gchar *comp_uid = NULL; + gchar *comp_rid = NULL; + GDate start_date; + GDate end_date; + icaltimezone *zone; + gboolean handled = FALSE; + GError *error = NULL; + + shell = e_shell_backend_get_shell (shell_backend); + client_cache = e_shell_get_client_cache (shell); + + if (strncmp (uri, "calendar:", 9) != 0) + return FALSE; + + soup_uri = soup_uri_new (uri); + + if (soup_uri == NULL) + return FALSE; + + cp = soup_uri_get_query (soup_uri); + if (cp == NULL) + goto exit; + + g_date_clear (&start_date, 1); + g_date_clear (&end_date, 1); + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + if (g_settings_get_boolean (settings, "use-system-timezone")) + zone = e_cal_util_get_system_timezone (); + else { + gchar *location; + + location = g_settings_get_string (settings, "timezone"); + + if (location != NULL) { + zone = icaltimezone_get_builtin_timezone (location); + g_free (location); + } + } + + if (zone == NULL) + zone = icaltimezone_get_utc_timezone (); + + g_object_unref (settings); + + while (*cp != '\0') { + gchar *header; + gchar *content; + gsize header_len; + gsize content_len; + + header_len = strcspn (cp, "=&"); + + /* It it's malformed, give up. */ + if (cp[header_len] != '=') + break; + + header = (gchar *) cp; + header[header_len] = '\0'; + cp += header_len + 1; + + content_len = strcspn (cp, "&"); + + content = g_strndup (cp, content_len); + if (g_ascii_strcasecmp (header, "startdate") == 0) + populate_g_date (&start_date, time_from_isodate (content), zone); + else if (g_ascii_strcasecmp (header, "enddate") == 0) + populate_g_date (&end_date, time_from_isodate (content), zone); + else if (g_ascii_strcasecmp (header, "source-uid") == 0) + source_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-uid") == 0) + comp_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-rid") == 0) + comp_rid = g_strdup (content); + g_free (content); + + cp += content_len; + if (*cp == '&') { + cp++; + if (strncmp (cp, "amp;", 4) == 0) + cp += 4; + } + } + + /* This is primarily for launching Evolution + * from the calendar in the clock applet. */ + if (g_date_valid (&start_date)) { + if (g_date_valid (&end_date)) + e_cal_shell_backend_open_date_range ( + E_CAL_SHELL_BACKEND (shell_backend), + &start_date, &end_date); + else + e_cal_shell_backend_open_date_range ( + E_CAL_SHELL_BACKEND (shell_backend), + &start_date, NULL); + handled = TRUE; + goto exit; + } + + if (source_uid == NULL || comp_uid == NULL) + goto exit; + + /* URI is valid, so consider it handled. Whether + * we successfully open it is another matter... */ + handled = TRUE; + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_source (registry, source_uid); + if (source == NULL) { + g_printerr ("No source for UID '%s'\n", source_uid); + goto exit; + } + + client = e_client_cache_get_client_sync ( + client_cache, source, + E_SOURCE_EXTENSION_CALENDAR, + NULL, &error); + + /* Sanity check. */ + g_return_val_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL)), FALSE); + + if (error != NULL) { + g_warning ( + "%s: Failed to create/open client '%s': %s", + G_STRFUNC, e_source_get_display_name (source), + error->message); + g_object_unref (source); + g_error_free (error); + goto exit; + } + + g_object_unref (source); + source = NULL; + + /* XXX Copied from e_cal_shell_view_open_event(). + * Clearly a new utility function is needed. */ + + editor = comp_editor_find_instance (comp_uid); + + if (editor != NULL) + goto present; + + e_cal_client_get_object_sync ( + E_CAL_CLIENT (client),comp_uid, + comp_rid, &icalcomp, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to get object from client: %s", + G_STRFUNC, error->message); + g_object_unref (client); + g_error_free (error); + goto exit; + } + + comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { + g_warning ("%s: Failed to set icalcomp to comp\n", G_STRFUNC); + icalcomponent_free (icalcomp); + icalcomp = NULL; + } + + icalprop = icalcomp ? icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY) : NULL; + if (icalprop != NULL) + flags |= COMP_EDITOR_MEETING; + + if (itip_organizer_is_user (registry, comp, E_CAL_CLIENT (client))) + flags |= COMP_EDITOR_USER_ORG; + + if (itip_sentby_is_user (registry, comp, E_CAL_CLIENT (client))) + flags |= COMP_EDITOR_USER_ORG; + + if (!e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + + editor = event_editor_new (E_CAL_CLIENT (client), shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +present: + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (client); + +exit: + g_free (source_uid); + g_free (comp_uid); + g_free (comp_rid); + + soup_uri_free (soup_uri); + + return handled; +} + +static void +cal_shell_backend_window_added_cb (EShellBackend *shell_backend, + GtkWindow *window) +{ + const gchar *backend_name; + + if (!E_IS_SHELL_WINDOW (window)) + return; + + backend_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name; + + e_shell_window_register_new_item_actions ( + E_SHELL_WINDOW (window), backend_name, + item_entries, G_N_ELEMENTS (item_entries)); + + e_shell_window_register_new_source_actions ( + E_SHELL_WINDOW (window), backend_name, + source_entries, G_N_ELEMENTS (source_entries)); +} + +static void +ensure_alarm_notify_is_running (void) +{ + const gchar *base_dir; + gchar *filename; + + #ifdef G_OS_WIN32 + base_dir = EVOLUTION_BINDIR; + #else + base_dir = EVOLUTION_PRIVLIBEXECDIR; + #endif + + filename = g_build_filename (base_dir, "evolution-alarm-notify", NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_EXECUTABLE)) { + gchar *argv[2]; + GError *error = NULL; + + argv[0] = filename; + argv[1] = NULL; + + g_spawn_async ( + base_dir, argv, NULL, 0, NULL, NULL, NULL, &error); + + if (error != NULL) { + g_message ( + "Failed to start '%s': %s", + filename, error->message); + g_error_free (error); + } + } + + g_free (filename); +} + +static void +cal_shell_backend_use_system_timezone_changed_cb (GSettings *settings, + const gchar *key) +{ + g_signal_emit_by_name (settings, "changed::timezone", timezone); +} + +static void +cal_shell_backend_constructed (GObject *object) +{ + EShell *shell; + EShellBackend *shell_backend; + GtkWidget *preferences_window; + GSettings *settings; + + shell_backend = E_SHELL_BACKEND (object); + shell = e_shell_backend_get_shell (shell_backend); + + g_signal_connect_swapped ( + shell, "handle-uri", + G_CALLBACK (cal_shell_backend_handle_uri_cb), + shell_backend); + + g_signal_connect_swapped ( + shell, "window-added", + G_CALLBACK (cal_shell_backend_window_added_cb), + shell_backend); + + cal_shell_backend_init_importers (); + + /* Setup preference widget factories */ + preferences_window = e_shell_get_preferences_window (shell); + + e_preferences_window_add_page ( + E_PREFERENCES_WINDOW (preferences_window), + "calendar-and-tasks", + "preferences-calendar-and-tasks", + _("Calendar and Tasks"), + "index#calendar", + e_calendar_preferences_new, + 600); + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "prefer-new-item", + shell_backend, "prefer-new-item", + G_SETTINGS_BIND_DEFAULT); + + /* Changing whether or not to use the system timezone may change + * Evolution's current timezone. Need to emit "changed" signals + * for both keys. */ + g_signal_connect ( + settings, "changed::use-system-timezone", + G_CALLBACK (cal_shell_backend_use_system_timezone_changed_cb), + NULL); + + g_object_unref (settings); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_cal_shell_backend_parent_class)->constructed (object); + + ensure_alarm_notify_is_running (); +} + +static void +e_cal_shell_backend_class_init (ECalShellBackendClass *class) +{ + GObjectClass *object_class; + EShellBackendClass *shell_backend_class; + + g_type_class_add_private (class, sizeof (ECalShellBackendPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = cal_shell_backend_constructed; + + shell_backend_class = E_SHELL_BACKEND_CLASS (class); + shell_backend_class->shell_view_type = E_TYPE_CAL_SHELL_VIEW; + shell_backend_class->name = "calendar"; + shell_backend_class->aliases = ""; + shell_backend_class->schemes = "calendar"; + shell_backend_class->sort_order = 400; + shell_backend_class->preferences_page = "calendar-and-tasks"; + shell_backend_class->start = NULL; + shell_backend_class->migrate = e_cal_shell_backend_migrate; + + /* Register relevant ESource extensions. */ + E_TYPE_SOURCE_CALENDAR; +} + +static void +e_cal_shell_backend_class_finalize (ECalShellBackendClass *class) +{ +} + +static void +e_cal_shell_backend_init (ECalShellBackend *cal_shell_backend) +{ + icalarray *builtin_timezones; + gint ii; + + cal_shell_backend->priv = + E_CAL_SHELL_BACKEND_GET_PRIVATE (cal_shell_backend); + + /* XXX Pre-load all built-in timezones in libical. + * + * Built-in time zones in libical 0.43 are loaded on demand, + * but not in a thread-safe manner, resulting in a race when + * multiple threads call icaltimezone_load_builtin_timezone() + * on the same time zone. Until built-in time zone loading + * in libical is made thread-safe, work around the issue by + * loading all built-in time zones now, so libical's internal + * time zone array will be fully populated before any threads + * are spawned. + */ + builtin_timezones = icaltimezone_get_builtin_timezones (); + for (ii = 0; ii < builtin_timezones->num_elements; ii++) { + icaltimezone *zone; + + zone = icalarray_element_at (builtin_timezones, ii); + + /* We don't care about the component right now, + * we just need some function that will trigger + * icaltimezone_load_builtin_timezone(). */ + icaltimezone_get_component (zone); + } +} + +void +e_cal_shell_backend_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_cal_shell_backend_register_type (type_module); +} + +void +e_cal_shell_backend_open_date_range (ECalShellBackend *cal_shell_backend, + const GDate *start_date, + const GDate *end_date) +{ + EShell *shell; + EShellView *shell_view; + EShellBackend *shell_backend; + EShellSidebar *shell_sidebar; + GtkWidget *shell_window = NULL; + GtkApplication *application; + ECalendar *navigator; + GList *list; + + g_return_if_fail (E_IS_CAL_SHELL_BACKEND (cal_shell_backend)); + + shell_backend = E_SHELL_BACKEND (cal_shell_backend); + shell = e_shell_backend_get_shell (shell_backend); + + application = GTK_APPLICATION (shell); + list = gtk_application_get_windows (application); + + /* Try to find an EShellWindow already in calendar view. */ + while (list != NULL) { + GtkWidget *window = GTK_WIDGET (list->data); + + if (E_IS_SHELL_WINDOW (window)) { + const gchar *active_view; + + active_view = e_shell_window_get_active_view ( + E_SHELL_WINDOW (window)); + if (g_strcmp0 (active_view, "calendar") == 0) { + gtk_window_present (GTK_WINDOW (window)); + shell_window = window; + break; + } + } + + list = g_list_next (list); + } + + /* Otherwise create a new EShellWindow in calendar view. */ + if (shell_window == NULL) + shell_window = e_shell_create_shell_window (shell, "calendar"); + + /* Now dig up the date navigator and select the date range. */ + + shell_view = e_shell_window_get_shell_view ( + E_SHELL_WINDOW (shell_window), "calendar"); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + navigator = e_cal_shell_sidebar_get_date_navigator ( + E_CAL_SHELL_SIDEBAR (shell_sidebar)); + + e_calendar_item_set_selection ( + navigator->calitem, start_date, end_date); +} diff --git a/modules/calendar/e-cal-shell-backend.h b/modules/calendar/e-cal-shell-backend.h new file mode 100644 index 0000000000..c3058af53e --- /dev/null +++ b/modules/calendar/e-cal-shell-backend.h @@ -0,0 +1,71 @@ +/* + * e-cal-shell-backend.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_BACKEND_H +#define E_CAL_SHELL_BACKEND_H + +#include <shell/e-shell-backend.h> + +/* Standard GObject macros */ +#define E_TYPE_CAL_SHELL_BACKEND \ + (e_cal_shell_backend_get_type ()) +#define E_CAL_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackend)) +#define E_CAL_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendClass)) +#define E_IS_CAL_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_SHELL_BACKEND)) +#define E_IS_CAL_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_SHELL_BACKEND)) +#define E_CAL_SHELL_BACKEND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_SHELL_BACKEND, ECalShellBackendClass)) + +G_BEGIN_DECLS + +typedef struct _ECalShellBackend ECalShellBackend; +typedef struct _ECalShellBackendClass ECalShellBackendClass; +typedef struct _ECalShellBackendPrivate ECalShellBackendPrivate; + +struct _ECalShellBackend { + EShellBackend parent; + ECalShellBackendPrivate *priv; +}; + +struct _ECalShellBackendClass { + EShellBackendClass parent_class; +}; + +GType e_cal_shell_backend_get_type (void); +void e_cal_shell_backend_type_register + (GTypeModule *type_module); +void e_cal_shell_backend_open_date_range + (ECalShellBackend *cal_shell_backend, + const GDate *start_date, + const GDate *end_date); + +G_END_DECLS + +#endif /* E_CAL_SHELL_BACKEND_H */ diff --git a/modules/calendar/e-cal-shell-content.c b/modules/calendar/e-cal-shell-content.c new file mode 100644 index 0000000000..6e35a1fc66 --- /dev/null +++ b/modules/calendar/e-cal-shell-content.c @@ -0,0 +1,945 @@ +/* + * e-cal-shell-content.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-content.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/calendar-view.h" +#include "calendar/gui/e-cal-list-view.h" +#include "calendar/gui/e-cal-model-calendar.h" +#include "calendar/gui/e-calendar-view.h" +#include "calendar/gui/e-day-view.h" +#include "calendar/gui/e-week-view.h" + +#include "e-cal-shell-view-private.h" + +#define E_CAL_SHELL_CONTENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentPrivate)) + +struct _ECalShellContentPrivate { + GtkWidget *hpaned; + GtkWidget *notebook; + GtkWidget *vpaned; + + GtkWidget *calendar; + GtkWidget *task_table; + GtkWidget *memo_table; +}; + +enum { + PROP_0, + PROP_CALENDAR, + PROP_MEMO_TABLE, + PROP_TASK_TABLE +}; + +/* Used to indicate who has the focus within the calendar view. */ +typedef enum { + FOCUS_CALENDAR, + FOCUS_MEMO_TABLE, + FOCUS_TASK_TABLE, + FOCUS_OTHER +} FocusLocation; + +G_DEFINE_DYNAMIC_TYPE ( + ECalShellContent, + e_cal_shell_content, + E_TYPE_SHELL_CONTENT) + +static void +cal_shell_content_display_view_cb (ECalShellContent *cal_shell_content, + GalView *gal_view) +{ + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + GType gal_view_type; + + gal_view_type = G_OBJECT_TYPE (gal_view); + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + if (gal_view_type == GAL_TYPE_VIEW_ETABLE) { + ECalendarView *calendar_view; + + view_type = GNOME_CAL_LIST_VIEW; + calendar_view = gnome_calendar_get_calendar_view ( + calendar, view_type); + gal_view_etable_attach_table ( + GAL_VIEW_ETABLE (gal_view), + E_CAL_LIST_VIEW (calendar_view)->table); + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_DAY) { + view_type = GNOME_CAL_DAY_VIEW; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_WORK_WEEK) { + view_type = GNOME_CAL_WORK_WEEK_VIEW; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_WEEK) { + view_type = GNOME_CAL_WEEK_VIEW; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_MONTH) { + view_type = GNOME_CAL_MONTH_VIEW; + + } else { + g_return_if_reached (); + } + + gnome_calendar_display_view (calendar, view_type); +} + +static void +cal_shell_content_notify_view_id_cb (ECalShellContent *cal_shell_content) +{ + EShellContent *shell_content; + EShellView *shell_view; + GSettings *settings; + GtkWidget *paned; + const gchar *key; + const gchar *view_id; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + paned = cal_shell_content->priv->hpaned; + + shell_content = E_SHELL_CONTENT (cal_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + view_id = e_shell_view_get_view_id (shell_view); + + if (view_id != NULL && strcmp (view_id, "Month_View") == 0) + key = "month-hpane-position"; + else + key = "hpane-position"; + + g_settings_unbind (paned, "hposition"); + + g_settings_bind ( + settings, key, + paned, "hposition", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); +} + +static gchar * +cal_shell_content_get_pad_state_filename (EShellContent *shell_content, + ETable *table) +{ + EShellBackend *shell_backend; + EShellView *shell_view; + const gchar *config_dir, *nick = NULL; + + g_return_val_if_fail (shell_content != NULL, NULL); + g_return_val_if_fail (E_IS_SHELL_CONTENT (shell_content), NULL); + g_return_val_if_fail (table != NULL, NULL); + g_return_val_if_fail (E_IS_TABLE (table), NULL); + + if (E_IS_TASK_TABLE (table)) + nick = "TaskPad"; + else if (E_IS_MEMO_TABLE (table)) + nick = "MemoPad"; + + g_return_val_if_fail (nick != NULL, NULL); + + shell_view = e_shell_content_get_shell_view (shell_content); + shell_backend = e_shell_view_get_shell_backend (shell_view); + config_dir = e_shell_backend_get_config_dir (shell_backend); + + return g_build_filename (config_dir, nick, NULL); +} + +static void +cal_shell_content_save_table_state (EShellContent *shell_content, + ETable *table) +{ + gchar *filename; + + filename = cal_shell_content_get_pad_state_filename ( + shell_content, table); + g_return_if_fail (filename != NULL); + + e_table_save_state (table, filename); + g_free (filename); +} + +static void +cal_shell_content_load_table_state (EShellContent *shell_content, + ETable *table) +{ + gchar *filename; + + filename = cal_shell_content_get_pad_state_filename ( + shell_content, table); + g_return_if_fail (filename != NULL); + + e_table_load_state (table, filename); + g_free (filename); +} + +void +e_cal_shell_content_save_state (ECalShellContent *cal_shell_content) +{ + ECalShellContentPrivate *priv; + + g_return_if_fail (cal_shell_content != NULL); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + priv = cal_shell_content->priv; + + if (priv->task_table != NULL) + cal_shell_content_save_table_state ( + E_SHELL_CONTENT (cal_shell_content), + E_TABLE (priv->task_table)); + + if (priv->memo_table != NULL) + cal_shell_content_save_table_state ( + E_SHELL_CONTENT (cal_shell_content), + E_TABLE (priv->memo_table)); +} + +static void +cal_shell_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_shell_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CALENDAR: + g_value_set_object ( + value, e_cal_shell_content_get_calendar ( + E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_MEMO_TABLE: + g_value_set_object ( + value, e_cal_shell_content_get_memo_table ( + E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_TASK_TABLE: + g_value_set_object ( + value, e_cal_shell_content_get_task_table ( + E_CAL_SHELL_CONTENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_shell_content_dispose (GObject *object) +{ + ECalShellContentPrivate *priv; + + priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (object); + + if (priv->hpaned != NULL) { + g_object_unref (priv->hpaned); + priv->hpaned = NULL; + } + + if (priv->notebook != NULL) { + g_object_unref (priv->notebook); + priv->notebook = NULL; + } + + if (priv->vpaned != NULL) { + g_object_unref (priv->vpaned); + priv->vpaned = NULL; + } + + if (priv->calendar != NULL) { + g_object_unref (priv->calendar); + priv->calendar = NULL; + } + + if (priv->task_table != NULL) { + g_object_unref (priv->task_table); + priv->task_table = NULL; + } + + if (priv->memo_table != NULL) { + g_object_unref (priv->memo_table); + priv->memo_table = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_cal_shell_content_parent_class)->dispose (object); +} + +static time_t +gc_get_default_time (ECalModel *model, + gpointer user_data) +{ + GnomeCalendar *gcal = user_data; + time_t res = 0, end; + + g_return_val_if_fail (model != NULL, 0); + g_return_val_if_fail (GNOME_IS_CALENDAR (user_data), 0); + + gnome_calendar_get_current_time_range (gcal, &res, &end); + + return res; +} + +static void +cal_shell_content_is_editing_changed_cb (gpointer cal_view_tasks_memos_table, + GParamSpec *param, + EShellView *shell_view) +{ + g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); + + e_shell_view_update_actions (shell_view); +} + +static void +cal_shell_content_constructed (GObject *object) +{ + ECalShellContentPrivate *priv; + ECalendarView *calendar_view; + ECalModel *memo_model = NULL; + ECalModel *task_model = NULL; + EShell *shell; + EShellContent *shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellContent *foreign_content; + EShellView *foreign_view; + GnomeCalendar *calendar; + ESourceRegistry *registry; + GalViewInstance *view_instance; + GSettings *settings; + GtkWidget *container; + GtkWidget *widget; + gchar *markup; + gint ii; + + priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_cal_shell_content_parent_class)->constructed (object); + + shell_content = E_SHELL_CONTENT (object); + shell_view = e_shell_content_get_shell_view (shell_content); + shell_window = e_shell_view_get_shell_window (shell_view); + + shell = e_shell_window_get_shell (shell_window); + + /* We borrow the memopad and taskpad models from the memo + * and task views, loading the views if necessary. */ + foreign_view = e_shell_window_get_shell_view (shell_window, "memos"); + foreign_content = e_shell_view_get_shell_content (foreign_view); + g_object_get (foreign_content, "model", &memo_model, NULL); + + foreign_view = e_shell_window_get_shell_view (shell_window, "tasks"); + foreign_content = e_shell_view_get_shell_content (foreign_view); + g_object_get (foreign_content, "model", &task_model, NULL); + + /* Build content widgets. */ + + container = GTK_WIDGET (object); + + widget = e_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->hpaned = g_object_ref (widget); + gtk_widget_show (widget); + + container = priv->hpaned; + + widget = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE); + priv->notebook = g_object_ref (widget); + gtk_widget_show (widget); + + /* FIXME Need to deal with saving and restoring the position. + * Month view has its own position. */ + widget = e_paned_new (GTK_ORIENTATION_VERTICAL); + e_paned_set_fixed_resize (E_PANED (widget), FALSE); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, TRUE); + priv->vpaned = g_object_ref (widget); + gtk_widget_show (widget); + + container = priv->notebook; + + /* Add views in the order defined by GnomeCalendarViewType, such + * that the notebook page number corresponds to the view type. */ + + registry = e_shell_get_registry (shell); + priv->calendar = gnome_calendar_new (registry); + calendar = GNOME_CALENDAR (priv->calendar); + + for (ii = 0; ii < GNOME_CAL_LAST_VIEW; ii++) { + calendar_view = gnome_calendar_get_calendar_view (calendar, ii); + + g_signal_connect ( + calendar_view, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + gtk_notebook_append_page ( + GTK_NOTEBOOK (container), + GTK_WIDGET (calendar_view), NULL); + gtk_widget_show (GTK_WIDGET (calendar_view)); + } + + g_object_bind_property ( + priv->calendar, "view", + priv->notebook, "page", + G_BINDING_SYNC_CREATE); + + container = priv->vpaned; + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, TRUE); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + gtk_widget_show (widget); + + widget = gtk_label_new (NULL); + markup = g_strdup_printf ("<b>%s</b>", _("Tasks")); + gtk_label_set_markup (GTK_LABEL (widget), markup); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + gtk_widget_show (widget); + g_free (markup); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = e_task_table_new (shell_view, task_model); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->task_table = g_object_ref (widget); + gtk_widget_show (widget); + + cal_shell_content_load_table_state ( + shell_content, E_TABLE (widget)); + + g_signal_connect_swapped ( + widget, "open-component", + G_CALLBACK (e_cal_shell_view_taskpad_open_task), + shell_view); + + g_signal_connect ( + widget, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + container = priv->vpaned; + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_paned_pack2 (GTK_PANED (container), widget, TRUE, TRUE); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_label_new (NULL); + markup = g_strdup_printf ("<b>%s</b>", _("Memos")); + gtk_label_set_markup (GTK_LABEL (widget), markup); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + gtk_widget_show (widget); + g_free (markup); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = e_memo_table_new (shell_view, memo_model); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->memo_table = g_object_ref (widget); + gtk_widget_show (widget); + + cal_shell_content_load_table_state ( + shell_content, E_TABLE (widget)); + + e_cal_model_set_default_time_func ( + memo_model, gc_get_default_time, calendar); + + g_signal_connect_swapped ( + widget, "open-component", + G_CALLBACK (e_cal_shell_view_memopad_open_memo), + shell_view); + + g_signal_connect ( + widget, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + /* Load the view instance. */ + + view_instance = e_shell_view_new_view_instance (shell_view, NULL); + g_signal_connect_swapped ( + view_instance, "display-view", + G_CALLBACK (cal_shell_content_display_view_cb), + object); + /* XXX Actually, don't load the view instance just yet. + * The GtkWidget::map() callback below explains why. */ + e_shell_view_set_view_instance (shell_view, view_instance); + g_object_unref (view_instance); + + g_signal_connect_swapped ( + shell_view, "notify::view-id", + G_CALLBACK (cal_shell_content_notify_view_id_cb), + object); + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "tag-vpane-position", + priv->vpaned, "proportion", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); + + if (memo_model) + g_object_unref (memo_model); + if (task_model) + g_object_unref (task_model); +} + +static void +cal_shell_content_map (GtkWidget *widget) +{ + EShellView *shell_view; + EShellContent *shell_content; + GalViewInstance *view_instance; + + shell_content = E_SHELL_CONTENT (widget); + shell_view = e_shell_content_get_shell_view (shell_content); + view_instance = e_shell_view_get_view_instance (shell_view); + + /* XXX Delay loading the GalViewInstance until after ECalShellView + * has a chance to install the sidebar's date navigator into + * GnomeCalendar, since loading the GalViewInstance triggers a + * callback in GnomeCalendar that requires the date navigator. + * Ordinarily we would do this at the end of constructed(), but + * that's too soon in this case. (This feels kind of kludgy.) */ + gal_view_instance_load (view_instance); + + /* Chain up to parent's map() method. */ + GTK_WIDGET_CLASS (e_cal_shell_content_parent_class)->map (widget); +} + +/* Helper for cal_shell_content_check_state() */ +static icalproperty * +cal_shell_content_get_attendee_prop (icalcomponent *icalcomp, + const gchar *address) +{ + icalproperty *prop; + + if (address == NULL || *address == '\0') + return NULL; + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + + while (prop != NULL) { + const gchar *attendee; + + attendee = icalproperty_get_attendee (prop); + + if (g_str_equal (itip_strip_mailto (attendee), address)) + return prop; + + prop = icalcomponent_get_next_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + } + + return NULL; +} + +/* Helper for cal_shell_content_check_state() */ +static gboolean +cal_shell_content_icalcomp_is_delegated (icalcomponent *icalcomp, + const gchar *user_email) +{ + icalproperty *prop; + icalparameter *param; + const gchar *delto = NULL; + gboolean is_delegated = FALSE; + + prop = cal_shell_content_get_attendee_prop (icalcomp, user_email); + + if (prop != NULL) { + param = icalproperty_get_first_parameter ( + prop, ICAL_DELEGATEDTO_PARAMETER); + if (param != NULL) { + delto = icalparameter_get_delegatedto (param); + delto = itip_strip_mailto (delto); + } + } else + return FALSE; + + prop = cal_shell_content_get_attendee_prop (icalcomp, delto); + + if (prop != NULL) { + const gchar *delfrom = NULL; + icalparameter_partstat status = ICAL_PARTSTAT_NONE; + + param = icalproperty_get_first_parameter ( + prop, ICAL_DELEGATEDFROM_PARAMETER); + if (param != NULL) { + delfrom = icalparameter_get_delegatedfrom (param); + delfrom = itip_strip_mailto (delfrom); + } + param = icalproperty_get_first_parameter ( + prop, ICAL_PARTSTAT_PARAMETER); + if (param != NULL) + status = icalparameter_get_partstat (param); + is_delegated = + (status != ICAL_PARTSTAT_DECLINED) && + (g_strcmp0 (delfrom, user_email) == 0); + } + + return is_delegated; +} + +static guint32 +cal_shell_content_check_state (EShellContent *shell_content) +{ + EShell *shell; + EShellView *shell_view; + EShellBackend *shell_backend; + ESourceRegistry *registry; + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + GnomeCalendarViewType view_type; + gboolean selection_is_editable = FALSE; + gboolean selection_is_instance = FALSE; + gboolean selection_is_meeting = FALSE; + gboolean selection_is_organizer = FALSE; + gboolean selection_is_recurring = FALSE; + gboolean selection_can_delegate = FALSE; + guint32 state = 0; + GList *selected; + GList *link; + guint n_selected; + + cal_shell_content = E_CAL_SHELL_CONTENT (shell_content); + + shell_view = e_shell_content_get_shell_view (shell_content); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_backend_get_shell (shell_backend); + registry = e_shell_get_registry (shell); + + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + n_selected = g_list_length (selected); + + /* If we have a selection, assume it's + * editable until we learn otherwise. */ + if (n_selected > 0) + selection_is_editable = TRUE; + + for (link = selected; link != NULL; link = g_list_next (link)) { + ECalendarViewEvent *event = link->data; + ECalClient *client; + ECalComponent *comp; + gchar *user_email; + icalcomponent *icalcomp; + const gchar *capability; + gboolean cap_delegate_supported; + gboolean cap_delegate_to_many; + gboolean icalcomp_is_delegated; + gboolean read_only; + + if (!is_comp_data_valid (event)) + continue; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + read_only = e_client_is_readonly (E_CLIENT (client)); + selection_is_editable &= !read_only; + + selection_is_instance |= + e_cal_util_component_is_instance (icalcomp); + + selection_is_meeting = + (n_selected == 1) && + e_cal_util_component_has_attendee (icalcomp); + + selection_is_recurring |= + e_cal_util_component_is_instance (icalcomp) || + e_cal_util_component_has_recurrences (icalcomp); + + /* XXX The rest of this is rather expensive and + * only applies if a single event is selected, + * so continue with the loop iteration if the + * rest of this is not applicable. */ + if (n_selected > 1) + continue; + + /* XXX This probably belongs in comp-util.c. */ + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + comp, icalcomponent_new_clone (icalcomp)); + user_email = itip_get_comp_attendee ( + registry, comp, client); + + selection_is_organizer = + e_cal_util_component_has_organizer (icalcomp) && + itip_organizer_is_user (registry, comp, client); + + capability = CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED; + cap_delegate_supported = + e_client_check_capability ( + E_CLIENT (client), capability); + + capability = CAL_STATIC_CAPABILITY_DELEGATE_TO_MANY; + cap_delegate_to_many = + e_client_check_capability ( + E_CLIENT (client), capability); + + icalcomp_is_delegated = + (user_email != NULL) && + cal_shell_content_icalcomp_is_delegated ( + icalcomp, user_email); + + selection_can_delegate = + cap_delegate_supported && + (cap_delegate_to_many || + (!selection_is_organizer && + !icalcomp_is_delegated)); + + g_free (user_email); + g_object_unref (comp); + } + + g_list_free (selected); + + if (n_selected == 1) + state |= E_CAL_SHELL_CONTENT_SELECTION_SINGLE; + if (n_selected > 1) + state |= E_CAL_SHELL_CONTENT_SELECTION_MULTIPLE; + if (selection_is_editable) + state |= E_CAL_SHELL_CONTENT_SELECTION_IS_EDITABLE; + if (selection_is_instance) + state |= E_CAL_SHELL_CONTENT_SELECTION_IS_INSTANCE; + if (selection_is_meeting) + state |= E_CAL_SHELL_CONTENT_SELECTION_IS_MEETING; + if (selection_is_organizer) + state |= E_CAL_SHELL_CONTENT_SELECTION_IS_ORGANIZER; + if (selection_is_recurring) + state |= E_CAL_SHELL_CONTENT_SELECTION_IS_RECURRING; + if (selection_can_delegate) + state |= E_CAL_SHELL_CONTENT_SELECTION_CAN_DELEGATE; + + return state; +} + +static void +cal_shell_content_focus_search_results (EShellContent *shell_content) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + + cal_shell_content = E_CAL_SHELL_CONTENT (shell_content); + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + gtk_widget_grab_focus (GTK_WIDGET (calendar_view)); +} + +static void +e_cal_shell_content_class_init (ECalShellContentClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + EShellContentClass *shell_content_class; + + g_type_class_add_private (class, sizeof (ECalShellContentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = cal_shell_content_set_property; + object_class->get_property = cal_shell_content_get_property; + object_class->dispose = cal_shell_content_dispose; + object_class->constructed = cal_shell_content_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->map = cal_shell_content_map; + + shell_content_class = E_SHELL_CONTENT_CLASS (class); + shell_content_class->check_state = cal_shell_content_check_state; + shell_content_class->focus_search_results = cal_shell_content_focus_search_results; + + g_object_class_install_property ( + object_class, + PROP_CALENDAR, + g_param_spec_object ( + "calendar", + NULL, + NULL, + GNOME_TYPE_CALENDAR, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_MEMO_TABLE, + g_param_spec_object ( + "memo-table", + NULL, + NULL, + E_TYPE_MEMO_TABLE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_TASK_TABLE, + g_param_spec_object ( + "task-table", + NULL, + NULL, + E_TYPE_TASK_TABLE, + G_PARAM_READABLE)); +} + +static void +e_cal_shell_content_class_finalize (ECalShellContentClass *class) +{ +} + +static void +e_cal_shell_content_init (ECalShellContent *cal_shell_content) +{ + cal_shell_content->priv = + E_CAL_SHELL_CONTENT_GET_PRIVATE (cal_shell_content); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_cal_shell_content_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_cal_shell_content_register_type (type_module); +} + +GtkWidget * +e_cal_shell_content_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_CAL_SHELL_CONTENT, + "shell-view", shell_view, NULL); +} + +ECalModel * +e_cal_shell_content_get_model (ECalShellContent *cal_shell_content) +{ + GnomeCalendar *calendar; + + g_return_val_if_fail ( + E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + return gnome_calendar_get_model (calendar); +} + +GnomeCalendar * +e_cal_shell_content_get_calendar (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return GNOME_CALENDAR (cal_shell_content->priv->calendar); +} + +EMemoTable * +e_cal_shell_content_get_memo_table (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return E_MEMO_TABLE (cal_shell_content->priv->memo_table); +} + +ETaskTable * +e_cal_shell_content_get_task_table (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return E_TASK_TABLE (cal_shell_content->priv->task_table); +} + +EShellSearchbar * +e_cal_shell_content_get_searchbar (ECalShellContent *cal_shell_content) +{ + EShellView *shell_view; + EShellContent *shell_content; + GtkWidget *widget; + + g_return_val_if_fail ( + E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + shell_content = E_SHELL_CONTENT (cal_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + widget = e_shell_view_get_searchbar (shell_view); + + return E_SHELL_SEARCHBAR (widget); +} + diff --git a/modules/calendar/e-cal-shell-content.h b/modules/calendar/e-cal-shell-content.h new file mode 100644 index 0000000000..b42d099cb6 --- /dev/null +++ b/modules/calendar/e-cal-shell-content.h @@ -0,0 +1,98 @@ +/* + * e-cal-shell-content.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_CONTENT_H +#define E_CAL_SHELL_CONTENT_H + +#include <shell/e-shell-content.h> +#include <shell/e-shell-searchbar.h> +#include <shell/e-shell-view.h> + +#include <calendar/gui/e-memo-table.h> +#include <calendar/gui/e-task-table.h> +#include <calendar/gui/gnome-cal.h> + +/* Standard GObject macros */ +#define E_TYPE_CAL_SHELL_CONTENT \ + (e_cal_shell_content_get_type ()) +#define E_CAL_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContent)) +#define E_CAL_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentClass)) +#define E_IS_CAL_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_SHELL_CONTENT)) +#define E_IS_CAL_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_SHELL_CONTENT)) +#define E_CAL_SHELL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentClass)) + +G_BEGIN_DECLS + +typedef struct _ECalShellContent ECalShellContent; +typedef struct _ECalShellContentClass ECalShellContentClass; +typedef struct _ECalShellContentPrivate ECalShellContentPrivate; + +enum { + E_CAL_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0, + E_CAL_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1, + E_CAL_SHELL_CONTENT_SELECTION_IS_EDITABLE = 1 << 2, + E_CAL_SHELL_CONTENT_SELECTION_IS_INSTANCE = 1 << 3, + E_CAL_SHELL_CONTENT_SELECTION_IS_MEETING = 1 << 4, + E_CAL_SHELL_CONTENT_SELECTION_IS_ORGANIZER = 1 << 5, + E_CAL_SHELL_CONTENT_SELECTION_IS_RECURRING = 1 << 6, + E_CAL_SHELL_CONTENT_SELECTION_CAN_DELEGATE = 1 << 7 +}; + +struct _ECalShellContent { + EShellContent parent; + ECalShellContentPrivate *priv; +}; + +struct _ECalShellContentClass { + EShellContentClass parent_class; +}; + +GType e_cal_shell_content_get_type (void); +void e_cal_shell_content_type_register + (GTypeModule *type_module); +GtkWidget * e_cal_shell_content_new (EShellView *shell_view); +ECalModel * e_cal_shell_content_get_model + (ECalShellContent *cal_shell_content); +GnomeCalendar * e_cal_shell_content_get_calendar + (ECalShellContent *cal_shell_content); +EMemoTable * e_cal_shell_content_get_memo_table + (ECalShellContent *cal_shell_content); +ETaskTable * e_cal_shell_content_get_task_table + (ECalShellContent *cal_shell_content); +EShellSearchbar * + e_cal_shell_content_get_searchbar + (ECalShellContent *cal_shell_content); +void e_cal_shell_content_save_state + (ECalShellContent *cal_shell_content); + +G_END_DECLS + +#endif /* E_CAL_SHELL_CONTENT_H */ diff --git a/modules/calendar/e-cal-shell-migrate.c b/modules/calendar/e-cal-shell-migrate.c new file mode 100644 index 0000000000..3b92c996e7 --- /dev/null +++ b/modules/calendar/e-cal-shell-migrate.c @@ -0,0 +1,37 @@ +/* + * e-cal-shell-backend-migrate.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-migrate.h" + +gboolean +e_cal_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error) +{ + return TRUE; +} + diff --git a/modules/calendar/e-cal-shell-migrate.h b/modules/calendar/e-cal-shell-migrate.h new file mode 100644 index 0000000000..6c021978a0 --- /dev/null +++ b/modules/calendar/e-cal-shell-migrate.h @@ -0,0 +1,37 @@ +/* + * e-cal-shell-backend-migrate.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_BACKEND_MIGRATE_H +#define E_CAL_SHELL_BACKEND_MIGRATE_H + +#include <shell/e-shell-backend.h> + +G_BEGIN_DECLS + +gboolean e_cal_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_CAL_SHELL_BACKEND_MIGRATE_H */ diff --git a/modules/calendar/e-cal-shell-sidebar.c b/modules/calendar/e-cal-shell-sidebar.c new file mode 100644 index 0000000000..f8d982a681 --- /dev/null +++ b/modules/calendar/e-cal-shell-sidebar.c @@ -0,0 +1,889 @@ +/* + * e-cal-shell-sidebar.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-sidebar.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "calendar/gui/e-calendar-selector.h" +#include "calendar/gui/misc.h" + +#define E_CAL_SHELL_SIDEBAR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarPrivate)) + +typedef struct _ConnectClosure ConnectClosure; + +struct _ECalShellSidebarPrivate { + GtkWidget *paned; + GtkWidget *selector; + GtkWidget *date_navigator; + + /* The default client is for ECalModel. It follows the + * sidebar's primary selection, even if the highlighted + * source is not selected. The tricky part is we don't + * update the property until the client is successfully + * opened. So the user first highlights a source, then + * sometime later we update our default-client property + * which is bound by an EBinding to ECalModel. */ + EClient *default_client; + + /* Not referenced, only for pointer comparison. */ + ESource *connecting_default_source_instance; + + EActivity *connecting_default_client; +}; + +struct _ConnectClosure { + ECalShellSidebar *cal_shell_sidebar; + EActivity *activity; + + /* For error messages. */ + gchar *unique_display_name; +}; + +enum { + PROP_0, + PROP_DATE_NAVIGATOR, + PROP_DEFAULT_CLIENT, + PROP_SELECTOR +}; + +enum { + CLIENT_ADDED, + CLIENT_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_DYNAMIC_TYPE ( + ECalShellSidebar, + e_cal_shell_sidebar, + E_TYPE_SHELL_SIDEBAR) + +static ConnectClosure * +connect_closure_new (ECalShellSidebar *cal_shell_sidebar, + ESource *source) +{ + ConnectClosure *closure; + EAlertSink *alert_sink; + GCancellable *cancellable; + ESourceRegistry *registry; + ESourceSelector *selector; + EShellView *shell_view; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + gchar *text; + + shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + registry = e_source_selector_get_registry (selector); + + closure = g_slice_new0 (ConnectClosure); + closure->cal_shell_sidebar = g_object_ref (cal_shell_sidebar); + closure->activity = e_activity_new (); + closure->unique_display_name = + e_source_registry_dup_unique_display_name ( + registry, source, E_SOURCE_EXTENSION_CALENDAR); + + text = g_strdup_printf ( + _("Opening calendar '%s'"), + closure->unique_display_name); + e_activity_set_text (closure->activity, text); + g_free (text); + + alert_sink = E_ALERT_SINK (shell_content); + e_activity_set_alert_sink (closure->activity, alert_sink); + + cancellable = g_cancellable_new (); + e_activity_set_cancellable (closure->activity, cancellable); + g_object_unref (cancellable); + + e_shell_backend_add_activity (shell_backend, closure->activity); + + return closure; +} + +static void +connect_closure_free (ConnectClosure *closure) +{ + g_clear_object (&closure->cal_shell_sidebar); + g_clear_object (&closure->activity); + + g_free (closure->unique_display_name); + + g_slice_free (ConnectClosure, closure); +} + +static gboolean +cal_shell_sidebar_map_uid_to_source (GValue *value, + GVariant *variant, + gpointer user_data) +{ + ESourceRegistry *registry; + ESource *source; + const gchar *uid; + + registry = E_SOURCE_REGISTRY (user_data); + uid = g_variant_get_string (variant, NULL); + if (uid != NULL && *uid != '\0') + source = e_source_registry_ref_source (registry, uid); + else + source = e_source_registry_ref_default_calendar (registry); + g_value_take_object (value, source); + + return (source != NULL); +} + +static GVariant * +cal_shell_sidebar_map_source_to_uid (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariant *variant = NULL; + ESource *source; + + source = g_value_get_object (value); + + if (source != NULL) { + const gchar *uid; + + uid = e_source_get_uid (source); + variant = g_variant_new_string (uid); + } + + return variant; +} + +static void +cal_shell_sidebar_emit_client_added (ECalShellSidebar *cal_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_ADDED]; + + g_signal_emit (cal_shell_sidebar, signal_id, 0, client); +} + +static void +cal_shell_sidebar_emit_client_removed (ECalShellSidebar *cal_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_REMOVED]; + + g_signal_emit (cal_shell_sidebar, signal_id, 0, client); +} + +static void +cal_shell_sidebar_handle_connect_error (EActivity *activity, + const gchar *unique_display_name, + const GError *error) +{ + EAlertSink *alert_sink; + gboolean cancelled = FALSE; + gboolean offline_error; + + alert_sink = e_activity_get_alert_sink (activity); + + cancelled |= g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + cancelled |= g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED); + + offline_error = g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE); + + if (e_activity_handle_cancellation (activity, error)) { + /* do nothing */ + } else if (offline_error) { + e_alert_submit ( + alert_sink, + "calendar:prompt-no-contents-offline-calendar", + unique_display_name, + NULL); + } else { + e_alert_submit ( + alert_sink, + "calendar:failed-open-calendar", + unique_display_name, + error->message, + NULL); + } +} + +static void +cal_shell_sidebar_client_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ConnectClosure *closure = user_data; + GError *error = NULL; + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (error != NULL) { + cal_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + e_cal_shell_sidebar_add_client (closure->cal_shell_sidebar, client); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +cal_shell_sidebar_default_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ESource *source; + ConnectClosure *closure = user_data; + ECalShellSidebarPrivate *priv; + GError *error = NULL; + + priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (closure->cal_shell_sidebar); + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + g_clear_object (&priv->connecting_default_client); + + if (error != NULL) { + cal_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + source = e_client_get_source (client); + + if (source == priv->connecting_default_source_instance) + priv->connecting_default_source_instance = NULL; + + if (priv->default_client != NULL) + g_object_unref (priv->default_client); + + priv->default_client = g_object_ref (client); + + g_object_notify ( + G_OBJECT (closure->cal_shell_sidebar), "default-client"); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +cal_shell_sidebar_set_default (ECalShellSidebar *cal_shell_sidebar, + ESource *source) +{ + ECalShellSidebarPrivate *priv; + ESourceSelector *selector; + ConnectClosure *closure; + + priv = cal_shell_sidebar->priv; + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + /* already loading that source as default source */ + if (source == priv->connecting_default_source_instance) + return; + + /* Cancel the previous request if unfinished. */ + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + closure = connect_closure_new (cal_shell_sidebar, source); + + /* it's only for pointer comparison, no need to ref it */ + priv->connecting_default_source_instance = source; + priv->connecting_default_client = g_object_ref (closure->activity); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + cal_shell_sidebar_default_connect_cb, closure); +} + +static void +cal_shell_sidebar_row_changed_cb (ECalShellSidebar *cal_shell_sidebar, + GtkTreePath *tree_path, + GtkTreeIter *tree_iter, + GtkTreeModel *tree_model) +{ + ESourceSelector *selector; + ESource *source; + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + source = e_source_selector_ref_source_by_path (selector, tree_path); + + /* XXX This signal gets emitted a lot while the model is being + * rebuilt, during which time we won't get a valid ESource. + * ESourceSelector should probably block this signal while + * rebuilding the model, but we'll be forgiving and not + * emit a warning. */ + if (source == NULL) + return; + + if (e_source_selector_source_is_selected (selector, source)) + e_cal_shell_sidebar_add_source (cal_shell_sidebar, source); + else + e_cal_shell_sidebar_remove_source (cal_shell_sidebar, source); + + g_object_unref (source); +} + +static void +cal_shell_sidebar_primary_selection_changed_cb (ECalShellSidebar *cal_shell_sidebar, + ESourceSelector *selector) +{ + ESource *source; + + source = e_source_selector_ref_primary_selection (selector); + if (source == NULL) + return; + + cal_shell_sidebar_set_default (cal_shell_sidebar, source); + + g_object_unref (source); +} + +static void +cal_shell_sidebar_restore_state_cb (EShellWindow *shell_window, + EShellView *shell_view, + EShellSidebar *shell_sidebar) +{ + ECalShellSidebarPrivate *priv; + ESourceRegistry *registry; + ESourceSelector *selector; + GSettings *settings; + GtkTreeModel *model; + + priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (shell_sidebar); + + g_signal_handlers_disconnect_by_func ( + shell_window, + cal_shell_sidebar_restore_state_cb, shell_sidebar); + + selector = E_SOURCE_SELECTOR (priv->selector); + registry = e_source_selector_get_registry (selector); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector)); + + g_signal_connect_swapped ( + registry, "source-removed", + G_CALLBACK (e_cal_shell_sidebar_remove_source), shell_sidebar); + + g_signal_connect_swapped ( + model, "row-changed", + G_CALLBACK (cal_shell_sidebar_row_changed_cb), + shell_sidebar); + + g_signal_connect_swapped ( + selector, "primary-selection-changed", + G_CALLBACK (cal_shell_sidebar_primary_selection_changed_cb), + shell_sidebar); + + /* This will trigger our "row-changed" signal handler for each + * calendar source, so the appropriate ECalClients get added to + * the ECalModel, which will then create view objects to display + * the calendar content. This all happens asynchronously. */ + e_source_selector_update_all_rows (selector); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind_with_mapping ( + settings, "primary-calendar", + selector, "primary-selection", + G_SETTINGS_BIND_DEFAULT, + cal_shell_sidebar_map_uid_to_source, + cal_shell_sidebar_map_source_to_uid, + g_object_ref (registry), + (GDestroyNotify) g_object_unref); + + g_settings_bind ( + settings, "date-navigator-pane-position", + priv->paned, "vposition", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); +} + +static void +cal_shell_sidebar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_DATE_NAVIGATOR: + g_value_set_object ( + value, + e_cal_shell_sidebar_get_date_navigator ( + E_CAL_SHELL_SIDEBAR (object))); + return; + + case PROP_DEFAULT_CLIENT: + g_value_set_object ( + value, + e_cal_shell_sidebar_get_default_client ( + E_CAL_SHELL_SIDEBAR (object))); + return; + + case PROP_SELECTOR: + g_value_set_object ( + value, + e_cal_shell_sidebar_get_selector ( + E_CAL_SHELL_SIDEBAR (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_shell_sidebar_dispose (GObject *object) +{ + ECalShellSidebarPrivate *priv; + + priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object); + + if (priv->paned != NULL) { + g_object_unref (priv->paned); + priv->paned = NULL; + } + + if (priv->selector != NULL) { + g_object_unref (priv->selector); + priv->selector = NULL; + } + + if (priv->date_navigator != NULL) { + g_object_unref (priv->date_navigator); + priv->date_navigator = NULL; + } + + if (priv->default_client != NULL) { + g_object_unref (priv->default_client); + priv->default_client = NULL; + } + + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_cal_shell_sidebar_parent_class)->dispose (object); +} + +static void +cal_shell_sidebar_constructed (GObject *object) +{ + ECalShellSidebarPrivate *priv; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + EShellSidebar *shell_sidebar; + EClientCache *client_cache; + ECalendarItem *calitem; + GtkWidget *container; + GtkWidget *widget; + AtkObject *a11y; + + priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_cal_shell_sidebar_parent_class)->constructed (object); + + shell_sidebar = E_SHELL_SIDEBAR (object); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_backend_get_shell (shell_backend); + + container = GTK_WIDGET (shell_sidebar); + + widget = e_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->paned = g_object_ref (widget); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, TRUE); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + client_cache = e_shell_get_client_cache (shell); + widget = e_calendar_selector_new (client_cache); + e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE); + gtk_container_add (GTK_CONTAINER (container), widget); + a11y = gtk_widget_get_accessible (widget); + atk_object_set_name (a11y, _("Calendar Selector")); + priv->selector = g_object_ref (widget); + gtk_widget_show (widget); + + container = priv->paned; + + widget = e_calendar_new (); + calitem = E_CALENDAR (widget)->calitem; + e_calendar_item_set_days_start_week_sel (calitem, 9); + e_calendar_item_set_max_days_sel (calitem, 42); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->date_navigator = g_object_ref (widget); + gtk_widget_show (widget); + + /* Restore widget state from the last session once + * the shell view is fully initialized and visible. */ + g_signal_connect ( + shell_window, "shell-view-created::calendar", + G_CALLBACK (cal_shell_sidebar_restore_state_cb), + shell_sidebar); +} + +static guint32 +cal_shell_sidebar_check_state (EShellSidebar *shell_sidebar) +{ + ECalShellSidebar *cal_shell_sidebar; + ESourceSelector *selector; + ESourceRegistry *registry; + ESource *source; + gboolean is_writable = FALSE; + gboolean is_removable = FALSE; + gboolean is_remote_creatable = FALSE; + gboolean is_remote_deletable = FALSE; + gboolean in_collection = FALSE; + gboolean refresh_supported = FALSE; + gboolean has_primary_source = FALSE; + guint32 state = 0; + + cal_shell_sidebar = E_CAL_SHELL_SIDEBAR (shell_sidebar); + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + registry = e_source_selector_get_registry (selector); + + if (source != NULL) { + EClient *client; + ESource *collection; + + has_primary_source = TRUE; + is_writable = e_source_get_writable (source); + is_removable = e_source_get_removable (source); + is_remote_creatable = e_source_get_remote_creatable (source); + is_remote_deletable = e_source_get_remote_deletable (source); + + collection = e_source_registry_find_extension ( + registry, source, E_SOURCE_EXTENSION_COLLECTION); + if (collection != NULL) { + in_collection = TRUE; + g_object_unref (collection); + } + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + refresh_supported = + e_client_check_refresh_supported (client); + g_object_unref (client); + } + + g_object_unref (source); + } + + if (has_primary_source) + state |= E_CAL_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE; + if (is_writable) + state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE; + if (is_removable) + state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE; + if (is_remote_creatable) + state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE; + if (is_remote_deletable) + state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE; + if (in_collection) + state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION; + if (refresh_supported) + state |= E_CAL_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH; + + return state; +} + +static void +cal_shell_sidebar_client_removed (ECalShellSidebar *cal_shell_sidebar, + ECalClient *client) +{ + ESourceSelector *selector; + ESource *source; + + source = e_client_get_source (E_CLIENT (client)); + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + e_source_selector_unselect_source (selector, source); +} + +static void +e_cal_shell_sidebar_class_init (ECalShellSidebarClass *class) +{ + GObjectClass *object_class; + EShellSidebarClass *shell_sidebar_class; + + g_type_class_add_private (class, sizeof (ECalShellSidebarPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = cal_shell_sidebar_get_property; + object_class->dispose = cal_shell_sidebar_dispose; + object_class->constructed = cal_shell_sidebar_constructed; + + shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class); + shell_sidebar_class->check_state = cal_shell_sidebar_check_state; + + class->client_removed = cal_shell_sidebar_client_removed; + + g_object_class_install_property ( + object_class, + PROP_DATE_NAVIGATOR, + g_param_spec_object ( + "date-navigator", + "Date Navigator Widget", + "This widget displays a miniature calendar", + E_TYPE_CALENDAR, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_CLIENT, + g_param_spec_object ( + "default-client", + "Default Calendar ECalClient", + "Default client for calendar operations", + E_TYPE_CAL_CLIENT, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_SELECTOR, + g_param_spec_object ( + "selector", + "Source Selector Widget", + "This widget displays groups of calendars", + E_TYPE_SOURCE_SELECTOR, + G_PARAM_READABLE)); + + signals[CLIENT_ADDED] = g_signal_new ( + "client-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalShellSidebarClass, client_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); + + signals[CLIENT_REMOVED] = g_signal_new ( + "client-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalShellSidebarClass, client_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); +} + +static void +e_cal_shell_sidebar_class_finalize (ECalShellSidebarClass *class) +{ +} + +static void +e_cal_shell_sidebar_init (ECalShellSidebar *cal_shell_sidebar) +{ + cal_shell_sidebar->priv = + E_CAL_SHELL_SIDEBAR_GET_PRIVATE (cal_shell_sidebar); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_cal_shell_sidebar_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_cal_shell_sidebar_register_type (type_module); +} + +GtkWidget * +e_cal_shell_sidebar_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_CAL_SHELL_SIDEBAR, + "shell-view", shell_view, NULL); +} + +ECalendar * +e_cal_shell_sidebar_get_date_navigator (ECalShellSidebar *cal_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL); + + return E_CALENDAR (cal_shell_sidebar->priv->date_navigator); +} + +ECalClient * +e_cal_shell_sidebar_get_default_client (ECalShellSidebar *cal_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL); + + return (ECalClient *) cal_shell_sidebar->priv->default_client; +} + +ESourceSelector * +e_cal_shell_sidebar_get_selector (ECalShellSidebar *cal_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL); + + return E_SOURCE_SELECTOR (cal_shell_sidebar->priv->selector); +} + +void +e_cal_shell_sidebar_add_client (ECalShellSidebar *cal_shell_sidebar, + EClient *client) +{ + ESource *source; + ESourceSelector *selector; + + g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + source = e_client_get_source (client); + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + e_source_selector_select_source (selector, source); + + cal_shell_sidebar_emit_client_added (cal_shell_sidebar, client); +} + +void +e_cal_shell_sidebar_add_source (ECalShellSidebar *cal_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + ConnectClosure *closure; + + g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + e_source_selector_select_source (selector, source); + + closure = connect_closure_new (cal_shell_sidebar, source); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + cal_shell_sidebar_client_connect_cb, closure); +} + +void +e_cal_shell_sidebar_remove_source (ECalShellSidebar *cal_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + EClient *client; + + g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + cal_shell_sidebar_emit_client_removed ( + cal_shell_sidebar, client); + g_object_unref (client); + } +} diff --git a/modules/calendar/e-cal-shell-sidebar.h b/modules/calendar/e-cal-shell-sidebar.h new file mode 100644 index 0000000000..f751e61892 --- /dev/null +++ b/modules/calendar/e-cal-shell-sidebar.h @@ -0,0 +1,104 @@ +/* + * e-cal-shell-sidebar.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_SIDEBAR_H +#define E_CAL_SHELL_SIDEBAR_H + +#include <libecal/libecal.h> + +#include <shell/e-shell-sidebar.h> +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_CAL_SHELL_SIDEBAR \ + (e_cal_shell_sidebar_get_type ()) +#define E_CAL_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebar)) +#define E_CAL_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarClass)) +#define E_IS_CAL_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_SHELL_SIDEBAR)) +#define E_IS_CAL_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_SHELL_SIDEBAR)) +#define E_CAL_SHELL_SIDEBAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarClass)) + +G_BEGIN_DECLS + +typedef struct _ECalShellSidebar ECalShellSidebar; +typedef struct _ECalShellSidebarClass ECalShellSidebarClass; +typedef struct _ECalShellSidebarPrivate ECalShellSidebarPrivate; + +enum { + E_CAL_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0, + E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE = 1 << 1, + E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE = 1 << 2, + E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE = 1 << 3, + E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE = 1 << 4, + E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION = 1 << 5, + E_CAL_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH = 1 << 6 +}; + +struct _ECalShellSidebar { + EShellSidebar parent; + ECalShellSidebarPrivate *priv; +}; + +struct _ECalShellSidebarClass { + EShellSidebarClass parent_class; + + /* Signals */ + void (*client_added) (ECalShellSidebar *cal_shell_sidebar, + ECalClient *client); + void (*client_removed) + (ECalShellSidebar *cal_shell_sidebar, + ECalClient *client); +}; + +GType e_cal_shell_sidebar_get_type (void); +void e_cal_shell_sidebar_type_register + (GTypeModule *type_module); +GtkWidget * e_cal_shell_sidebar_new (EShellView *shell_view); +ECalendar * e_cal_shell_sidebar_get_date_navigator + (ECalShellSidebar *cal_shell_sidebar); +ECalClient * e_cal_shell_sidebar_get_default_client + (ECalShellSidebar *cal_shell_sidebar); +ESourceSelector * + e_cal_shell_sidebar_get_selector + (ECalShellSidebar *cal_shell_sidebar); +void e_cal_shell_sidebar_add_client + (ECalShellSidebar *cal_shell_sidebar, + EClient *client); +void e_cal_shell_sidebar_add_source + (ECalShellSidebar *cal_shell_sidebar, + ESource *source); +void e_cal_shell_sidebar_remove_source + (ECalShellSidebar *cal_shell_sidebar, + ESource *source); + +G_END_DECLS + +#endif /* E_CAL_SHELL_SIDEBAR_H */ diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c new file mode 100644 index 0000000000..b59a3f3ef4 --- /dev/null +++ b/modules/calendar/e-cal-shell-view-actions.c @@ -0,0 +1,2021 @@ +/* + * e-cal-shell-view-actions.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-view-private.h" + +/* This is for radio action groups whose value is persistent. We + * initialize it to a bogus value to ensure a "changed" signal is + * emitted when a valid value is restored. */ +#define BOGUS_INITIAL_VALUE G_MININT + +static void +action_calendar_copy_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellSidebar *cal_shell_sidebar; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ESourceSelector *selector; + ESource *source; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + copy_source_dialog ( + GTK_WINDOW (shell_window), registry, + source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS); + + g_object_unref (source); +} + +static void +action_calendar_delete_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellSidebar *cal_shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + ESource *source; + ESourceSelector *selector; + gint response; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + if (e_source_get_remote_deletable (source)) { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-remote-calendar", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remote_delete_source (shell_view, source); + + } else { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-calendar", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remove_source (shell_view, source); + } + + g_object_unref (source); +} + +static void +action_calendar_go_back_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + gnome_calendar_previous (calendar); +} + +static void +action_calendar_go_forward_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + gnome_calendar_next (calendar); +} + +static void +action_calendar_go_today_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + gnome_calendar_goto_today (calendar); +} + +static void +action_calendar_jump_to_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShellWindow *shell_window; + EShellView *shell_view; + GnomeCalendar *calendar; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + goto_dialog (GTK_WINDOW (shell_window), calendar); +} + +static void +action_calendar_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Calendar")); + + gtk_widget_show (dialog); +} + +static void +action_calendar_print_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *view; + GtkPrintOperationAction print_action; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + view = gnome_calendar_get_calendar_view (calendar, view_type); + print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG; + + if (E_IS_CAL_LIST_VIEW (view)) { + ETable *table; + + table = E_CAL_LIST_VIEW (view)->table; + print_table (table, _("Print"), _("Calendar"), print_action); + } else { + time_t start; + + gnome_calendar_get_current_time_range (calendar, &start, NULL); + print_calendar (calendar, print_action, start); + } +} + +static void +action_calendar_print_preview_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *view; + GtkPrintOperationAction print_action; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + view = gnome_calendar_get_calendar_view (calendar, view_type); + print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW; + + if (E_IS_CAL_LIST_VIEW (view)) { + ETable *table; + + table = E_CAL_LIST_VIEW (view)->table; + print_table (table, _("Print"), _("Calendar"), print_action); + } else { + time_t start; + + gnome_calendar_get_current_time_range (calendar, &start, NULL); + print_calendar (calendar, print_action, start); + } +} + +static void +action_calendar_properties_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellSidebar *cal_shell_sidebar; + ECalClientSourceType source_type; + ESource *source; + ESourceSelector *selector; + ESourceRegistry *registry; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + registry = e_source_selector_get_registry (selector); + config = e_cal_source_config_new (registry, source, source_type); + + g_object_unref (source); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Calendar Properties")); + + gtk_widget_show (dialog); +} + +static void +action_calendar_purge_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + GtkSpinButton *spin_button; + GtkWidget *container; + GtkWidget *dialog; + GtkWidget *widget; + gint days; + time_t tt; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + dialog = gtk_message_dialog_new ( + GTK_WINDOW (shell_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK_CANCEL, + _("This operation will permanently erase all events older " + "than the selected amount of time. If you continue, you " + "will not be able to recover these events.")); + + gtk_dialog_set_default_response ( + GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); + + container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6); + gtk_widget_show (widget); + + container = widget; + + /* Translators: This is the first part of the sentence: + * "Purge events older than <<spin-button>> days" */ + widget = gtk_label_new (_("Purge events older than")); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6); + gtk_widget_show (widget); + + widget = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 60.0); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 6); + gtk_widget_show (widget); + + spin_button = GTK_SPIN_BUTTON (widget); + + /* Translators: This is the last part of the sentence: + * "Purge events older than <<spin-button>> days" */ + widget = gtk_label_new (_("days")); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 6); + gtk_widget_show (widget); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) + goto exit; + + days = gtk_spin_button_get_value_as_int (spin_button); + + tt = time (NULL); + tt -= (days * (24 * 3600)); + + gnome_calendar_purge (calendar, tt); + +exit: + gtk_widget_destroy (dialog); +} + +static void +action_calendar_refresh_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellSidebar *cal_shell_sidebar; + ESourceSelector *selector; + EClient *client = NULL; + ESource *source; + GError *error = NULL; + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + + if (source != NULL) { + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + g_object_unref (source); + } + + if (client == NULL) + return; + + g_return_if_fail (e_client_check_refresh_supported (client)); + + e_client_refresh_sync (client, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to refresh '%s', %s", + G_STRFUNC, e_source_get_display_name (source), + error->message); + g_error_free (error); + } + + g_object_unref (client); +} + +static void +action_calendar_rename_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellSidebar *cal_shell_sidebar; + ESourceSelector *selector; + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + e_source_selector_edit_primary_selection (selector); +} + +static void +action_calendar_search_next_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_events (cal_shell_view, TRUE); +} + +static void +action_calendar_search_prev_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_events (cal_shell_view, FALSE); +} + +static void +action_calendar_search_stop_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_stop (cal_shell_view); +} + +static void +action_calendar_select_one_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellSidebar *cal_shell_sidebar; + ESourceSelector *selector; + ESource *primary; + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar); + + primary = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (primary != NULL); + + e_source_selector_select_exclusive (selector, primary); + + g_object_unref (primary); +} + +static void +action_calendar_view_cb (GtkRadioAction *action, + GtkRadioAction *current, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + GnomeCalendarViewType view_type; + const gchar *view_id; + + shell_view = E_SHELL_VIEW (cal_shell_view); + view_type = gtk_radio_action_get_current_value (action); + + switch (view_type) { + case GNOME_CAL_DAY_VIEW: + view_id = "Day_View"; + break; + + case GNOME_CAL_WORK_WEEK_VIEW: + view_id = "Work_Week_View"; + break; + + case GNOME_CAL_WEEK_VIEW: + view_id = "Week_View"; + break; + + case GNOME_CAL_MONTH_VIEW: + view_id = "Month_View"; + break; + + case GNOME_CAL_LIST_VIEW: + view_id = "List_View"; + break; + + default: + g_return_if_reached (); + } + + e_shell_view_set_view_id (shell_view, view_id); +} + +static void +action_event_all_day_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + + /* These are just for readability. */ + gboolean all_day = TRUE; + gboolean meeting = FALSE; + gboolean no_past_date = FALSE; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_calendar_view_new_appointment_full ( + calendar_view, all_day, meeting, no_past_date); +} + +static void +action_event_copy_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ESource *source_source = NULL; + ESource *destination_source = NULL; + ESourceRegistry *registry; + EClient *destination_client = NULL; + GList *selected, *iter; + GError *error = NULL; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + registry = gnome_calendar_get_registry (calendar); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (selected != NULL); + + if (selected->data) { + ECalendarViewEvent *event = selected->data; + + if (is_comp_data_valid (event) && event->comp_data->client) + source_source = e_client_get_source ( + E_CLIENT (event->comp_data->client)); + } + + /* Get a destination source from the user. */ + destination_source = select_source_dialog ( + GTK_WINDOW (shell_window), registry, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, source_source); + if (destination_source == NULL) + return; + + /* Open the destination calendar. */ + destination_client = e_cal_client_connect_sync ( + destination_source, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to open destination client: %s", + G_STRFUNC, error->message); + g_error_free (error); + goto exit; + } + + e_cal_shell_view_set_status_message ( + cal_shell_view, _("Copying Items"), -1.0); + + for (iter = selected; iter != NULL; iter = iter->next) { + ECalendarViewEvent *event = iter->data; + gboolean remove = FALSE; + + e_cal_shell_view_transfer_item_to ( + cal_shell_view, event, + E_CAL_CLIENT (destination_client), remove); + } + + e_cal_shell_view_set_status_message (cal_shell_view, NULL, -1.0); + +exit: + if (destination_client != NULL) + g_object_unref (destination_client); + if (destination_source != NULL) + g_object_unref (destination_source); + g_list_free (selected); +} + +static void +action_event_delegate_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ESourceRegistry *registry; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ECalendarViewEvent *event; + ECalComponent *component; + ECalClient *client; + ECalModel *model; + GList *selected; + icalcomponent *clone; + icalproperty *property; + gboolean found = FALSE; + gchar *attendee; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + model = e_calendar_view_get_model (calendar_view); + registry = e_cal_model_get_registry (model); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + clone = icalcomponent_new_clone (event->comp_data->icalcomp); + + /* Set the attendee status for the delegate. */ + + component = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + component, icalcomponent_new_clone (clone)); + + attendee = itip_get_comp_attendee ( + registry, component, client); + property = icalcomponent_get_first_property ( + clone, ICAL_ATTENDEE_PROPERTY); + + while (property != NULL) { + const gchar *candidate; + + candidate = icalproperty_get_attendee (property); + candidate = itip_strip_mailto (candidate); + + if (g_ascii_strcasecmp (candidate, attendee) == 0) { + icalparameter *parameter; + + parameter = icalparameter_new_role ( + ICAL_ROLE_NONPARTICIPANT); + icalproperty_set_parameter (property, parameter); + + parameter = icalparameter_new_partstat ( + ICAL_PARTSTAT_DELEGATED); + icalproperty_set_parameter (property, parameter); + + found = TRUE; + break; + } + + property = icalcomponent_get_next_property ( + clone, ICAL_ATTENDEE_PROPERTY); + } + + /* If the attendee is not already in the component, add it. */ + if (!found) { + icalparameter *parameter; + gchar *address; + + address = g_strdup_printf ("MAILTO:%s", attendee); + + property = icalproperty_new_attendee (address); + icalcomponent_add_property (clone, property); + + parameter = icalparameter_new_role (ICAL_ROLE_NONPARTICIPANT); + icalproperty_add_parameter (property, parameter); + + parameter = icalparameter_new_cutype (ICAL_CUTYPE_INDIVIDUAL); + icalproperty_add_parameter (property, parameter); + + parameter = icalparameter_new_rsvp (ICAL_RSVP_TRUE); + icalproperty_add_parameter (property, parameter); + + g_free (address); + } + + g_free (attendee); + g_object_unref (component); + + e_calendar_view_open_event_with_flags ( + calendar_view, event->comp_data->client, clone, + COMP_EDITOR_MEETING | COMP_EDITOR_DELEGATE); + + icalcomponent_free (clone); + g_list_free (selected); +} + +static void +action_event_delete_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_selectable_delete_selection (E_SELECTABLE (calendar_view)); +} + +static void +action_event_delete_occurrence_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_calendar_view_delete_selected_occurrence (calendar_view); +} + +static void +action_event_forward_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + ESourceRegistry *registry; + ECalendarViewEvent *event; + ECalComponent *component; + ECalClient *client; + icalcomponent *icalcomp; + GList *selected; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + registry = gnome_calendar_get_registry (calendar); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + component = e_cal_component_new (); + + e_cal_component_set_icalcomponent ( + component, icalcomponent_new_clone (icalcomp)); + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_PUBLISH, + component, client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (component); + + g_list_free (selected); +} + +static void +action_event_meeting_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + + /* These are just for readability. */ + gboolean all_day = FALSE; + gboolean meeting = TRUE; + gboolean no_past_date = FALSE; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_calendar_view_new_appointment_full ( + calendar_view, all_day, meeting, no_past_date); +} + +static void +action_event_move_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ESource *source_source = NULL; + ESource *destination_source = NULL; + ESourceRegistry *registry; + EClient *destination_client = NULL; + GList *selected, *iter; + GError *error = NULL; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + registry = gnome_calendar_get_registry (calendar); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (selected != NULL); + + if (selected->data) { + ECalendarViewEvent *event = selected->data; + + if (is_comp_data_valid (event) && event->comp_data->client) + source_source = e_client_get_source ( + E_CLIENT (event->comp_data->client)); + } + + /* Get a destination source from the user. */ + destination_source = select_source_dialog ( + GTK_WINDOW (shell_window), registry, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, source_source); + if (destination_source == NULL) + return; + + /* Open the destination calendar. */ + destination_client = e_cal_client_connect_sync ( + destination_source, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to open destination client: %s", + G_STRFUNC, error->message); + g_clear_error (&error); + goto exit; + } + + e_cal_shell_view_set_status_message ( + cal_shell_view, _("Moving Items"), -1.0); + + for (iter = selected; iter != NULL; iter = iter->next) { + ECalendarViewEvent *event = iter->data; + gboolean remove = TRUE; + + e_cal_shell_view_transfer_item_to ( + cal_shell_view, event, + E_CAL_CLIENT (destination_client), remove); + } + + e_cal_shell_view_set_status_message (cal_shell_view, NULL, -1.0); + +exit: + if (destination_client != NULL) + g_object_unref (destination_client); + if (destination_source != NULL) + g_object_unref (destination_source); + g_list_free (selected); +} + +static void +action_event_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_calendar_view_new_appointment (calendar_view); +} + +static void +action_event_occurrence_movable_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalModel *model; + ECalendarView *calendar_view; + ECalendarViewEvent *event; + ECalComponent *exception_component; + ECalComponent *recurring_component; + ECalComponentDateTime date; + ECalComponentId *id; + ECalClient *client; + icalcomponent *icalcomp; + icaltimetype itt; + icaltimezone *timezone; + GList *selected; + gchar *uid; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + model = e_calendar_view_get_model (calendar_view); + timezone = e_cal_model_get_timezone (model); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + /* For the recurring object, we add an exception + * to get rid of the instance. */ + + recurring_component = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + recurring_component, icalcomponent_new_clone (icalcomp)); + id = e_cal_component_get_id (recurring_component); + + /* For the unrecurred instance, we duplicate the original object, + * create a new UID for it, get rid of the recurrence rules, and + * set the start and end times to the instance times. */ + + exception_component = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + exception_component, icalcomponent_new_clone (icalcomp)); + + uid = e_cal_component_gen_uid (); + e_cal_component_set_uid (exception_component, uid); + g_free (uid); + + e_cal_component_set_recurid (exception_component, NULL); + e_cal_component_set_rdate_list (exception_component, NULL); + e_cal_component_set_rrule_list (exception_component, NULL); + e_cal_component_set_exdate_list (exception_component, NULL); + e_cal_component_set_exrule_list (exception_component, NULL); + + date.value = &itt; + date.tzid = icaltimezone_get_tzid (timezone); + *date.value = icaltime_from_timet_with_zone ( + event->comp_data->instance_start, FALSE, timezone); + cal_comp_set_dtstart_with_oldzone (client, exception_component, &date); + *date.value = icaltime_from_timet_with_zone ( + event->comp_data->instance_end, FALSE, timezone); + cal_comp_set_dtstart_with_oldzone (client, exception_component, &date); + e_cal_component_commit_sequence (exception_component); + + /* Now update both ECalComponents. Note that we do this last + * since at present the updates happend synchronously so our + * event may disappear. */ + + e_cal_client_remove_object_sync ( + client, id->uid, id->rid, CALOBJ_MOD_THIS, NULL, NULL); + + e_cal_component_free_id (id); + g_object_unref (recurring_component); + + icalcomp = e_cal_component_get_icalcomponent (exception_component); + if (e_cal_client_create_object_sync (client, icalcomp, &uid, NULL, NULL)) + g_free (uid); + + g_object_unref (exception_component); + + g_list_free (selected); +} + +static void +action_event_open_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *view; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + view = gnome_calendar_get_calendar_view (calendar, view_type); + + e_calendar_view_open_event (view); +} + +static void +action_event_print_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ECalendarViewEvent *event; + ECalComponent *component; + ECalModel *model; + ECalClient *client; + icalcomponent *icalcomp; + GList *selected; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + model = e_calendar_view_get_model (calendar_view); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + component = e_cal_component_new (); + + e_cal_component_set_icalcomponent ( + component, icalcomponent_new_clone (icalcomp)); + print_comp ( + component, client, + e_cal_model_get_timezone (model), + e_cal_model_get_use_24_hour_format (model), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); + + g_object_unref (component); + + g_list_free (selected); +} + +static void +action_event_reply_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + ECalendarViewEvent *event; + ECalComponent *component; + ECalClient *client; + ESourceRegistry *registry; + icalcomponent *icalcomp; + GList *selected; + gboolean reply_all = FALSE; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + registry = gnome_calendar_get_registry (calendar); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + component = e_cal_component_new (); + + e_cal_component_set_icalcomponent ( + component, icalcomponent_new_clone (icalcomp)); + reply_to_calendar_comp ( + registry, E_CAL_COMPONENT_METHOD_REPLY, + component, client, reply_all, NULL, NULL); + + g_object_unref (component); + + g_list_free (selected); +} + +static void +action_event_reply_all_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + ECalendarViewEvent *event; + ECalComponent *component; + ECalClient *client; + ESourceRegistry *registry; + icalcomponent *icalcomp; + GList *selected; + gboolean reply_all = TRUE; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + registry = gnome_calendar_get_registry (calendar); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + component = e_cal_component_new (); + + e_cal_component_set_icalcomponent ( + component, icalcomponent_new_clone (icalcomp)); + reply_to_calendar_comp ( + registry, E_CAL_COMPONENT_METHOD_REPLY, + component, client, reply_all, NULL, NULL); + + g_object_unref (component); + + g_list_free (selected); +} + +static void +action_event_save_as_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ECalendarViewEvent *event; + ECalClient *client; + icalcomponent *icalcomp; + EActivity *activity; + GList *selected; + GFile *file; + gchar *string = NULL; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + /* Translators: Default filename part saving an event to a file when + * no summary is filed, the '.ics' extension is concatenated to it. */ + string = icalcomp_suggest_filename (icalcomp, _("event")); + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), string, + "*.ics:text/calendar", NULL, NULL); + g_free (string); + if (file == NULL) + return; + + string = e_cal_client_get_component_as_string (client, icalcomp); + if (string == NULL) { + g_warning ("Could not convert item to a string"); + goto exit; + } + + /* XXX No callbacks means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + +exit: + g_object_unref (file); + + g_list_free (selected); +} + +static void +edit_event_as (ECalShellView *cal_shell_view, + gboolean as_meeting) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalendarView *calendar_view; + ECalendarViewEvent *event; + ECalClient *client; + icalcomponent *icalcomp; + GList *selected; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + selected = e_calendar_view_get_selected_events (calendar_view); + g_return_if_fail (g_list_length (selected) == 1); + + event = selected->data; + + if (!is_comp_data_valid (event)) + return; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + if (!as_meeting && icalcomp) { + /* remove organizer and all attendees */ + icalproperty *prop; + + /* do it on a copy, as user can cancel changes */ + icalcomp = icalcomponent_new_clone (icalcomp); + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + while (prop != NULL) { + icalcomponent_remove_property (icalcomp, prop); + icalproperty_free (prop); + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + } + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ORGANIZER_PROPERTY); + while (prop != NULL) { + icalcomponent_remove_property (icalcomp, prop); + icalproperty_free (prop); + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ORGANIZER_PROPERTY); + } + } + + e_calendar_view_edit_appointment ( + calendar_view, client, icalcomp, as_meeting ? + EDIT_EVENT_FORCE_MEETING : EDIT_EVENT_FORCE_APPOINTMENT); + + if (!as_meeting && icalcomp) { + icalcomponent_free (icalcomp); + } + + g_list_free (selected); +} + +static void +action_event_schedule_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + edit_event_as (cal_shell_view, TRUE); +} + +static void +quit_calendar_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + GdkWindow *window; + GdkEvent *event; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + /* Synthesize a delete_event on this window. */ + event = gdk_event_new (GDK_DELETE); + window = gtk_widget_get_window (GTK_WIDGET (shell_window)); + event->any.window = g_object_ref (window); + event->any.send_event = TRUE; + gtk_main_do_event (event); + gdk_event_free (event); + +} + +static void +action_event_schedule_appointment_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + edit_event_as (cal_shell_view, FALSE); +} + +static GtkActionEntry calendar_entries[] = { + + { "calendar-copy", + GTK_STOCK_COPY, + N_("_Copy..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_copy_cb) }, + + { "calendar-delete", + GTK_STOCK_DELETE, + N_("D_elete Calendar"), + NULL, + N_("Delete the selected calendar"), + G_CALLBACK (action_calendar_delete_cb) }, + + { "calendar-go-back", + GTK_STOCK_GO_BACK, + N_("Previous"), + NULL, + N_("Go Back"), + G_CALLBACK (action_calendar_go_back_cb) }, + + { "calendar-go-forward", + GTK_STOCK_GO_FORWARD, + N_("Next"), + NULL, + N_("Go Forward"), + G_CALLBACK (action_calendar_go_forward_cb) }, + + { "calendar-go-today", + "go-today", + N_("Select _Today"), + "<Control>t", + N_("Select today"), + G_CALLBACK (action_calendar_go_today_cb) }, + + { "calendar-jump-to", + GTK_STOCK_JUMP_TO, + N_("Select _Date"), + "<Control>g", + N_("Select a specific date"), + G_CALLBACK (action_calendar_jump_to_cb) }, + + { "calendar-new", + "x-office-calendar", + N_("_New Calendar"), + NULL, + N_("Create a new calendar"), + G_CALLBACK (action_calendar_new_cb) }, + + { "calendar-properties", + GTK_STOCK_PROPERTIES, + NULL, + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_properties_cb) }, + + { "calendar-purge", + NULL, + N_("Purg_e"), + "<Control>e", + N_("Purge old appointments and meetings"), + G_CALLBACK (action_calendar_purge_cb) }, + + { "calendar-refresh", + GTK_STOCK_REFRESH, + N_("Re_fresh"), + NULL, + N_("Refresh the selected calendar"), + G_CALLBACK (action_calendar_refresh_cb) }, + + { "calendar-rename", + NULL, + N_("_Rename..."), + "F2", + N_("Rename the selected calendar"), + G_CALLBACK (action_calendar_rename_cb) }, + + { "calendar-search-next", + GTK_STOCK_GO_FORWARD, + N_("Find _next"), + "<Control><Shift>n", + N_("Find next occurrence of the current search string"), + G_CALLBACK (action_calendar_search_next_cb) }, + + { "calendar-search-prev", + GTK_STOCK_GO_BACK, + N_("Find _previous"), + "<Control><Shift>p", + N_("Find previous occurrence of the current search string"), + G_CALLBACK (action_calendar_search_prev_cb) }, + + { "calendar-search-stop", + GTK_STOCK_STOP, + N_("Stop _running search"), + NULL, + N_("Stop currently running search"), + G_CALLBACK (action_calendar_search_stop_cb) }, + + { "calendar-select-one", + "stock_check-filled", + N_("Show _Only This Calendar"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_select_one_cb) }, + + { "event-copy", + NULL, + N_("Cop_y to Calendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_copy_cb) }, + + { "event-delegate", + NULL, + N_("_Delegate Meeting..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_delegate_cb) }, + + { "event-delete", + GTK_STOCK_DELETE, + N_("_Delete Appointment"), + "<Control>d", + N_("Delete selected appointments"), + G_CALLBACK (action_event_delete_cb) }, + + { "event-delete-occurrence", + GTK_STOCK_DELETE, + N_("Delete This _Occurrence"), + NULL, + N_("Delete this occurrence"), + G_CALLBACK (action_event_delete_occurrence_cb) }, + + { "event-delete-occurrence-all", + GTK_STOCK_DELETE, + N_("Delete All Occ_urrences"), + NULL, + N_("Delete all occurrences"), + G_CALLBACK (action_event_delete_cb) }, + + { "event-all-day-new", + "stock_new-24h-appointment", + N_("New All Day _Event..."), + NULL, + N_("Create a new all day event"), + G_CALLBACK (action_event_all_day_new_cb) }, + + { "event-forward", + "mail-forward", + N_("_Forward as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_forward_cb) }, + + { "event-meeting-new", + "stock_new-meeting", + N_("New _Meeting..."), + NULL, + N_("Create a new meeting"), + G_CALLBACK (action_event_meeting_new_cb) }, + + { "event-move", + NULL, + N_("Mo_ve to Calendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_move_cb) }, + + { "event-new", + "appointment-new", + N_("New _Appointment..."), + NULL, + N_("Create a new appointment"), + G_CALLBACK (action_event_new_cb) }, + + { "event-occurrence-movable", + NULL, + N_("Make this Occurrence _Movable"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_occurrence_movable_cb) }, + + { "event-open", + GTK_STOCK_OPEN, + N_("_Open Appointment"), + "<Control>o", + N_("View the current appointment"), + G_CALLBACK (action_event_open_cb) }, + + { "event-reply", + "mail-reply-sender", + N_("_Reply"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_reply_cb) }, + + { "event-reply-all", + "mail-reply-all", + N_("Reply to _All"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_reply_all_cb) }, + + { "event-schedule", + NULL, + N_("_Schedule Meeting..."), + NULL, + N_("Converts an appointment to a meeting"), + G_CALLBACK (action_event_schedule_cb) }, + + { "event-schedule-appointment", + NULL, + N_("Conv_ert to Appointment..."), + NULL, + N_("Converts a meeting to an appointment"), + G_CALLBACK (action_event_schedule_appointment_cb) }, + + { "quit-calendar", + GTK_STOCK_CLOSE, + N_("Quit"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (quit_calendar_cb) }, + + /*** Menus ***/ + + { "calendar-actions-menu", + NULL, + N_("_Actions"), + NULL, + NULL, + NULL } +}; + +static EPopupActionEntry calendar_popup_entries[] = { + + /* FIXME No equivalent main menu items for the any of the calendar + * popup menu items and for many of the event popup menu items. + * This is an accessibility issue. */ + + { "calendar-popup-copy", + NULL, + "calendar-copy" }, + + { "calendar-popup-delete", + N_("_Delete"), + "calendar-delete" }, + + { "calendar-popup-go-today", + NULL, + "calendar-go-today" }, + + { "calendar-popup-jump-to", + NULL, + "calendar-jump-to" }, + + { "calendar-popup-properties", + NULL, + "calendar-properties" }, + + { "calendar-popup-refresh", + NULL, + "calendar-refresh" }, + + { "calendar-popup-rename", + NULL, + "calendar-rename" }, + + { "calendar-popup-select-one", + NULL, + "calendar-select-one" }, + + { "event-popup-copy", + NULL, + "event-copy" }, + + { "event-popup-delegate", + NULL, + "event-delegate" }, + + { "event-popup-delete", + NULL, + "event-delete" }, + + { "event-popup-delete-occurrence", + NULL, + "event-delete-occurrence" }, + + { "event-popup-delete-occurrence-all", + NULL, + "event-delete-occurrence-all" }, + + { "event-popup-forward", + NULL, + "event-forward" }, + + { "event-popup-move", + NULL, + "event-move" }, + + { "event-popup-occurrence-movable", + NULL, + "event-occurrence-movable" }, + + { "event-popup-open", + NULL, + "event-open" }, + + { "event-popup-reply", + NULL, + "event-reply" }, + + { "event-popup-reply-all", + NULL, + "event-reply-all" }, + + { "event-popup-schedule", + NULL, + "event-schedule" }, + + { "event-popup-schedule-appointment", + NULL, + "event-schedule-appointment" } +}; + +static GtkRadioActionEntry calendar_view_entries[] = { + + /* This action represents the initial calendar view. + * It should not be visible in the UI, nor should it be + * possible to switch to it from another calendar view. */ + { "calendar-view-initial", + NULL, + NULL, + NULL, + NULL, + BOGUS_INITIAL_VALUE }, + + { "calendar-view-day", + "view-calendar-day", + N_("Day"), + NULL, + N_("Show one day"), + GNOME_CAL_DAY_VIEW }, + + { "calendar-view-list", + "view-calendar-list", + N_("List"), + NULL, + N_("Show as list"), + GNOME_CAL_LIST_VIEW }, + + { "calendar-view-month", + "view-calendar-month", + N_("Month"), + NULL, + N_("Show one month"), + GNOME_CAL_MONTH_VIEW }, + + { "calendar-view-week", + "view-calendar-week", + N_("Week"), + NULL, + N_("Show one week"), + GNOME_CAL_WEEK_VIEW }, + + { "calendar-view-workweek", + "view-calendar-workweek", + N_("Work Week"), + NULL, + N_("Show one work week"), + GNOME_CAL_WORK_WEEK_VIEW } +}; + +static GtkRadioActionEntry calendar_filter_entries[] = { + + { "calendar-filter-active-appointments", + NULL, + N_("Active Appointments"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_FILTER_ACTIVE_APPOINTMENTS }, + + { "calendar-filter-any-category", + NULL, + N_("Any Category"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_FILTER_ANY_CATEGORY }, + + { "calendar-filter-next-7-days-appointments", + NULL, + N_("Next 7 Days' Appointments"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS }, + + { "calendar-filter-occurs-less-than-5-times", + NULL, + N_("Occurs Less Than 5 Times"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_FILTER_OCCURS_LESS_THAN_5_TIMES }, + + { "calendar-filter-unmatched", + NULL, + N_("Unmatched"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_FILTER_UNMATCHED } +}; + +static GtkRadioActionEntry calendar_search_entries[] = { + + { "calendar-search-advanced-hidden", + NULL, + N_("Advanced Search"), + NULL, + NULL, + CALENDAR_SEARCH_ADVANCED }, + + { "calendar-search-any-field-contains", + NULL, + N_("Any field contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_SEARCH_ANY_FIELD_CONTAINS }, + + { "calendar-search-description-contains", + NULL, + N_("Description contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_SEARCH_DESCRIPTION_CONTAINS }, + + { "calendar-search-summary-contains", + NULL, + N_("Summary contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + CALENDAR_SEARCH_SUMMARY_CONTAINS } +}; + +static GtkActionEntry lockdown_printing_entries[] = { + + { "calendar-print", + GTK_STOCK_PRINT, + NULL, + "<Control>p", + N_("Print this calendar"), + G_CALLBACK (action_calendar_print_cb) }, + + { "calendar-print-preview", + GTK_STOCK_PRINT_PREVIEW, + NULL, + NULL, + N_("Preview the calendar to be printed"), + G_CALLBACK (action_calendar_print_preview_cb) }, + + { "event-print", + GTK_STOCK_PRINT, + NULL, + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_print_cb) } +}; + +static EPopupActionEntry lockdown_printing_popup_entries[] = { + + { "event-popup-print", + NULL, + "event-print" } +}; + +static GtkActionEntry lockdown_save_to_disk_entries[] = { + + { "event-save-as", + GTK_STOCK_SAVE_AS, + N_("_Save as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_event_save_as_cb) }, +}; + +static EPopupActionEntry lockdown_save_to_disk_popup_entries[] = { + + { "event-popup-save-as", + NULL, + "event-save-as" }, +}; + +void +e_cal_shell_view_actions_init (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + GtkActionGroup *action_group; + GtkAction *action; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + searchbar = e_cal_shell_content_get_searchbar (cal_shell_content); + + /* Calendar Actions */ + action_group = ACTION_GROUP (CALENDAR); + gtk_action_group_add_actions ( + action_group, calendar_entries, + G_N_ELEMENTS (calendar_entries), cal_shell_view); + e_action_group_add_popup_actions ( + action_group, calendar_popup_entries, + G_N_ELEMENTS (calendar_popup_entries)); + gtk_action_group_add_radio_actions ( + action_group, calendar_view_entries, + G_N_ELEMENTS (calendar_view_entries), BOGUS_INITIAL_VALUE, + G_CALLBACK (action_calendar_view_cb), cal_shell_view); + gtk_action_group_add_radio_actions ( + action_group, calendar_search_entries, + G_N_ELEMENTS (calendar_search_entries), + -1, NULL, NULL); + + /* Advanced Search Action */ + action = ACTION (CALENDAR_SEARCH_ADVANCED_HIDDEN); + gtk_action_set_visible (action, FALSE); + if (searchbar) + e_shell_searchbar_set_search_option ( + searchbar, GTK_RADIO_ACTION (action)); + + /* Lockdown Printing Actions */ + action_group = ACTION_GROUP (LOCKDOWN_PRINTING); + gtk_action_group_add_actions ( + action_group, lockdown_printing_entries, + G_N_ELEMENTS (lockdown_printing_entries), cal_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_printing_popup_entries, + G_N_ELEMENTS (lockdown_printing_popup_entries)); + + /* Lockdown Save-to-Disk Actions */ + action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK); + gtk_action_group_add_actions ( + action_group, lockdown_save_to_disk_entries, + G_N_ELEMENTS (lockdown_save_to_disk_entries), cal_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_save_to_disk_popup_entries, + G_N_ELEMENTS (lockdown_save_to_disk_popup_entries)); + + /* Fine tuning. */ + + action = ACTION (CALENDAR_GO_TODAY); + gtk_action_set_short_label (action, _("Today")); + + action = ACTION (CALENDAR_JUMP_TO); + gtk_action_set_short_label (action, _("Go To")); + + action = ACTION (CALENDAR_VIEW_DAY); + gtk_action_set_is_important (action, TRUE); + + action = ACTION (CALENDAR_VIEW_LIST); + gtk_action_set_is_important (action, TRUE); + + action = ACTION (CALENDAR_VIEW_MONTH); + gtk_action_set_is_important (action, TRUE); + + action = ACTION (CALENDAR_VIEW_WEEK); + gtk_action_set_is_important (action, TRUE); + + action = ACTION (CALENDAR_VIEW_WORKWEEK); + gtk_action_set_is_important (action, TRUE); + + /* Initialize the memo and task pad actions. */ + e_cal_shell_view_memopad_actions_init (cal_shell_view); + e_cal_shell_view_taskpad_actions_init (cal_shell_view); +} + +void +e_cal_shell_view_update_search_filter (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GtkActionGroup *action_group; + GtkRadioAction *radio_action; + GList *list, *iter; + GSList *group; + gint ii; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + action_group = ACTION_GROUP (CALENDAR_FILTER); + e_action_group_remove_all_actions (action_group); + + /* Add the standard filter actions. No callback is needed + * because changes in the EActionComboBox are detected and + * handled by EShellSearchbar. */ + gtk_action_group_add_radio_actions ( + action_group, calendar_filter_entries, + G_N_ELEMENTS (calendar_filter_entries), + CALENDAR_FILTER_ANY_CATEGORY, NULL, NULL); + + /* Retrieve the radio group from an action we just added. */ + list = gtk_action_group_list_actions (action_group); + radio_action = GTK_RADIO_ACTION (list->data); + group = gtk_radio_action_get_group (radio_action); + g_list_free (list); + + /* Build the category actions. */ + + list = e_util_get_searchable_categories (); + for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) { + const gchar *category_name = iter->data; + const gchar *filename; + GtkAction *action; + gchar *action_name; + + action_name = g_strdup_printf ( + "calendar-filter-category-%d", ii); + radio_action = gtk_radio_action_new ( + action_name, category_name, NULL, NULL, ii); + g_free (action_name); + + /* Convert the category icon file to a themed icon name. */ + filename = e_categories_get_icon_file_for (category_name); + if (filename != NULL && *filename != '\0') { + gchar *basename; + gchar *cp; + + basename = g_path_get_basename (filename); + + /* Lose the file extension. */ + if ((cp = strrchr (basename, '.')) != NULL) + *cp = '\0'; + + g_object_set ( + radio_action, "icon-name", basename, NULL); + + g_free (basename); + } + + gtk_radio_action_set_group (radio_action, group); + group = gtk_radio_action_get_group (radio_action); + + /* The action group takes ownership of the action. */ + action = GTK_ACTION (radio_action); + gtk_action_group_add_action (action_group, action); + g_object_unref (radio_action); + } + g_list_free (list); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + searchbar = e_cal_shell_content_get_searchbar (cal_shell_content); + if (searchbar) { + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + + e_shell_view_block_execute_search (shell_view); + + /* Use any action in the group; doesn't matter which. */ + e_action_combo_box_set_action (combo_box, radio_action); + + ii = CALENDAR_FILTER_UNMATCHED; + e_action_combo_box_add_separator_after (combo_box, ii); + + ii = CALENDAR_FILTER_OCCURS_LESS_THAN_5_TIMES; + e_action_combo_box_add_separator_after (combo_box, ii); + + e_shell_view_unblock_execute_search (shell_view); + } +} diff --git a/modules/calendar/e-cal-shell-view-actions.h b/modules/calendar/e-cal-shell-view-actions.h new file mode 100644 index 0000000000..079f0ab546 --- /dev/null +++ b/modules/calendar/e-cal-shell-view-actions.h @@ -0,0 +1,163 @@ +/* + * e-cal-shell-view-actions.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_VIEW_ACTIONS_H +#define E_CAL_SHELL_VIEW_ACTIONS_H + +#include <shell/e-shell-window-actions.h> + +/* Calendar Actions */ +#define E_SHELL_WINDOW_ACTION_CALENDAR_COPY(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-copy") +#define E_SHELL_WINDOW_ACTION_CALENDAR_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-delete") +#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_BACK(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-go-back") +#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-go-forward") +#define E_SHELL_WINDOW_ACTION_CALENDAR_GO_TODAY(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-go-today") +#define E_SHELL_WINDOW_ACTION_CALENDAR_JUMP_TO(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-jump-to") +#define E_SHELL_WINDOW_ACTION_CALENDAR_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-new") +#define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-print") +#define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-print-preview") +#define E_SHELL_WINDOW_ACTION_CALENDAR_PROPERTIES(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-properties") +#define E_SHELL_WINDOW_ACTION_CALENDAR_PURGE(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-purge") +#define E_SHELL_WINDOW_ACTION_CALENDAR_REFRESH(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-refresh") +#define E_SHELL_WINDOW_ACTION_CALENDAR_RENAME(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-rename") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_PREV(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-prev") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_NEXT(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-next") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_STOP(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-stop") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SELECT_ONE(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-select-one") +#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_DAY(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-view-day") +#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_LIST(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-view-list") +#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_MONTH(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-view-month") +#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_WEEK(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-view-week") +#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_WORKWEEK(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-view-workweek") + +/* Event Actions */ +#define E_SHELL_WINDOW_ACTION_EVENT_DELEGATE(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-delegate") +#define E_SHELL_WINDOW_ACTION_EVENT_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-delete") +#define E_SHELL_WINDOW_ACTION_EVENT_DELETE_OCCURRENCE(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-delete-occurrence") +#define E_SHELL_WINDOW_ACTION_EVENT_DELETE_OCCURRENCE_ALL(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-delete-occurrence-all") +#define E_SHELL_WINDOW_ACTION_EVENT_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-forward") +#define E_SHELL_WINDOW_ACTION_EVENT_OPEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-open") +#define E_SHELL_WINDOW_ACTION_EVENT_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-print") +#define E_SHELL_WINDOW_ACTION_EVENT_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-save-as") +#define E_SHELL_WINDOW_ACTION_EVENT_SCHEDULE(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-schedule") +#define E_SHELL_WINDOW_ACTION_EVENT_SCHEDULE_APPOINTMENT(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-schedule-appointment") +#define E_SHELL_WINDOW_ACTION_EVENT_REPLY(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-reply") +#define E_SHELL_WINDOW_ACTION_EVENT_REPLY_ALL(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-reply-all") +#define E_SHELL_WINDOW_ACTION_EVENT_OCCURRENCE_MOVABLE(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-occurrence-movable") +#define E_SHELL_WINDOW_ACTION_EVENT_MEETING_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "event-meeting-new") + +/* Memo Pad Actions */ +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-forward") +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-new") +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_OPEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-open") +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_OPEN_URL(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-open-url") +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-print") +#define E_SHELL_WINDOW_ACTION_CALENDAR_MEMOPAD_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-memopad-save-as") + +/* Task Pad Actions */ +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_ASSIGN(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-assign") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-forward") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_MARK_COMPLETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-mark-complete") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_MARK_INCOMPLETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-mark-incomplete") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-new") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_OPEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-open") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_OPEN_URL(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-open-url") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-print") +#define E_SHELL_WINDOW_ACTION_CALENDAR_TASKPAD_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-taskpad-save-as") + +/* Calendar Query Actions */ +#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_ACTIVE_APPOINTMENTS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-filter-active-appointments") +#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_ANY_CATEGORY(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-filter-any-category") +#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-filter-next-7-days-appointments") +#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_OCCURS_LESS_THAN_5_TIMES(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-filter-occurs-less-than-5-times") +#define E_SHELL_WINDOW_ACTION_CALENDAR_FILTER_UNMATCHED(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-filter-unmatched") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_ADVANCED_HIDDEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-advanced-hidden") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_ANY_FIELD_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-any-field-contains") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_DESCRIPTION_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-description-contains") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_SUMMARY_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-summary-contains") + +/* Action Groups */ +#define E_SHELL_WINDOW_ACTION_GROUP_CALENDAR(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "calendar") +#define E_SHELL_WINDOW_ACTION_GROUP_CALENDAR_FILTER(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "calendar-filter") + +#endif /* E_CAL_SHELL_VIEW_ACTIONS_H */ diff --git a/modules/calendar/e-cal-shell-view-memopad.c b/modules/calendar/e-cal-shell-view-memopad.c new file mode 100644 index 0000000000..f8a0e2da0a --- /dev/null +++ b/modules/calendar/e-cal-shell-view-memopad.c @@ -0,0 +1,482 @@ +/* + * e-cal-shell-view-memopad.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-view-private.h" + +/* Much of this file is based on e-memo-shell-view-actions.c. */ + +static void +action_calendar_memopad_forward_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + ECalComponent *comp; + icalcomponent *clone; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only forward the first selected memo. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_PUBLISH, comp, + comp_data->client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (comp); +} + +static void +action_calendar_memopad_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + ECalClient *client; + ECalComponent *comp; + CompEditor *editor; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + client = comp_data->client; + comp = cal_comp_memo_new_with_defaults (client); + cal_comp_update_time_by_active_window (comp, shell); + editor = memo_editor_new (client, shell, COMP_EDITOR_NEW_ITEM); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); +} + +static void +action_calendar_memopad_open_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + GSList *list; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected memo. */ + e_cal_shell_view_memopad_open_memo (cal_shell_view, comp_data); +} + +static void +action_calendar_memopad_open_url_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + icalproperty *prop; + const gchar *uri; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the URI of the first selected memo. */ + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + g_return_if_fail (prop != NULL); + + uri = icalproperty_get_url (prop); + e_show_uri (GTK_WINDOW (shell_window), uri); +} + +static void +action_calendar_memopad_print_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + ECalComponent *comp; + ECalModel *model; + icalcomponent *clone; + GSList *list; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + model = e_memo_table_get_model (memo_table); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only print the first selected memo. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + print_comp ( + comp, comp_data->client, + e_cal_model_get_timezone (model), + e_cal_model_get_use_24_hour_format (model), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); + + g_object_unref (comp); +} + +static void +action_calendar_memopad_save_as_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + ECalShellContent *cal_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + EActivity *activity; + GSList *list; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* Translators: Default filename part saving a memo to a file when + * no summary is filed, the '.ics' extension is concatenated to it. */ + string = icalcomp_suggest_filename (comp_data->icalcomp, _("memo")); + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), string, + "*.ics:text/calendar", NULL, NULL); + g_free (string); + if (file == NULL) + return; + + /* XXX We only save the first selected memo. */ + string = e_cal_client_get_component_as_string ( + comp_data->client, comp_data->icalcomp); + if (string == NULL) { + g_warning ("Could not convert memo to a string."); + g_object_unref (file); + return; + } + + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); +} + +static GtkActionEntry calendar_memopad_entries[] = { + + { "calendar-memopad-forward", + "mail-forward", + N_("_Forward as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_memopad_forward_cb) }, + + { "calendar-memopad-new", + "stock_insert-note", + N_("New _Memo"), + NULL, + N_("Create a new memo"), + G_CALLBACK (action_calendar_memopad_new_cb) }, + + { "calendar-memopad-open", + GTK_STOCK_OPEN, + N_("_Open Memo"), + NULL, + N_("View the selected memo"), + G_CALLBACK (action_calendar_memopad_open_cb) }, + + { "calendar-memopad-open-url", + "applications-internet", + N_("Open _Web Page"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_memopad_open_url_cb) }, +}; + +static GtkActionEntry lockdown_printing_entries[] = { + + { "calendar-memopad-print", + GTK_STOCK_PRINT, + NULL, + NULL, + N_("Print the selected memo"), + G_CALLBACK (action_calendar_memopad_print_cb) } +}; + +static GtkActionEntry lockdown_save_to_disk_entries[] = { + + { "calendar-memopad-save-as", + GTK_STOCK_SAVE_AS, + N_("_Save as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_memopad_save_as_cb) } +}; + +void +e_cal_shell_view_memopad_actions_init (ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + GtkActionGroup *action_group; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + /* Calendar Actions */ + action_group = ACTION_GROUP (CALENDAR); + gtk_action_group_add_actions ( + action_group, calendar_memopad_entries, + G_N_ELEMENTS (calendar_memopad_entries), cal_shell_view); + + /* Lockdown Printing Actions */ + action_group = ACTION_GROUP (LOCKDOWN_PRINTING); + gtk_action_group_add_actions ( + action_group, lockdown_printing_entries, + G_N_ELEMENTS (lockdown_printing_entries), cal_shell_view); + + /* Lockdown Save-to-Disk Actions */ + action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK); + gtk_action_group_add_actions ( + action_group, lockdown_save_to_disk_entries, + G_N_ELEMENTS (lockdown_save_to_disk_entries), cal_shell_view); +} + +void +e_cal_shell_view_memopad_actions_update (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShellWindow *shell_window; + EShellView *shell_view; + EMemoTable *memo_table; + GtkAction *action; + GSList *list, *iter; + gboolean editable = TRUE; + gboolean has_url = FALSE; + gboolean sensitive; + gint n_selected; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + + n_selected = e_table_selected_count (E_TABLE (memo_table)); + + list = e_memo_table_get_selected (memo_table); + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + icalproperty *prop; + gboolean read_only; + + read_only = e_client_is_readonly (E_CLIENT (comp_data->client)); + editable &= !read_only; + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + has_url |= (prop != NULL); + } + g_slist_free (list); + + action = ACTION (CALENDAR_MEMOPAD_FORWARD); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_MEMOPAD_OPEN); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_MEMOPAD_OPEN_URL); + sensitive = (n_selected == 1) && has_url; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_MEMOPAD_PRINT); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_MEMOPAD_SAVE_AS); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); +} + +void +e_cal_shell_view_memopad_open_memo (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + CompEditor *editor; + CompEditorFlags flags = 0; + ECalComponent *comp; + icalcomponent *clone; + const gchar *uid; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + uid = icalcomponent_get_uid (comp_data->icalcomp); + editor = comp_editor_find_instance (uid); + + if (editor != NULL) + goto exit; + + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + if (e_cal_component_has_organizer (comp)) + flags |= COMP_EDITOR_IS_SHARED; + + if (itip_organizer_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + editor = memo_editor_new (comp_data->client, shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +exit: + gtk_window_present (GTK_WINDOW (editor)); +} + +void +e_cal_shell_view_memopad_set_status_message (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent) +{ + EActivity *activity; + EShellView *shell_view; + EShellBackend *shell_backend; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + + activity = cal_shell_view->priv->memopad_activity; + + if (status_message == NULL || *status_message == '\0') { + if (activity != NULL) { + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_object_unref (activity); + activity = NULL; + } + + } else if (activity == NULL) { + activity = e_activity_new (); + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + e_shell_backend_add_activity (shell_backend, activity); + + } else { + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + } + + cal_shell_view->priv->memopad_activity = activity; +} diff --git a/modules/calendar/e-cal-shell-view-private.c b/modules/calendar/e-cal-shell-view-private.c new file mode 100644 index 0000000000..6b93828f65 --- /dev/null +++ b/modules/calendar/e-cal-shell-view-private.c @@ -0,0 +1,1787 @@ +/* + * e-cal-shell-view-private.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-util/e-util-private.h" + +#include "e-cal-shell-view-private.h" + +#define CHECK_NB 5 + +/* be compatible with older e-d-s for MeeGo */ +#ifndef ETC_TIMEZONE +# define ETC_TIMEZONE "/etc/timezone" +# define ETC_TIMEZONE_MAJ "/etc/TIMEZONE" +# define ETC_RC_CONF "/etc/rc.conf" +# define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock" +# define ETC_CONF_D_CLOCK "/etc/conf.d/clock" +# define ETC_LOCALTIME "/etc/localtime" +#endif + +static const gchar * files_to_check[CHECK_NB] = { + ETC_TIMEZONE, + ETC_TIMEZONE_MAJ, + ETC_SYSCONFIG_CLOCK, + ETC_CONF_D_CLOCK, + ETC_LOCALTIME +}; + +static struct tm +cal_shell_view_get_current_time (ECalendarItem *calitem, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + struct icaltimetype tt; + icaltimezone *timezone; + ECalModel *model; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + model = e_cal_shell_content_get_model (cal_shell_content); + timezone = e_cal_model_get_timezone (model); + + tt = icaltime_from_timet_with_zone (time (NULL), FALSE, timezone); + + return icaltimetype_to_tm (&tt); +} + +static void +cal_shell_view_date_navigator_date_range_changed_cb (ECalShellView *cal_shell_view, + ECalendarItem *calitem) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + gnome_calendar_update_query (calendar); +} + +static void +cal_shell_view_date_navigator_selection_changed_cb (ECalShellView *cal_shell_view, + ECalendarItem *calitem) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType switch_to; + GnomeCalendarViewType view_type; + GnomeCalendar *calendar; + ECalModel *model; + GDate start_date, end_date; + GDate new_start_date, new_end_date; + icaltimetype tt; + icaltimezone *timezone; + time_t start, end, new_time; + gboolean starts_on_week_start_day; + gint new_days_shown, old_days_shown; + GDateWeekday week_start_day; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + model = gnome_calendar_get_model (calendar); + view_type = gnome_calendar_get_view (calendar); + switch_to = view_type; + + timezone = e_cal_model_get_timezone (model); + week_start_day = e_cal_model_get_week_start_day (model); + e_cal_model_get_time_range (model, &start, &end); + + time_to_gdate_with_zone (&start_date, start, timezone); + time_to_gdate_with_zone (&end_date, end, timezone); + + if (view_type == GNOME_CAL_MONTH_VIEW) { + EWeekView *week_view; + ECalendarView *calendar_view; + gboolean multi_week_view; + gboolean compress_weekend; + + calendar_view = gnome_calendar_get_calendar_view ( + calendar, GNOME_CAL_MONTH_VIEW); + + week_view = E_WEEK_VIEW (calendar_view); + multi_week_view = e_week_view_get_multi_week_view (week_view); + compress_weekend = e_week_view_get_compress_weekend (week_view); + + if (week_start_day == G_DATE_SUNDAY && + (!multi_week_view || compress_weekend)) + g_date_add_days (&start_date, 1); + } + + g_date_subtract_days (&end_date, 1); + + e_calendar_item_get_selection ( + calitem, &new_start_date, &new_end_date); + + /* If the selection hasn't changed, just return. */ + if (g_date_compare (&start_date, &new_start_date) == 0 && + g_date_compare (&end_date, &new_end_date) == 0) + return; + + old_days_shown = + g_date_get_julian (&end_date) - + g_date_get_julian (&start_date) + 1; + new_days_shown = + g_date_get_julian (&new_end_date) - + g_date_get_julian (&new_start_date) + 1; + + /* If a complete week is selected we show the week view. + * Note that if weekends are compressed and the week start + * day is set to Sunday, we don't actually show complete + * weeks in the week view, so this may need tweaking. */ + starts_on_week_start_day = + (g_date_get_weekday (&new_start_date) == week_start_day); + + /* Update selection to be in the new time range. */ + tt = icaltime_null_time (); + tt.year = g_date_get_year (&new_start_date); + tt.month = g_date_get_month (&new_start_date); + tt.day = g_date_get_day (&new_start_date); + new_time = icaltime_as_timet_with_zone (tt, timezone); + + /* Switch views as appropriate, and change the number of + * days or weeks shown. */ + if (view_type == GNOME_CAL_WORK_WEEK_VIEW && old_days_shown == new_days_shown) { + /* keep the work week view when has same days shown */ + switch_to = GNOME_CAL_WORK_WEEK_VIEW; + } else if (new_days_shown > 9) { + if (view_type != GNOME_CAL_LIST_VIEW) { + ECalendarView *calendar_view; + + calendar_view = gnome_calendar_get_calendar_view ( + calendar, GNOME_CAL_MONTH_VIEW); + e_week_view_set_weeks_shown ( + E_WEEK_VIEW (calendar_view), + (new_days_shown + 6) / 7); + switch_to = GNOME_CAL_MONTH_VIEW; + } + } else if (new_days_shown == 7 && starts_on_week_start_day) { + switch_to = GNOME_CAL_WEEK_VIEW; + } else if (new_days_shown == 1 && (view_type == GNOME_CAL_WORK_WEEK_VIEW || view_type == GNOME_CAL_WEEK_VIEW)) { + /* preserve week views when clicking on one day */ + switch_to = view_type; + } else { + ECalendarView *calendar_view; + + calendar_view = gnome_calendar_get_calendar_view ( + calendar, GNOME_CAL_DAY_VIEW); + e_day_view_set_days_shown ( + E_DAY_VIEW (calendar_view), new_days_shown); + + if (new_days_shown != 5 || !starts_on_week_start_day) + switch_to = GNOME_CAL_DAY_VIEW; + + else if (view_type != GNOME_CAL_WORK_WEEK_VIEW) + switch_to = GNOME_CAL_DAY_VIEW; + } + + /* Make the views display things properly. */ + gnome_calendar_update_view_times (calendar, new_time); + gnome_calendar_set_view (calendar, switch_to); + gnome_calendar_set_range_selected (calendar, TRUE); + + gnome_calendar_notify_dates_shown_changed (calendar); + + g_signal_handlers_block_by_func ( + calitem, + cal_shell_view_date_navigator_selection_changed_cb, cal_shell_view); + + /* make sure the selected days in the calendar matches shown days */ + e_cal_model_get_time_range (model, &start, &end); + + time_to_gdate_with_zone (&start_date, start, timezone); + time_to_gdate_with_zone (&end_date, end, timezone); + + g_date_subtract_days (&end_date, 1); + + e_calendar_item_set_selection (calitem, &start_date, &end_date); + + g_signal_handlers_unblock_by_func ( + calitem, + cal_shell_view_date_navigator_selection_changed_cb, cal_shell_view); +} + +static gboolean +cal_shell_view_date_navigator_scroll_event_cb (ECalShellView *cal_shell_view, + GdkEventScroll *event, + ECalendar *date_navigator) +{ + ECalendarItem *calitem; + GDate start_date, end_date; + GdkScrollDirection direction; + + calitem = date_navigator->calitem; + if (!e_calendar_item_get_selection (calitem, &start_date, &end_date)) + return FALSE; + + direction = event->direction; + + if (direction == GDK_SCROLL_SMOOTH) { + static gdouble total_delta_y = 0.0; + + total_delta_y += event->delta_y; + + if (total_delta_y >= 1.0) { + total_delta_y = 0.0; + direction = GDK_SCROLL_DOWN; + } else if (total_delta_y <= -1.0) { + total_delta_y = 0.0; + direction = GDK_SCROLL_UP; + } else { + return FALSE; + } + } + + switch (direction) { + case GDK_SCROLL_UP: + g_date_subtract_months (&start_date, 1); + g_date_subtract_months (&end_date, 1); + break; + + case GDK_SCROLL_DOWN: + g_date_add_months (&start_date, 1); + g_date_add_months (&end_date, 1); + break; + + default: + g_return_val_if_reached (FALSE); + } + + /* XXX Does ECalendarItem emit a signal for this? If so, maybe + * we could move this handler into ECalShellSidebar. */ + e_calendar_item_set_selection (calitem, &start_date, &end_date); + + cal_shell_view_date_navigator_selection_changed_cb ( + cal_shell_view, calitem); + + return TRUE; +} + +static void +cal_shell_view_popup_event_cb (EShellView *shell_view, + GdkEvent *button_event) +{ + GList *list; + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + ECalendarView *view; + ECalShellViewPrivate *priv; + const gchar *widget_path; + gint n_selected; + + priv = E_CAL_SHELL_VIEW_GET_PRIVATE (shell_view); + + calendar = e_cal_shell_content_get_calendar (priv->cal_shell_content); + + view_type = gnome_calendar_get_view (calendar); + view = gnome_calendar_get_calendar_view (calendar, view_type); + + list = e_calendar_view_get_selected_events (view); + n_selected = g_list_length (list); + g_list_free (list); + + if (n_selected <= 0) + widget_path = "/calendar-empty-popup"; + else + widget_path = "/calendar-event-popup"; + + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); +} + +static gboolean +cal_shell_view_selector_popup_event_cb (EShellView *shell_view, + ESource *primary_source, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/calendar-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); + + return TRUE; +} + +static void +cal_shell_view_selector_client_added_cb (ECalShellView *cal_shell_view, + ECalClient *client) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + ECalModel *model; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + model = gnome_calendar_get_model (calendar); + + if (e_cal_model_add_client (model, client)) + gnome_calendar_update_query (calendar); +} + +static void +cal_shell_view_selector_client_removed_cb (ECalShellView *cal_shell_view, + ECalClient *client) +{ + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + ECalModel *model; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + model = gnome_calendar_get_model (calendar); + + if (e_cal_model_remove_client (model, client)) + gnome_calendar_update_query (calendar); +} + +static void +cal_shell_view_memopad_popup_event_cb (EShellView *shell_view, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/calendar-memopad-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); +} + +static void +cal_shell_view_taskpad_popup_event_cb (EShellView *shell_view, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/calendar-taskpad-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); +} + +static void +cal_shell_view_user_created_cb (ECalShellView *cal_shell_view, + ECalClient *client, + ECalendarView *calendar_view) +{ + ECalShellSidebar *cal_shell_sidebar; + + cal_shell_sidebar = cal_shell_view->priv->cal_shell_sidebar; + + e_cal_shell_sidebar_add_client (cal_shell_sidebar, E_CLIENT (client)); +} + +static void +cal_shell_view_backend_error_cb (EClientCache *client_cache, + EClient *client, + EAlert *alert, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ESource *source; + const gchar *extension_name; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + + source = e_client_get_source (client); + extension_name = E_SOURCE_EXTENSION_CALENDAR; + + /* Only submit alerts from calendar backends. */ + if (e_source_has_extension (source, extension_name)) { + EAlertSink *alert_sink; + + alert_sink = E_ALERT_SINK (cal_shell_content); + e_alert_sink_submit_alert (alert_sink, alert); + } +} + +static void +cal_shell_view_notify_view_id_cb (EShellView *shell_view) +{ + GalViewInstance *view_instance; + const gchar *view_id; + + view_id = e_shell_view_get_view_id (shell_view); + view_instance = e_shell_view_get_view_instance (shell_view); + + /* A NULL view ID implies we're in a custom view. But you can + * only get to a custom view via the "Define Views" dialog, which + * would have already modified the view instance appropriately. + * Furthermore, there's no way to refer to a custom view by ID + * anyway, since custom views have no IDs. */ + if (view_id == NULL) + return; + + gal_view_instance_set_current_view_id (view_instance, view_id); +} + +void +e_cal_shell_view_private_init (ECalShellView *cal_shell_view) +{ + g_signal_connect ( + cal_shell_view, "notify::view-id", + G_CALLBACK (cal_shell_view_notify_view_id_cb), NULL); +} + +static void +system_timezone_monitor_changed (GFileMonitor *handle, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer user_data) +{ + GSettings *settings; + + if (event != G_FILE_MONITOR_EVENT_CHANGED && + event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT && + event != G_FILE_MONITOR_EVENT_DELETED && + event != G_FILE_MONITOR_EVENT_CREATED) + return; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + g_signal_emit_by_name (settings, "changed::timezone", "timezone"); + g_object_unref (settings); +} + +static void +init_timezone_monitors (ECalShellView *view) +{ + ECalShellViewPrivate *priv = view->priv; + gint i; + + for (i = 0; i < CHECK_NB; i++) { + GFile *file; + + file = g_file_new_for_path (files_to_check[i]); + priv->monitors[i] = g_file_monitor_file ( + file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + if (priv->monitors[i]) + g_signal_connect ( + priv->monitors[i], "changed", + G_CALLBACK (system_timezone_monitor_changed), + NULL); + } +} + +void +e_cal_shell_view_private_constructed (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv = cal_shell_view->priv; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + EShell *shell; + gulong handler_id; + gint ii; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + e_shell_window_add_action_group (shell_window, "calendar"); + e_shell_window_add_action_group (shell_window, "calendar-filter"); + + /* Cache these to avoid lots of awkward casting. */ + priv->cal_shell_backend = g_object_ref (shell_backend); + priv->cal_shell_content = g_object_ref (shell_content); + priv->cal_shell_sidebar = g_object_ref (shell_sidebar); + + handler_id = g_signal_connect_swapped ( + priv->cal_shell_sidebar, "client-added", + G_CALLBACK (cal_shell_view_selector_client_added_cb), + cal_shell_view); + priv->client_added_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->cal_shell_sidebar, "client-removed", + G_CALLBACK (cal_shell_view_selector_client_removed_cb), + cal_shell_view); + priv->client_removed_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->client_cache = e_shell_get_client_cache (shell); + g_object_ref (priv->client_cache); + + handler_id = g_signal_connect ( + priv->client_cache, "backend-error", + G_CALLBACK (cal_shell_view_backend_error_cb), + cal_shell_view); + priv->backend_error_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->calendar = e_cal_shell_content_get_calendar ( + E_CAL_SHELL_CONTENT (shell_content)); + g_object_ref (priv->calendar); + + handler_id = g_signal_connect_swapped ( + priv->calendar, "dates-shown-changed", + G_CALLBACK (e_cal_shell_view_update_sidebar), + cal_shell_view); + priv->dates_shown_changed_handler_id = handler_id; + + for (ii = 0; ii < GNOME_CAL_LAST_VIEW; ii++) { + ECalendarView *calendar_view; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + calendar_view = + gnome_calendar_get_calendar_view (priv->calendar, ii); + priv->views[ii].calendar_view = g_object_ref (calendar_view); + + handler_id = g_signal_connect_swapped ( + calendar_view, "popup-event", + G_CALLBACK (cal_shell_view_popup_event_cb), + cal_shell_view); + priv->views[ii].popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + calendar_view, "selection-changed", + G_CALLBACK (e_shell_view_update_actions), + cal_shell_view); + priv->views[ii].selection_changed_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + calendar_view, "user-created", + G_CALLBACK (cal_shell_view_user_created_cb), + cal_shell_view); + priv->views[ii].user_created_handler_id = handler_id; + } + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->model = e_cal_shell_content_get_model ( + E_CAL_SHELL_CONTENT (shell_content)); + g_object_ref (priv->model); + + handler_id = g_signal_connect_swapped ( + priv->model, "status-message", + G_CALLBACK (e_cal_shell_view_set_status_message), + cal_shell_view); + priv->status_message_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->date_navigator = e_cal_shell_sidebar_get_date_navigator ( + E_CAL_SHELL_SIDEBAR (shell_sidebar)); + + handler_id = g_signal_connect_swapped ( + priv->date_navigator, "scroll-event", + G_CALLBACK (cal_shell_view_date_navigator_scroll_event_cb), + cal_shell_view); + priv->scroll_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->date_navigator->calitem, "date-range-changed", + G_CALLBACK (cal_shell_view_date_navigator_date_range_changed_cb), + cal_shell_view); + priv->date_range_changed_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->date_navigator->calitem, "selection-changed", + G_CALLBACK (cal_shell_view_date_navigator_selection_changed_cb), + cal_shell_view); + priv->selection_changed_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->selector = e_cal_shell_sidebar_get_selector ( + E_CAL_SHELL_SIDEBAR (shell_sidebar)); + g_object_ref (priv->selector); + + handler_id = g_signal_connect_swapped ( + priv->selector, "popup-event", + G_CALLBACK (cal_shell_view_selector_popup_event_cb), + cal_shell_view); + priv->selector_popup_event_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->memo_table = e_cal_shell_content_get_memo_table ( + E_CAL_SHELL_CONTENT (shell_content)); + g_object_ref (priv->memo_table); + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "popup-event", + G_CALLBACK (cal_shell_view_memopad_popup_event_cb), + cal_shell_view); + priv->memo_table_popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "selection-change", + G_CALLBACK (e_cal_shell_view_memopad_actions_update), + cal_shell_view); + priv->memo_table_selection_change_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "status-message", + G_CALLBACK (e_cal_shell_view_memopad_set_status_message), + cal_shell_view); + priv->memo_table_status_message_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->task_table = e_cal_shell_content_get_task_table ( + E_CAL_SHELL_CONTENT (shell_content)); + g_object_ref (priv->task_table); + + handler_id = g_signal_connect_swapped ( + priv->task_table, "popup-event", + G_CALLBACK (cal_shell_view_taskpad_popup_event_cb), + cal_shell_view); + priv->task_table_popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "selection-change", + G_CALLBACK (e_cal_shell_view_taskpad_actions_update), + cal_shell_view); + priv->task_table_selection_change_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "status-message", + G_CALLBACK (e_cal_shell_view_taskpad_set_status_message), + cal_shell_view); + priv->task_table_status_message_handler_id = handler_id; + + e_categories_add_change_hook ( + (GHookFunc) e_cal_shell_view_update_search_filter, + cal_shell_view); + + /* Give GnomeCalendar a handle to the date navigator, + * memo and task table. */ + gnome_calendar_set_date_navigator ( + priv->calendar, priv->date_navigator); + gnome_calendar_set_memo_table ( + priv->calendar, GTK_WIDGET (priv->memo_table)); + gnome_calendar_set_task_table ( + priv->calendar, GTK_WIDGET (priv->task_table)); + + e_calendar_item_set_get_time_callback ( + priv->date_navigator->calitem, (ECalendarItemGetTimeCallback) + cal_shell_view_get_current_time, cal_shell_view, NULL); + + init_timezone_monitors (cal_shell_view); + e_cal_shell_view_actions_init (cal_shell_view); + e_cal_shell_view_update_sidebar (cal_shell_view); + e_cal_shell_view_update_search_filter (cal_shell_view); + + /* Keep the ECalModel in sync with the sidebar. */ + g_object_bind_property ( + shell_sidebar, "default-client", + priv->model, "default-client", + G_BINDING_SYNC_CREATE); + + /* Keep the toolbar view buttons in sync with the calendar. */ + g_object_bind_property ( + priv->calendar, "view", + ACTION (CALENDAR_VIEW_DAY), "current-value", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + /* Force the main calendar to update its default source. */ + g_signal_emit_by_name (priv->selector, "primary-selection-changed"); +} + +void +e_cal_shell_view_private_dispose (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv = cal_shell_view->priv; + gint ii; + + e_cal_shell_view_search_stop (cal_shell_view); + + /* Calling ECalShellContent's save state from here, + * because it is too late in its own dispose(). */ + if (priv->cal_shell_content != NULL) + e_cal_shell_content_save_state (priv->cal_shell_content); + + if (priv->client_added_handler_id > 0) { + g_signal_handler_disconnect ( + priv->cal_shell_sidebar, + priv->client_added_handler_id); + priv->client_added_handler_id = 0; + } + + if (priv->client_removed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->cal_shell_sidebar, + priv->client_removed_handler_id); + priv->client_removed_handler_id = 0; + } + + if (priv->prepare_for_quit_handler_id > 0) { + g_signal_handler_disconnect ( + priv->shell, + priv->prepare_for_quit_handler_id); + priv->prepare_for_quit_handler_id = 0; + } + + if (priv->backend_error_handler_id > 0) { + g_signal_handler_disconnect ( + priv->client_cache, + priv->backend_error_handler_id); + priv->backend_error_handler_id = 0; + } + + if (priv->dates_shown_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->calendar, + priv->dates_shown_changed_handler_id); + priv->dates_shown_changed_handler_id = 0; + } + + if (priv->status_message_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->status_message_handler_id); + priv->status_message_handler_id = 0; + } + + if (priv->scroll_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->date_navigator, + priv->scroll_event_handler_id); + priv->scroll_event_handler_id = 0; + } + + if (priv->date_range_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->date_navigator->calitem, + priv->date_range_changed_handler_id); + priv->date_range_changed_handler_id = 0; + } + + if (priv->selection_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->date_navigator->calitem, + priv->selection_changed_handler_id); + priv->selection_changed_handler_id = 0; + } + + if (priv->selector_popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->selector, + priv->selector_popup_event_handler_id); + priv->selector_popup_event_handler_id = 0; + } + + if (priv->memo_table_popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->memo_table_popup_event_handler_id); + priv->memo_table_popup_event_handler_id = 0; + } + + if (priv->memo_table_selection_change_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->memo_table_selection_change_handler_id); + priv->memo_table_selection_change_handler_id = 0; + } + + if (priv->memo_table_status_message_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->memo_table_status_message_handler_id); + priv->memo_table_status_message_handler_id = 0; + } + + if (priv->task_table_popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->task_table_popup_event_handler_id); + priv->task_table_popup_event_handler_id = 0; + } + + if (priv->task_table_selection_change_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->task_table_selection_change_handler_id); + priv->task_table_selection_change_handler_id = 0; + } + + if (priv->task_table_status_message_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->task_table_status_message_handler_id); + priv->task_table_status_message_handler_id = 0; + } + + for (ii = 0; ii < GNOME_CAL_LAST_VIEW; ii++) { + if (priv->views[ii].popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->views[ii].calendar_view, + priv->views[ii].popup_event_handler_id); + priv->views[ii].popup_event_handler_id = 0; + } + + if (priv->views[ii].selection_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->views[ii].calendar_view, + priv->views[ii].selection_changed_handler_id); + priv->views[ii].selection_changed_handler_id = 0; + } + + if (priv->views[ii].user_created_handler_id > 0) { + g_signal_handler_disconnect ( + priv->views[ii].calendar_view, + priv->views[ii].user_created_handler_id); + priv->views[ii].user_created_handler_id = 0; + } + + g_clear_object (&priv->views[ii].calendar_view); + } + + g_clear_object (&priv->cal_shell_backend); + g_clear_object (&priv->cal_shell_content); + g_clear_object (&priv->cal_shell_sidebar); + + g_clear_object (&priv->shell); + g_clear_object (&priv->client_cache); + g_clear_object (&priv->calendar); + g_clear_object (&priv->model); + g_clear_object (&priv->date_navigator); + g_clear_object (&priv->selector); + g_clear_object (&priv->memo_table); + g_clear_object (&priv->task_table); + + if (priv->calendar_activity != NULL) { + /* XXX Activity is not cancellable. */ + e_activity_set_state ( + priv->calendar_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->calendar_activity); + priv->calendar_activity = NULL; + } + + if (priv->memopad_activity != NULL) { + /* XXX Activity is not cancellable. */ + e_activity_set_state ( + priv->memopad_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->memopad_activity); + priv->memopad_activity = NULL; + } + + if (priv->taskpad_activity != NULL) { + /* XXX Activity is not cancellable. */ + e_activity_set_state ( + priv->taskpad_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->taskpad_activity); + priv->taskpad_activity = NULL; + } + + for (ii = 0; ii < CHECK_NB; ii++) + g_clear_object (&priv->monitors[ii]); +} + +void +e_cal_shell_view_private_finalize (ECalShellView *cal_shell_view) +{ + /* XXX Nothing to do? */ +} + +void +e_cal_shell_view_open_event (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + CompEditor *editor; + CompEditorFlags flags = 0; + ECalComponent *comp; + icalcomponent *clone; + icalproperty *prop; + const gchar *uid; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + uid = icalcomponent_get_uid (comp_data->icalcomp); + editor = comp_editor_find_instance (uid); + + if (editor != NULL) + goto exit; + + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY); + if (prop != NULL) + flags |= COMP_EDITOR_MEETING; + + if (itip_organizer_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + if (itip_sentby_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + if (!e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + + editor = event_editor_new (comp_data->client, shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +exit: + gtk_window_present (GTK_WINDOW (editor)); +} + +void +e_cal_shell_view_set_status_message (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent) +{ + EActivity *activity; + EShellView *shell_view; + EShellBackend *shell_backend; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + + activity = cal_shell_view->priv->calendar_activity; + + if (status_message == NULL || *status_message == '\0') { + if (activity != NULL) { + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_object_unref (activity); + activity = NULL; + } + } else if (activity == NULL) { + activity = e_activity_new (); + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + e_shell_backend_add_activity (shell_backend, activity); + } else { + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + } + + cal_shell_view->priv->calendar_activity = activity; +} + +struct ForeachTzidData +{ + ECalClient *source_client; + ECalClient *dest_client; +}; + +static void +add_timezone_to_cal_cb (icalparameter *param, + gpointer data) +{ + struct ForeachTzidData *ftd = data; + icaltimezone *tz = NULL; + const gchar *tzid; + + g_return_if_fail (ftd != NULL); + g_return_if_fail (ftd->source_client != NULL); + g_return_if_fail (ftd->dest_client != NULL); + + tzid = icalparameter_get_tzid (param); + if (!tzid || !*tzid) + return; + + if (e_cal_client_get_timezone_sync (ftd->source_client, tzid, &tz, NULL, NULL) && tz) + e_cal_client_add_timezone_sync (ftd->dest_client, tz, NULL, NULL); +} + +void +e_cal_shell_view_transfer_item_to (ECalShellView *cal_shell_view, + ECalendarViewEvent *event, + ECalClient *destination_client, + gboolean remove) +{ + icalcomponent *icalcomp; + icalcomponent *icalcomp_clone; + icalcomponent *icalcomp_event; + gboolean success; + const gchar *uid; + + /* XXX This function should be split up into + * smaller, more understandable pieces. */ + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + g_return_if_fail (event != NULL); + g_return_if_fail (E_IS_CAL_CLIENT (destination_client)); + + if (!is_comp_data_valid (event)) + return; + + icalcomp_event = event->comp_data->icalcomp; + uid = icalcomponent_get_uid (icalcomp_event); + + /* Put the new object into the destination calendar. */ + + success = e_cal_client_get_object_sync ( + destination_client, uid, NULL, &icalcomp, NULL, NULL); + + if (success) { + icalcomponent_free (icalcomp); + success = e_cal_client_modify_object_sync ( + destination_client, icalcomp_event, + CALOBJ_MOD_ALL, NULL, NULL); + + /* do not delete the event when it was found in the calendar */ + return; + } else { + icalproperty *icalprop; + gchar *new_uid; + GError *error = NULL; + struct ForeachTzidData ftd; + + ftd.source_client = event->comp_data->client; + ftd.dest_client = destination_client; + + if (e_cal_util_component_is_instance (icalcomp_event)) { + success = e_cal_client_get_object_sync ( + event->comp_data->client, + uid, NULL, &icalcomp, NULL, NULL); + if (success) { + /* Use master object when working + * with a recurring event ... */ + icalcomp_clone = icalcomponent_new_clone (icalcomp); + icalcomponent_free (icalcomp); + } else { + /* ... or remove the recurrence ID ... */ + icalcomp_clone = + icalcomponent_new_clone (icalcomp_event); + if (e_cal_util_component_has_recurrences (icalcomp_clone)) { + /* ... for non-detached instances, + * to make it a master object. */ + icalprop = icalcomponent_get_first_property ( + icalcomp_clone, ICAL_RECURRENCEID_PROPERTY); + if (icalprop != NULL) + icalcomponent_remove_property ( + icalcomp_clone, icalprop); + } + } + } else + icalcomp_clone = + icalcomponent_new_clone (icalcomp_event); + + icalprop = icalproperty_new_x ("1"); + icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR"); + icalcomponent_add_property (icalcomp_clone, icalprop); + + if (!remove) { + /* Change the UID to avoid problems with + * duplicated UIDs. */ + new_uid = e_cal_component_gen_uid (); + icalcomponent_set_uid (icalcomp_clone, new_uid); + g_free (new_uid); + } + + new_uid = NULL; + icalcomponent_foreach_tzid ( + icalcomp_clone, add_timezone_to_cal_cb, &ftd); + success = e_cal_client_create_object_sync ( + destination_client, icalcomp_clone, + &new_uid, NULL, &error); + if (!success) { + icalcomponent_free (icalcomp_clone); + g_warning ( + "%s: Failed to create object: %s", + G_STRFUNC, error->message); + g_error_free (error); + return; + } + + icalcomponent_free (icalcomp_clone); + g_free (new_uid); + } + + if (remove) { + ECalClient *source_client = event->comp_data->client; + + /* Remove the item from the source calendar. */ + if (e_cal_util_component_is_instance (icalcomp_event) || + e_cal_util_component_has_recurrences (icalcomp_event)) { + icaltimetype icaltime; + gchar *rid; + + icaltime = + icalcomponent_get_recurrenceid (icalcomp_event); + if (!icaltime_is_null_time (icaltime)) + rid = icaltime_as_ical_string_r (icaltime); + else + rid = NULL; + e_cal_client_remove_object_sync ( + source_client, uid, rid, + CALOBJ_MOD_ALL, NULL, NULL); + g_free (rid); + } else + e_cal_client_remove_object_sync ( + source_client, uid, NULL, + CALOBJ_MOD_THIS, NULL, NULL); + } +} + +void +e_cal_shell_view_update_sidebar (ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellSidebar *shell_sidebar; + ECalShellContent *cal_shell_content; + GnomeCalendar *calendar; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + ECalModel *model; + time_t start_time, end_time; + struct tm start_tm, end_tm; + struct icaltimetype start_tt, end_tt; + icaltimezone *timezone; + gchar buffer[512] = { 0 }; + gchar end_buffer[512] = { 0 }; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + + model = gnome_calendar_get_model (calendar); + timezone = e_cal_model_get_timezone (model); + + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + if (!e_calendar_view_get_visible_time_range ( + calendar_view, &start_time, &end_time)) { + e_shell_sidebar_set_secondary_text (shell_sidebar, ""); + return; + } + + start_tt = icaltime_from_timet_with_zone (start_time, FALSE, timezone); + start_tm.tm_year = start_tt.year - 1900; + start_tm.tm_mon = start_tt.month - 1; + start_tm.tm_mday = start_tt.day; + start_tm.tm_hour = start_tt.hour; + start_tm.tm_min = start_tt.minute; + start_tm.tm_sec = start_tt.second; + start_tm.tm_isdst = -1; + start_tm.tm_wday = time_day_of_week ( + start_tt.day, start_tt.month - 1, start_tt.year); + + /* Subtract one from end_time so we don't get an extra day. */ + end_tt = icaltime_from_timet_with_zone (end_time - 1, FALSE, timezone); + end_tm.tm_year = end_tt.year - 1900; + end_tm.tm_mon = end_tt.month - 1; + end_tm.tm_mday = end_tt.day; + end_tm.tm_hour = end_tt.hour; + end_tm.tm_min = end_tt.minute; + end_tm.tm_sec = end_tt.second; + end_tm.tm_isdst = -1; + end_tm.tm_wday = time_day_of_week ( + end_tt.day, end_tt.month - 1, end_tt.year); + + switch (view_type) { + case GNOME_CAL_DAY_VIEW: + case GNOME_CAL_WORK_WEEK_VIEW: + case GNOME_CAL_WEEK_VIEW: + if (start_tm.tm_year == end_tm.tm_year && + start_tm.tm_mon == end_tm.tm_mon && + start_tm.tm_mday == end_tm.tm_mday) { + e_utf8_strftime ( + buffer, sizeof (buffer), + _("%A %d %b %Y"), &start_tm); + } else if (start_tm.tm_year == end_tm.tm_year) { + e_utf8_strftime ( + buffer, sizeof (buffer), + _("%a %d %b"), &start_tm); + e_utf8_strftime ( + end_buffer, sizeof (end_buffer), + _("%a %d %b %Y"), &end_tm); + strcat (buffer, " - "); + strcat (buffer, end_buffer); + } else { + e_utf8_strftime ( + buffer, sizeof (buffer), + _("%a %d %b %Y"), &start_tm); + e_utf8_strftime ( + end_buffer, sizeof (end_buffer), + _("%a %d %b %Y"), &end_tm); + strcat (buffer, " - "); + strcat (buffer, end_buffer); + } + break; + + case GNOME_CAL_MONTH_VIEW: + case GNOME_CAL_LIST_VIEW: + if (start_tm.tm_year == end_tm.tm_year) { + if (start_tm.tm_mon == end_tm.tm_mon) { + e_utf8_strftime ( + buffer, + sizeof (buffer), + "%d", &start_tm); + e_utf8_strftime ( + end_buffer, + sizeof (end_buffer), + _("%d %b %Y"), &end_tm); + strcat (buffer, " - "); + strcat (buffer, end_buffer); + } else { + e_utf8_strftime ( + buffer, + sizeof (buffer), + _("%d %b"), &start_tm); + e_utf8_strftime ( + end_buffer, + sizeof (end_buffer), + _("%d %b %Y"), &end_tm); + strcat (buffer, " - "); + strcat (buffer, end_buffer); + } + } else { + e_utf8_strftime ( + buffer, sizeof (buffer), + _("%d %b %Y"), &start_tm); + e_utf8_strftime ( + end_buffer, sizeof (end_buffer), + _("%d %b %Y"), &end_tm); + strcat (buffer, " - "); + strcat (buffer, end_buffer); + } + break; + + default: + g_return_if_reached (); + } + + e_shell_sidebar_set_secondary_text (shell_sidebar, buffer); +} + +static gint +cal_searching_get_search_range_years (ECalShellView *cal_shell_view) +{ + GSettings *settings; + gint search_range_years; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + search_range_years = + g_settings_get_int (settings, "search-range-years"); + if (search_range_years <= 0) + search_range_years = 10; + + g_object_unref (settings); + + return search_range_years; +} + +static gint +cal_time_t_ptr_compare (gconstpointer a, + gconstpointer b) +{ + const time_t *ta = a, *tb = b; + + return (ta ? *ta : 0) - (tb ? *tb : 0); +} + +static void cal_iterate_searching (ECalShellView *cal_shell_view); + +struct GenerateInstancesData { + ECalClient *client; + ECalShellView *cal_shell_view; + GCancellable *cancellable; +}; + +static void +cal_searching_instances_done_cb (gpointer user_data) +{ + struct GenerateInstancesData *gid = user_data; + + g_return_if_fail (gid != NULL); + g_return_if_fail (gid->cal_shell_view != NULL); + + if (!g_cancellable_is_cancelled (gid->cancellable)) { + gid->cal_shell_view->priv->search_pending_count--; + if (!gid->cal_shell_view->priv->search_pending_count) { + gid->cal_shell_view->priv->search_hit_cache = + g_slist_sort ( + gid->cal_shell_view->priv->search_hit_cache, + cal_time_t_ptr_compare); + cal_iterate_searching (gid->cal_shell_view); + } + } + + g_object_unref (gid->cancellable); + g_free (gid); +} + +static gboolean +cal_searching_got_instance_cb (ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + struct GenerateInstancesData *gid = user_data; + ECalShellViewPrivate *priv; + ECalComponentDateTime dt; + time_t *value; + + g_return_val_if_fail (gid != NULL, FALSE); + + if (g_cancellable_is_cancelled (gid->cancellable)) + return FALSE; + + g_return_val_if_fail (gid->cal_shell_view != NULL, FALSE); + g_return_val_if_fail (gid->cal_shell_view->priv != NULL, FALSE); + + e_cal_component_get_dtstart (comp, &dt); + + if (dt.tzid && dt.value) { + icaltimezone *zone = NULL; + if (!e_cal_client_get_timezone_sync (gid->client, dt.tzid, &zone, gid->cancellable, NULL)) { + zone = NULL; + } + + if (g_cancellable_is_cancelled (gid->cancellable)) + return FALSE; + + if (zone) + instance_start = icaltime_as_timet_with_zone (*dt.value, zone); + } + + e_cal_component_free_datetime (&dt); + + priv = gid->cal_shell_view->priv; + value = g_new (time_t, 1); + *value = instance_start; + if (!g_slist_find_custom (priv->search_hit_cache, value, cal_time_t_ptr_compare)) + priv->search_hit_cache = g_slist_append (priv->search_hit_cache, value); + else + g_free (value); + + return TRUE; +} + +static void +cal_search_get_object_list_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (source); + ECalShellView *cal_shell_view = user_data; + GSList *icalcomps = NULL; + GError *error = NULL; + + g_return_if_fail (client != NULL); + g_return_if_fail (result != NULL); + g_return_if_fail (cal_shell_view != NULL); + + if (!e_cal_client_get_object_list_finish (client, result, &icalcomps, &error) || !icalcomps) { + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + g_clear_error (&error); + cal_shell_view->priv->search_pending_count--; + if (!cal_shell_view->priv->search_pending_count) { + cal_shell_view->priv->search_hit_cache = + g_slist_sort ( + cal_shell_view->priv->search_hit_cache, + cal_time_t_ptr_compare); + cal_iterate_searching (cal_shell_view); + } + } else { + GSList *iter; + GCancellable *cancellable; + time_t start, end; + + cancellable = e_activity_get_cancellable ( + cal_shell_view->priv->searching_activity); + start = time_add_day ( + cal_shell_view->priv->search_time, + (-1) * cal_shell_view->priv->search_direction); + end = cal_shell_view->priv->search_time; + if (start > end) { + time_t tmp = start; + start = end; + end = tmp; + } + + for (iter = icalcomps; iter; iter = iter->next) { + icalcomponent *icalcomp = iter->data; + struct GenerateInstancesData *gid; + + gid = g_new0 (struct GenerateInstancesData, 1); + gid->client = client; + gid->cal_shell_view = cal_shell_view; + gid->cancellable = g_object_ref (cancellable); + + e_cal_client_generate_instances_for_object ( + client, icalcomp, start, end, cancellable, + cal_searching_got_instance_cb, gid, + cal_searching_instances_done_cb); + } + + e_cal_client_free_icalcomp_slist (icalcomps); + } +} + +static gboolean +cal_searching_check_candidates (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + GSList *iter; + time_t value, candidate = -1; + + g_return_val_if_fail (cal_shell_view != NULL, FALSE); + g_return_val_if_fail (cal_shell_view->priv != NULL, FALSE); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + if (!e_calendar_view_get_selected_time_range (calendar_view, &value, NULL)) + return FALSE; + + if (cal_shell_view->priv->search_direction > 0 && (view_type == GNOME_CAL_WEEK_VIEW || view_type == GNOME_CAL_MONTH_VIEW)) + value = time_add_day (value, 1); + + for (iter = cal_shell_view->priv->search_hit_cache; iter; iter = iter->next) { + time_t cache = *((time_t *) iter->data); + + /* list is sorted, once the search iteration is complete */ + if (cache > value) { + if (cal_shell_view->priv->search_direction > 0) + candidate = cache; + break; + } else if (cal_shell_view->priv->search_direction < 0 && cache != value) + candidate = cache; + } + + if (candidate > 0) { + gnome_calendar_goto (calendar, candidate); + return TRUE; + } + + return FALSE; +} + +static void +cal_searching_update_alert (ECalShellView *cal_shell_view, + const gchar *message) +{ + ECalShellViewPrivate *priv; + EShellContent *shell_content; + EAlert *alert; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + + if (priv->search_alert) { + e_alert_response ( + priv->search_alert, + e_alert_get_default_response (priv->search_alert)); + priv->search_alert = NULL; + } + + if (!message) + return; + + alert = e_alert_new ("calendar:search-error-generic", message, NULL); + g_return_if_fail (alert != NULL); + + priv->search_alert = alert; + g_object_add_weak_pointer (G_OBJECT (alert), &priv->search_alert); + e_alert_start_timer (priv->search_alert, 5); + + shell_content = e_shell_view_get_shell_content ( + E_SHELL_VIEW (cal_shell_view)); + e_alert_sink_submit_alert ( + E_ALERT_SINK (shell_content), priv->search_alert); + g_object_unref (priv->search_alert); +} + +static void +cal_iterate_searching (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv; + GList *list, *link; + ECalModel *model; + time_t new_time, range1, range2; + icaltimezone *timezone; + const gchar *default_tzloc = NULL; + GCancellable *cancellable; + gchar *sexp, *start, *end; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + g_return_if_fail (priv->search_direction != 0); + g_return_if_fail (priv->search_pending_count == 0); + + cal_searching_update_alert (cal_shell_view, NULL); + + if (cal_searching_check_candidates (cal_shell_view)) { + if (priv->searching_activity) { + e_activity_set_state ( + priv->searching_activity, + E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + } + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + + return; + } + + if (!priv->searching_activity) { + EShellBackend *shell_backend; + + shell_backend = e_shell_view_get_shell_backend ( + E_SHELL_VIEW (cal_shell_view)); + + cancellable = g_cancellable_new (); + priv->searching_activity = e_activity_new (); + e_activity_set_cancellable ( + priv->searching_activity, cancellable); + e_activity_set_state ( + priv->searching_activity, E_ACTIVITY_RUNNING); + e_activity_set_text ( + priv->searching_activity, + priv->search_direction > 0 ? + _("Searching next matching event") : + _("Searching previous matching event")); + + e_shell_backend_add_activity ( + shell_backend, priv->searching_activity); + } + + new_time = time_add_day (priv->search_time, priv->search_direction); + if (new_time > priv->search_max_time || new_time < priv->search_min_time) { + gchar *alert_msg; + gint range_years; + + /* would get out of bounds, stop searching */ + e_activity_set_state ( + priv->searching_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + + range_years = cal_searching_get_search_range_years (cal_shell_view); + alert_msg = g_strdup_printf ( + priv->search_direction > 0 ? + ngettext ( + "Cannot find matching event in the next %d year", + "Cannot find matching event in the next %d years", + range_years) : + ngettext ( + "Cannot find matching event in the previous %d year", + "Cannot find matching event in the previous %d years", + range_years), + range_years); + cal_searching_update_alert (cal_shell_view, alert_msg); + g_free (alert_msg); + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + + return; + } + + model = gnome_calendar_get_model ( + e_cal_shell_content_get_calendar ( + cal_shell_view->priv->cal_shell_content)); + list = e_cal_model_list_clients (model); + + if (list == NULL) { + e_activity_set_state ( + priv->searching_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + + cal_searching_update_alert ( + cal_shell_view, + _("Cannot search with no active calendar")); + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + + return; + } + + timezone = e_cal_model_get_timezone (model); + range1 = priv->search_time; + range2 = time_add_day (range1, priv->search_direction); + if (range1 < range2) { + start = isodate_from_time_t (time_day_begin (range1)); + end = isodate_from_time_t (time_day_end (range2)); + } else { + start = isodate_from_time_t (time_day_begin (range2)); + end = isodate_from_time_t (time_day_end (range1)); + } + + if (timezone && timezone != icaltimezone_get_utc_timezone ()) + default_tzloc = icaltimezone_get_location (timezone); + if (!default_tzloc) + default_tzloc = ""; + + sexp = g_strdup_printf ( + "(and %s (occur-in-time-range? " + "(make-time \"%s\") " + "(make-time \"%s\") \"%s\"))", + e_cal_model_get_search_query (model), start, end, default_tzloc); + + g_free (start); + g_free (end); + + cancellable = e_activity_get_cancellable (priv->searching_activity); + priv->search_pending_count = g_list_length (list); + priv->search_time = new_time; + + for (link = list; link != NULL; link = g_list_next (link)) { + ECalClient *client = E_CAL_CLIENT (link->data); + + e_cal_client_get_object_list ( + client, sexp, cancellable, + cal_search_get_object_list_cb, cal_shell_view); + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + g_free (sexp); + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); +} + +void +e_cal_shell_view_search_events (ECalShellView *cal_shell_view, + gboolean search_forward) +{ + ECalShellViewPrivate *priv = cal_shell_view->priv; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + time_t start_time = 0; + gint range_years; + + if (priv->searching_activity || !priv->search_direction) + e_cal_shell_view_search_stop (cal_shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + if (!e_calendar_view_get_selected_time_range (calendar_view, &start_time, NULL)) { + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + return; + } + + start_time = time_day_begin (start_time); + if (priv->search_direction) { + time_t cached_start, cached_end, tmp; + + cached_start = priv->search_time; + cached_end = time_add_day ( + cached_start, (-1) * priv->search_direction); + + if (priv->search_direction > 0) { + tmp = cached_start; + cached_start = cached_end; + cached_end = tmp; + } + + /* clear cached results if searching out of cached bounds */ + if (start_time < cached_start || start_time > cached_end) + e_cal_shell_view_search_stop (cal_shell_view); + } + + priv->search_direction = search_forward ? +30 : -30; + + if (cal_searching_check_candidates (cal_shell_view)) { + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + return; + } + + range_years = cal_searching_get_search_range_years (cal_shell_view); + + priv->search_pending_count = 0; + priv->search_time = start_time; + priv->search_min_time = start_time - (range_years * 365 * 24 * 60 * 60); + priv->search_max_time = start_time + (range_years * 365 * 24 * 60 * 60); + + if (priv->search_min_time < 0) + priv->search_min_time = 0; + if (priv->search_hit_cache) { + g_slist_free_full (priv->search_hit_cache, g_free); + priv->search_hit_cache = NULL; + } + + cal_iterate_searching (cal_shell_view); +} + +void +e_cal_shell_view_search_stop (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + + cal_searching_update_alert (cal_shell_view, NULL); + + if (priv->searching_activity) { + g_cancellable_cancel ( + e_activity_get_cancellable (priv->searching_activity)); + e_activity_set_state ( + priv->searching_activity, E_ACTIVITY_CANCELLED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + } + + if (priv->search_hit_cache) { + g_slist_free_full (priv->search_hit_cache, g_free); + priv->search_hit_cache = NULL; + } + + priv->search_direction = 0; +} diff --git a/modules/calendar/e-cal-shell-view-private.h b/modules/calendar/e-cal-shell-view-private.h new file mode 100644 index 0000000000..000602b991 --- /dev/null +++ b/modules/calendar/e-cal-shell-view-private.h @@ -0,0 +1,223 @@ +/* + * e-cal-shell-view-private.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_VIEW_PRIVATE_H +#define E_CAL_SHELL_VIEW_PRIVATE_H + +#include "e-cal-shell-view.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include <libecal/libecal.h> + +#include <shell/e-shell-utils.h> + +#include <calendar/gui/calendar-config.h> +#include <calendar/gui/comp-util.h> +#include <calendar/gui/e-cal-list-view.h> +#include <calendar/gui/e-cal-model-tasks.h> +#include <calendar/gui/e-calendar-view.h> +#include <calendar/gui/e-day-view.h> +#include <calendar/gui/e-week-view.h> +#include <calendar/gui/gnome-cal.h> +#include <calendar/gui/print.h> +#include <calendar/gui/dialogs/copy-source-dialog.h> +#include <calendar/gui/dialogs/event-editor.h> +#include <calendar/gui/dialogs/goto-dialog.h> +#include <calendar/gui/dialogs/memo-editor.h> +#include <calendar/gui/dialogs/select-source-dialog.h> +#include <calendar/gui/dialogs/task-editor.h> + +#include "e-cal-shell-backend.h" +#include "e-cal-shell-content.h" +#include "e-cal-shell-sidebar.h" +#include "e-cal-shell-view-actions.h" + +#define E_CAL_SHELL_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellViewPrivate)) + +/* Shorthand, requires a variable named "shell_window". */ +#define ACTION(name) \ + (E_SHELL_WINDOW_ACTION_##name (shell_window)) +#define ACTION_GROUP(name) \ + (E_SHELL_WINDOW_ACTION_GROUP_##name (shell_window)) + +/* ETable Specifications */ +#define ETSPEC_FILENAME "e-calendar-table.etspec" +#define CHECK_NB 5 + +G_BEGIN_DECLS + +/* Filter items are displayed in ascending order. + * Non-negative values are reserved for categories. */ +enum { + CALENDAR_FILTER_ANY_CATEGORY = -5, + CALENDAR_FILTER_UNMATCHED = -4, + CALENDAR_FILTER_ACTIVE_APPOINTMENTS = -3, + CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS = -2, + CALENDAR_FILTER_OCCURS_LESS_THAN_5_TIMES = -1 +}; + +/* Search items are displayed in ascending order. */ +enum { + CALENDAR_SEARCH_ADVANCED = -1, + CALENDAR_SEARCH_SUMMARY_CONTAINS, + CALENDAR_SEARCH_DESCRIPTION_CONTAINS, + CALENDAR_SEARCH_ANY_FIELD_CONTAINS +}; + +struct _ECalShellViewPrivate { + + /* These are just for convenience. */ + ECalShellBackend *cal_shell_backend; + ECalShellContent *cal_shell_content; + ECalShellSidebar *cal_shell_sidebar; + + /* sidebar signal handlers */ + gulong client_added_handler_id; + gulong client_removed_handler_id; + + EShell *shell; + gulong prepare_for_quit_handler_id; + + EClientCache *client_cache; + gulong backend_error_handler_id; + + GnomeCalendar *calendar; + gulong dates_shown_changed_handler_id; + + struct { + ECalendarView *calendar_view; + gulong popup_event_handler_id; + gulong selection_changed_handler_id; + gulong user_created_handler_id; + } views[GNOME_CAL_LAST_VIEW]; + + ECalModel *model; + gulong status_message_handler_id; + + ECalendar *date_navigator; + gulong scroll_event_handler_id; + gulong date_range_changed_handler_id; + gulong selection_changed_handler_id; + + ESourceSelector *selector; + gulong selector_popup_event_handler_id; + + EMemoTable *memo_table; + gulong memo_table_popup_event_handler_id; + gulong memo_table_selection_change_handler_id; + gulong memo_table_status_message_handler_id; + + ETaskTable *task_table; + gulong task_table_popup_event_handler_id; + gulong task_table_selection_change_handler_id; + gulong task_table_status_message_handler_id; + + /* The last time explicitly selected by the user. */ + time_t base_view_time; + + EActivity *calendar_activity; + EActivity *memopad_activity; + EActivity *taskpad_activity; + + /* Time-range searching */ + EActivity *searching_activity; + gpointer search_alert; /* weak pointer to EAlert * */ + gint search_pending_count; /* how many clients are pending */ + time_t search_time; /* current search time from */ + time_t search_min_time, search_max_time; /* time boundary for searching */ + gint search_direction; /* negative value is backward, positive is forward, zero is error; in days */ + GSList *search_hit_cache; /* pointers on time_t for matched events */ + + GFileMonitor *monitors[CHECK_NB]; +}; + +void e_cal_shell_view_private_init + (ECalShellView *cal_shell_view); +void e_cal_shell_view_private_constructed + (ECalShellView *cal_shell_view); +void e_cal_shell_view_private_dispose + (ECalShellView *cal_shell_view); +void e_cal_shell_view_private_finalize + (ECalShellView *cal_shell_view); + +/* Private Utilities */ + +void e_cal_shell_view_actions_init + (ECalShellView *cal_shell_view); +void e_cal_shell_view_execute_search + (ECalShellView *cal_shell_view); +void e_cal_shell_view_open_event + (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data); +void e_cal_shell_view_set_status_message + (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent); +void e_cal_shell_view_transfer_item_to + (ECalShellView *cal_shell_view, + ECalendarViewEvent *event, + ECalClient *destination_client, + gboolean remove); +void e_cal_shell_view_update_sidebar + (ECalShellView *cal_shell_view); +void e_cal_shell_view_update_search_filter + (ECalShellView *cal_shell_view); +void e_cal_shell_view_search_events + (ECalShellView *cal_shell_view, + gboolean search_forward); +void e_cal_shell_view_search_stop + (ECalShellView *cal_shell_view); + +/* Memo Pad Utilities */ + +void e_cal_shell_view_memopad_actions_init + (ECalShellView *cal_shell_view); +void e_cal_shell_view_memopad_actions_update + (ECalShellView *cal_shell_view); +void e_cal_shell_view_memopad_open_memo + (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data); +void e_cal_shell_view_memopad_set_status_message + (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent); + +/* Task Pad Utilities */ + +void e_cal_shell_view_taskpad_actions_init + (ECalShellView *cal_shell_view); +void e_cal_shell_view_taskpad_actions_update + (ECalShellView *cal_shell_view); +void e_cal_shell_view_taskpad_open_task + (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data); +void e_cal_shell_view_taskpad_set_status_message + (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent); + +G_END_DECLS + +#endif /* E_CAL_SHELL_VIEW_PRIVATE_H */ diff --git a/modules/calendar/e-cal-shell-view-taskpad.c b/modules/calendar/e-cal-shell-view-taskpad.c new file mode 100644 index 0000000000..ec268775e9 --- /dev/null +++ b/modules/calendar/e-cal-shell-view-taskpad.c @@ -0,0 +1,609 @@ +/* + * e-cal-shell-view-taskpad.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-view-private.h" + +/* Much of this file is based on e-task-shell-view-actions.c. */ + +static void +action_calendar_taskpad_assign_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + GSList *list; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected task. */ + e_cal_shell_view_taskpad_open_task (cal_shell_view, comp_data); + + /* FIXME Need to actually assign the task. */ +} + +static void +action_calendar_taskpad_forward_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ECalModelComponent *comp_data; + ETaskTable *task_table; + ECalComponent *comp; + icalcomponent *clone; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only forward the first selected task. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_PUBLISH, comp, + comp_data->client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (comp); +} + +static void +action_calendar_taskpad_mark_complete_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ETaskTable *task_table; + ECalModel *model; + GSList *list, *iter; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + list = e_task_table_get_selected (task_table); + model = e_task_table_get_model (task_table); + + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + e_cal_model_tasks_mark_comp_complete ( + E_CAL_MODEL_TASKS (model), comp_data); + } + + g_slist_free (list); +} + +static void +action_calendar_taskpad_mark_incomplete_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ETaskTable *task_table; + ECalModel *model; + GSList *list, *iter; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + list = e_task_table_get_selected (task_table); + model = e_task_table_get_model (task_table); + + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + e_cal_model_tasks_mark_comp_incomplete ( + E_CAL_MODEL_TASKS (model), comp_data); + } + + g_slist_free (list); +} + +static void +action_calendar_taskpad_new_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + ECalClient *client; + ECalComponent *comp; + CompEditor *editor; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + client = comp_data->client; + editor = task_editor_new (client, shell, COMP_EDITOR_NEW_ITEM); + comp = cal_comp_task_new_with_defaults (client); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); +} + +static void +action_calendar_taskpad_open_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + GSList *list; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected task. */ + e_cal_shell_view_taskpad_open_task (cal_shell_view, comp_data); +} + +static void +action_calendar_taskpad_open_url_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + icalproperty *prop; + const gchar *uri; + GSList *list; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + + /* XXX We only open the URI of the first selected task. */ + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + g_return_if_fail (prop != NULL); + + uri = icalproperty_get_url (prop); + e_show_uri (GTK_WINDOW (shell_window), uri); +} + +static void +action_calendar_taskpad_print_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + ECalComponent *comp; + ECalModel *model; + icalcomponent *clone; + GSList *list; + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + model = e_task_table_get_model (task_table); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only print the first selected task. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + print_comp ( + comp, comp_data->client, + e_cal_model_get_timezone (model), + e_cal_model_get_use_24_hour_format (model), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); + + g_object_unref (comp); +} + +static void +action_calendar_taskpad_save_as_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + ECalShellContent *cal_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + EActivity *activity; + GSList *list; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* Translators: Default filename part saving a task to a file when + * no summary is filed, the '.ics' extension is concatenated to it. */ + string = icalcomp_suggest_filename (comp_data->icalcomp, _("task")); + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), string, + "*.ics:text/calendar", NULL, NULL); + g_free (string); + if (file == NULL) + return; + + string = e_cal_client_get_component_as_string ( + comp_data->client, comp_data->icalcomp); + if (string == NULL) { + g_warning ("Could not convert task to a string"); + g_object_unref (file); + return; + } + + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); +} + +static GtkActionEntry calendar_taskpad_entries[] = { + + { "calendar-taskpad-assign", + NULL, + N_("_Assign Task"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_taskpad_assign_cb) }, + + { "calendar-taskpad-forward", + "mail-forward", + N_("_Forward as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_taskpad_forward_cb) }, + + { "calendar-taskpad-mark-complete", + NULL, + N_("_Mark as Complete"), + NULL, + N_("Mark selected tasks as complete"), + G_CALLBACK (action_calendar_taskpad_mark_complete_cb) }, + + { "calendar-taskpad-mark-incomplete", + NULL, + N_("_Mark as Incomplete"), + NULL, + N_("Mark selected tasks as incomplete"), + G_CALLBACK (action_calendar_taskpad_mark_incomplete_cb) }, + + { "calendar-taskpad-new", + "stock_task", + N_("New _Task"), + NULL, + N_("Create a new task"), + G_CALLBACK (action_calendar_taskpad_new_cb) }, + + { "calendar-taskpad-open", + GTK_STOCK_OPEN, + N_("_Open Task"), + NULL, + N_("View the selected task"), + G_CALLBACK (action_calendar_taskpad_open_cb) }, + + { "calendar-taskpad-open-url", + "applications-internet", + N_("Open _Web Page"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_taskpad_open_url_cb) }, +}; + +static GtkActionEntry lockdown_printing_entries[] = { + + { "calendar-taskpad-print", + GTK_STOCK_PRINT, + NULL, + NULL, + N_("Print the selected task"), + G_CALLBACK (action_calendar_taskpad_print_cb) } +}; + +static GtkActionEntry lockdown_save_to_disk_entries[] = { + + { "calendar-taskpad-save-as", + GTK_STOCK_SAVE_AS, + N_("_Save as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_calendar_taskpad_save_as_cb) } +}; + +void +e_cal_shell_view_taskpad_actions_init (ECalShellView *cal_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + GtkActionGroup *action_group; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + /* Calendar Actions */ + action_group = ACTION_GROUP (CALENDAR); + gtk_action_group_add_actions ( + action_group, calendar_taskpad_entries, + G_N_ELEMENTS (calendar_taskpad_entries), cal_shell_view); + + /* Lockdown Printing Actions */ + action_group = ACTION_GROUP (LOCKDOWN_PRINTING); + gtk_action_group_add_actions ( + action_group, lockdown_printing_entries, + G_N_ELEMENTS (lockdown_printing_entries), cal_shell_view); + + /* Lockdown Save-to-Disk Actions */ + action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK); + gtk_action_group_add_actions ( + action_group, lockdown_save_to_disk_entries, + G_N_ELEMENTS (lockdown_save_to_disk_entries), cal_shell_view); +} + +void +e_cal_shell_view_taskpad_actions_update (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + EShellWindow *shell_window; + EShellView *shell_view; + ETaskTable *task_table; + GtkAction *action; + GSList *list, *iter; + gboolean assignable = TRUE; + gboolean editable = TRUE; + gboolean has_url = FALSE; + gboolean sensitive; + gint n_selected; + gint n_complete = 0; + gint n_incomplete = 0; + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + + n_selected = e_table_selected_count (E_TABLE (task_table)); + + list = e_task_table_get_selected (task_table); + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + icalproperty *prop; + const gchar *cap; + gboolean read_only; + + read_only = e_client_is_readonly (E_CLIENT (comp_data->client)); + editable &= !read_only; + + cap = CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT; + if (e_client_check_capability (E_CLIENT (comp_data->client), cap)) + assignable = FALSE; + + cap = CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK; + if (e_client_check_capability (E_CLIENT (comp_data->client), cap)) + assignable = FALSE; + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + has_url |= (prop != NULL); + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_COMPLETED_PROPERTY); + if (prop != NULL) + n_complete++; + else + n_incomplete++; + } + g_slist_free (list); + + action = ACTION (CALENDAR_TASKPAD_ASSIGN); + sensitive = (n_selected == 1) && editable && assignable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_FORWARD); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_MARK_COMPLETE); + sensitive = (n_selected > 0) && editable && (n_incomplete > 0); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_MARK_INCOMPLETE); + sensitive = (n_selected > 0) && editable && (n_complete > 0); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_OPEN); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_OPEN_URL); + sensitive = (n_selected == 1) && has_url; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_PRINT); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_TASKPAD_SAVE_AS); + sensitive = (n_selected == 1); + gtk_action_set_sensitive (action, sensitive); +} + +void +e_cal_shell_view_taskpad_open_task (ECalShellView *cal_shell_view, + ECalModelComponent *comp_data) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + CompEditor *editor; + CompEditorFlags flags = 0; + ECalComponent *comp; + icalcomponent *clone; + icalproperty *prop; + const gchar *uid; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + uid = icalcomponent_get_uid (comp_data->icalcomp); + editor = comp_editor_find_instance (uid); + + if (editor != NULL) + goto exit; + + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY); + if (prop != NULL) + flags |= COMP_EDITOR_IS_ASSIGNED; + + if (itip_organizer_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + if (!e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + + editor = task_editor_new (comp_data->client, shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + + if (flags & COMP_EDITOR_IS_ASSIGNED) + task_editor_show_assignment (TASK_EDITOR (editor)); + +exit: + gtk_window_present (GTK_WINDOW (editor)); +} + +void +e_cal_shell_view_taskpad_set_status_message (ECalShellView *cal_shell_view, + const gchar *status_message, + gdouble percent) +{ + EActivity *activity; + EShellView *shell_view; + EShellBackend *shell_backend; + + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + + activity = cal_shell_view->priv->taskpad_activity; + + if (status_message == NULL || *status_message == '\0') { + if (activity != NULL) { + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_object_unref (activity); + activity = NULL; + } + + } else if (activity == NULL) { + activity = e_activity_new (); + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + e_shell_backend_add_activity (shell_backend, activity); + + } else { + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + } + + cal_shell_view->priv->taskpad_activity = activity; +} diff --git a/modules/calendar/e-cal-shell-view.c b/modules/calendar/e-cal-shell-view.c new file mode 100644 index 0000000000..624647bafb --- /dev/null +++ b/modules/calendar/e-cal-shell-view.c @@ -0,0 +1,632 @@ +/* + * e-cal-shell-view.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-shell-view-private.h" + +#include "calendar/gui/calendar-view.h" + +G_DEFINE_DYNAMIC_TYPE ( + ECalShellView, + e_cal_shell_view, + E_TYPE_SHELL_VIEW) + +static void +cal_shell_view_add_action_button (GtkBox *box, + GtkAction *action) +{ + GtkWidget *button, *icon; + + button = gtk_button_new (); + icon = gtk_action_create_icon (action, GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), icon); + gtk_box_pack_start (box, button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_object_bind_property ( + action, "visible", + button, "visible", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + action, "sensitive", + button, "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + action, "tooltip", + button, "tooltip-text", + G_BINDING_SYNC_CREATE); + + g_signal_connect_swapped ( + button, "clicked", + G_CALLBACK (gtk_action_activate), action); +} + +static void +cal_shell_view_prepare_for_quit_cb (EShell *shell, + EActivity *activity, + ECalShellView *cal_shell_view) +{ + g_return_if_fail (E_IS_CAL_SHELL_VIEW (cal_shell_view)); + + /* Stop running searches, if any; the activity tight + * on the search prevents application from quitting. */ + e_cal_shell_view_search_stop (cal_shell_view); +} + +static void +cal_shell_view_dispose (GObject *object) +{ + e_cal_shell_view_private_dispose (E_CAL_SHELL_VIEW (object)); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_cal_shell_view_parent_class)->dispose (object); +} + +static void +cal_shell_view_finalize (GObject *object) +{ + e_cal_shell_view_private_finalize (E_CAL_SHELL_VIEW (object)); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_cal_shell_view_parent_class)->finalize (object); +} + +static void +cal_shell_view_constructed (GObject *object) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + ECalShellView *cal_shell_view; + ECalShellContent *cal_shell_content; + GtkWidget *container; + GtkWidget *widget; + gulong handler_id; + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_cal_shell_view_parent_class)->constructed (object); + + cal_shell_view = E_CAL_SHELL_VIEW (object); + e_cal_shell_view_private_constructed (cal_shell_view); + + shell_view = E_SHELL_VIEW (cal_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + searchbar = e_cal_shell_content_get_searchbar (cal_shell_content); + container = e_shell_searchbar_get_search_box (searchbar); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + cal_shell_view_add_action_button ( + GTK_BOX (widget), ACTION (CALENDAR_SEARCH_PREV)); + cal_shell_view_add_action_button ( + GTK_BOX (widget), ACTION (CALENDAR_SEARCH_NEXT)); + cal_shell_view_add_action_button ( + GTK_BOX (widget), ACTION (CALENDAR_SEARCH_STOP)); + gtk_container_add (GTK_CONTAINER (container), widget); + gtk_widget_show (widget); + + handler_id = g_signal_connect ( + shell, "prepare-for-quit", + G_CALLBACK (cal_shell_view_prepare_for_quit_cb), + cal_shell_view); + + cal_shell_view->priv->shell = g_object_ref (shell); + cal_shell_view->priv->prepare_for_quit_handler_id = handler_id; +} + +static void +cal_shell_view_execute_search (EShellView *shell_view) +{ + ECalShellContent *cal_shell_content; + ECalShellSidebar *cal_shell_sidebar; + EShellWindow *shell_window; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GnomeCalendar *calendar; + ECalendar *date_navigator; + ECalModel *model; + GtkRadioAction *action; + icaltimezone *timezone; + const gchar *default_tzloc = NULL; + struct icaltimetype current_time; + time_t start_range; + time_t end_range; + time_t now_time; + gboolean range_search; + gchar *start, *end; + gchar *query; + gchar *temp; + gint value; + + e_cal_shell_view_search_stop (E_CAL_SHELL_VIEW (shell_view)); + + shell_window = e_shell_view_get_shell_window (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + + cal_shell_content = E_CAL_SHELL_CONTENT (shell_content); + cal_shell_sidebar = E_CAL_SHELL_SIDEBAR (shell_sidebar); + + searchbar = e_cal_shell_content_get_searchbar (cal_shell_content); + + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + model = gnome_calendar_get_model (calendar); + timezone = e_cal_model_get_timezone (model); + current_time = icaltime_current_time_with_zone (timezone); + now_time = time_day_begin (icaltime_as_timet (current_time)); + + if (timezone && timezone != icaltimezone_get_utc_timezone ()) + default_tzloc = icaltimezone_get_location (timezone); + if (!default_tzloc) + default_tzloc = ""; + + action = GTK_RADIO_ACTION (ACTION (CALENDAR_SEARCH_ANY_FIELD_CONTAINS)); + value = gtk_radio_action_get_current_value (action); + + if (value == CALENDAR_SEARCH_ADVANCED) { + query = e_shell_view_get_search_query (shell_view); + + if (!query) + query = g_strdup (""); + } else { + const gchar *format; + const gchar *text; + GString *string; + + text = e_shell_searchbar_get_search_text (searchbar); + + if (text == NULL || *text == '\0') { + text = ""; + value = CALENDAR_SEARCH_SUMMARY_CONTAINS; + } + + switch (value) { + default: + text = ""; + /* fall through */ + + case CALENDAR_SEARCH_SUMMARY_CONTAINS: + format = "(contains? \"summary\" %s)"; + break; + + case CALENDAR_SEARCH_DESCRIPTION_CONTAINS: + format = "(contains? \"description\" %s)"; + break; + + case CALENDAR_SEARCH_ANY_FIELD_CONTAINS: + format = "(contains? \"any\" %s)"; + break; + } + + /* Build the query. */ + string = g_string_new (""); + e_sexp_encode_string (string, text); + query = g_strdup_printf (format, string->str); + g_string_free (string, TRUE); + } + + range_search = FALSE; + start_range = end_range = 0; + + /* Apply selected filter. */ + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + value = e_action_combo_box_get_current_value (combo_box); + switch (value) { + case CALENDAR_FILTER_ANY_CATEGORY: + break; + + case CALENDAR_FILTER_UNMATCHED: + temp = g_strdup_printf ( + "(and (has-categories? #f) %s)", query); + g_free (query); + query = temp; + break; + + case CALENDAR_FILTER_ACTIVE_APPOINTMENTS: + /* Show a year's worth of appointments. */ + start_range = now_time; + end_range = time_day_end (time_add_day (start_range, 365)); + start = isodate_from_time_t (start_range); + end = isodate_from_time_t (end_range); + + temp = g_strdup_printf ( + "(and %s (occur-in-time-range? " + "(make-time \"%s\") " + "(make-time \"%s\") \"%s\"))", + query, start, end, default_tzloc); + g_free (query); + query = temp; + + range_search = TRUE; + break; + + case CALENDAR_FILTER_NEXT_7_DAYS_APPOINTMENTS: + start_range = now_time; + end_range = time_day_end (time_add_day (start_range, 7)); + start = isodate_from_time_t (start_range); + end = isodate_from_time_t (end_range); + + temp = g_strdup_printf ( + "(and %s (occur-in-time-range? " + "(make-time \"%s\") " + "(make-time \"%s\") \"%s\"))", + query, start, end, default_tzloc); + g_free (query); + query = temp; + + range_search = TRUE; + break; + + case CALENDAR_FILTER_OCCURS_LESS_THAN_5_TIMES: + temp = g_strdup_printf ( + "(and %s (< (occurrences-count?) 5))", query); + g_free (query); + query = temp; + break; + + default: + { + GList *categories; + const gchar *category_name; + + categories = e_util_get_searchable_categories (); + category_name = g_list_nth_data (categories, value); + g_list_free (categories); + + temp = g_strdup_printf ( + "(and (has-categories? \"%s\") %s)", + category_name, query); + g_free (query); + query = temp; + break; + } + } + + date_navigator = e_cal_shell_sidebar_get_date_navigator (cal_shell_sidebar); + + if (range_search) { + /* Switch to list view and hide the date navigator. */ + action = GTK_RADIO_ACTION (ACTION (CALENDAR_VIEW_LIST)); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + gtk_widget_hide (GTK_WIDGET (date_navigator)); + } else { + /* Ensure the date navigator is visible. */ + gtk_widget_show (GTK_WIDGET (date_navigator)); + } + + /* Submit the query. */ + gnome_calendar_set_search_query ( + calendar, query, range_search, start_range, end_range); + g_free (query); + + /* Update actions so Find Prev/Next/Stop + * buttons will be sensitive as expected. */ + e_shell_view_update_actions (shell_view); +} + +static void +cal_shell_view_update_actions (EShellView *shell_view) +{ + ECalShellViewPrivate *priv; + ECalShellContent *cal_shell_content; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + EShell *shell; + ESource *source; + ESourceRegistry *registry; + GnomeCalendar *calendar; + ECalendarView *cal_view; + EMemoTable *memo_table; + ETaskTable *task_table; + ECalModel *model; + GtkAction *action; + const gchar *model_sexp; + gboolean is_searching; + gboolean sensitive; + guint32 state; + + /* Be descriptive. */ + gboolean any_events_selected; + gboolean has_mail_identity = FALSE; + gboolean has_primary_source; + gboolean primary_source_is_writable; + gboolean primary_source_is_removable; + gboolean primary_source_is_remote_deletable; + gboolean primary_source_in_collection; + gboolean multiple_events_selected; + gboolean selection_is_editable; + gboolean selection_is_instance; + gboolean selection_is_meeting; + gboolean selection_is_recurring; + gboolean selection_can_delegate; + gboolean single_event_selected; + gboolean refresh_supported; + + /* Chain up to parent's update_actions() method. */ + E_SHELL_VIEW_CLASS (e_cal_shell_view_parent_class)-> + update_actions (shell_view); + + priv = E_CAL_SHELL_VIEW_GET_PRIVATE (shell_view); + + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_default_mail_identity (registry); + has_mail_identity = (source != NULL); + if (source != NULL) { + has_mail_identity = TRUE; + g_object_unref (source); + } + + cal_shell_content = priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + cal_view = gnome_calendar_get_calendar_view (calendar, gnome_calendar_get_view (calendar)); + memo_table = e_cal_shell_content_get_memo_table (cal_shell_content); + task_table = e_cal_shell_content_get_task_table (cal_shell_content); + model = gnome_calendar_get_model (calendar); + model_sexp = e_cal_model_get_search_query (model); + is_searching = model_sexp && *model_sexp && + g_strcmp0 (model_sexp, "#t") != 0 && + g_strcmp0 (model_sexp, "(contains? \"summary\" \"\")") != 0; + + shell_content = e_shell_view_get_shell_content (shell_view); + state = e_shell_content_check_state (shell_content); + + single_event_selected = + (state & E_CAL_SHELL_CONTENT_SELECTION_SINGLE); + multiple_events_selected = + (state & E_CAL_SHELL_CONTENT_SELECTION_MULTIPLE); + selection_is_editable = + (state & E_CAL_SHELL_CONTENT_SELECTION_IS_EDITABLE); + selection_is_instance = + (state & E_CAL_SHELL_CONTENT_SELECTION_IS_INSTANCE); + selection_is_meeting = + (state & E_CAL_SHELL_CONTENT_SELECTION_IS_MEETING); + selection_is_recurring = + (state & E_CAL_SHELL_CONTENT_SELECTION_IS_RECURRING); + selection_can_delegate = + (state & E_CAL_SHELL_CONTENT_SELECTION_CAN_DELEGATE); + + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + state = e_shell_sidebar_check_state (shell_sidebar); + + has_primary_source = + (state & E_CAL_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE); + primary_source_is_writable = + (state & E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE); + primary_source_is_removable = + (state & E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE); + primary_source_is_remote_deletable = + (state & E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE); + primary_source_in_collection = + (state & E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION); + refresh_supported = + (state & E_CAL_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH); + + any_events_selected = + (single_event_selected || multiple_events_selected); + + action = ACTION (CALENDAR_COPY); + sensitive = has_primary_source; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_DELETE); + sensitive = + primary_source_is_removable || + primary_source_is_remote_deletable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_PROPERTIES); + sensitive = primary_source_is_writable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_REFRESH); + sensitive = refresh_supported; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_RENAME); + sensitive = + primary_source_is_writable && + !primary_source_in_collection; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (CALENDAR_SEARCH_PREV); + gtk_action_set_sensitive (action, is_searching); + + action = ACTION (CALENDAR_SEARCH_NEXT); + gtk_action_set_sensitive (action, is_searching); + + action = ACTION (CALENDAR_SEARCH_STOP); + sensitive = is_searching && priv->searching_activity != NULL; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_DELEGATE); + sensitive = + single_event_selected && + selection_is_editable && + selection_can_delegate && + selection_is_meeting; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_DELETE); + sensitive = + any_events_selected && + selection_is_editable && + !selection_is_recurring; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_DELETE_OCCURRENCE); + sensitive = + any_events_selected && + selection_is_editable && + selection_is_recurring; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_DELETE_OCCURRENCE_ALL); + sensitive = + any_events_selected && + selection_is_editable && + selection_is_recurring; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_FORWARD); + sensitive = single_event_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_OCCURRENCE_MOVABLE); + sensitive = + single_event_selected && + selection_is_editable && + selection_is_recurring && + selection_is_instance; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_OPEN); + sensitive = single_event_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_PRINT); + sensitive = single_event_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_SAVE_AS); + sensitive = single_event_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_SCHEDULE); + sensitive = + single_event_selected && + selection_is_editable && + !selection_is_meeting; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_SCHEDULE_APPOINTMENT); + sensitive = + single_event_selected && + selection_is_editable && + selection_is_meeting; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_REPLY); + sensitive = single_event_selected && selection_is_meeting; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_REPLY_ALL); + sensitive = single_event_selected && selection_is_meeting; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (EVENT_MEETING_NEW); + gtk_action_set_visible (action, has_mail_identity); + + if ((cal_view && e_calendar_view_is_editing (cal_view)) || + e_table_is_editing (E_TABLE (memo_table)) || + e_table_is_editing (E_TABLE (task_table))) { + EFocusTracker *focus_tracker; + + /* disable all clipboard actions, if any of the views is in editing mode */ + focus_tracker = e_shell_window_get_focus_tracker (shell_window); + + action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); + if (action) + gtk_action_set_sensitive (action, FALSE); + + action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); + if (action) + gtk_action_set_sensitive (action, FALSE); + + action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); + if (action) + gtk_action_set_sensitive (action, FALSE); + + action = e_focus_tracker_get_delete_selection_action (focus_tracker); + if (action) + gtk_action_set_sensitive (action, FALSE); + } +} + +static void +e_cal_shell_view_class_init (ECalShellViewClass *class) +{ + GObjectClass *object_class; + EShellViewClass *shell_view_class; + + g_type_class_add_private (class, sizeof (ECalShellViewPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = cal_shell_view_dispose; + object_class->finalize = cal_shell_view_finalize; + object_class->constructed = cal_shell_view_constructed; + + shell_view_class = E_SHELL_VIEW_CLASS (class); + shell_view_class->label = _("Calendar"); + shell_view_class->icon_name = "x-office-calendar"; + shell_view_class->ui_definition = "evolution-calendars.ui"; + shell_view_class->ui_manager_id = "org.gnome.evolution.calendars"; + shell_view_class->search_options = "/calendar-search-options"; + shell_view_class->search_rules = "caltypes.xml"; + shell_view_class->new_shell_content = e_cal_shell_content_new; + shell_view_class->new_shell_sidebar = e_cal_shell_sidebar_new; + shell_view_class->execute_search = cal_shell_view_execute_search; + shell_view_class->update_actions = cal_shell_view_update_actions; + + /* Ensure the GalView types we need are registered. */ + g_type_ensure (GAL_TYPE_VIEW_CALENDAR_DAY); + g_type_ensure (GAL_TYPE_VIEW_CALENDAR_WORK_WEEK); + g_type_ensure (GAL_TYPE_VIEW_CALENDAR_WEEK); + g_type_ensure (GAL_TYPE_VIEW_CALENDAR_MONTH); + g_type_ensure (GAL_TYPE_VIEW_ETABLE); +} + +static void +e_cal_shell_view_class_finalize (ECalShellViewClass *class) +{ +} + +static void +e_cal_shell_view_init (ECalShellView *cal_shell_view) +{ + cal_shell_view->priv = + E_CAL_SHELL_VIEW_GET_PRIVATE (cal_shell_view); + + e_cal_shell_view_private_init (cal_shell_view); +} + +void +e_cal_shell_view_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_cal_shell_view_register_type (type_module); +} + diff --git a/modules/calendar/e-cal-shell-view.h b/modules/calendar/e-cal-shell-view.h new file mode 100644 index 0000000000..312033221e --- /dev/null +++ b/modules/calendar/e-cal-shell-view.h @@ -0,0 +1,66 @@ +/* + * e-cal-shell-view.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_VIEW_H +#define E_CAL_SHELL_VIEW_H + +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_CAL_SHELL_VIEW \ + (e_cal_shell_view_get_type ()) +#define E_CAL_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellView)) +#define E_CAL_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CAL_SHELL_VIEW, ECalShellViewClass)) +#define E_IS_CAL_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CAL_SHELL_VIEW)) +#define E_IS_CAL_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CAL_SHELL_VIEW)) +#define E_CAL_SHELL_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CAL_SHELL_VIEW, ECalShellViewClass)) + +G_BEGIN_DECLS + +typedef struct _ECalShellView ECalShellView; +typedef struct _ECalShellViewClass ECalShellViewClass; +typedef struct _ECalShellViewPrivate ECalShellViewPrivate; + +struct _ECalShellView { + EShellView parent; + ECalShellViewPrivate *priv; +}; + +struct _ECalShellViewClass { + EShellViewClass parent_class; +}; + +GType e_cal_shell_view_get_type (void); +void e_cal_shell_view_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_CAL_SHELL_VIEW_H */ diff --git a/modules/calendar/e-calendar-preferences.c b/modules/calendar/e-calendar-preferences.c new file mode 100644 index 0000000000..7e1f458b8d --- /dev/null +++ b/modules/calendar/e-calendar-preferences.c @@ -0,0 +1,972 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Damon Chaplin <damon@ximian.com> + * Ettore Perazzoli <ettore@ximian.com> + * David Trowbridge <trowbrds cs colorado edu> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-calendar-preferences.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "calendar/gui/e-cal-config.h" +#include "calendar/gui/e-timezone-entry.h" +#include "calendar/gui/calendar-config.h" +#include "shell/e-shell-utils.h" + +/* same is used for Birthdays & Anniversaries calendar */ +static const gint default_reminder_units_map[] = { + E_DURATION_MINUTES, E_DURATION_HOURS, E_DURATION_DAYS, -1 +}; + +G_DEFINE_DYNAMIC_TYPE ( + ECalendarPreferences, + e_calendar_preferences, + GTK_TYPE_VBOX) + +static gboolean +calendar_preferences_map_string_to_integer (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GEnumClass *enum_class = G_ENUM_CLASS (user_data); + GEnumValue *enum_value; + const gchar *nick; + + /* XXX GSettings should know how to bind enum settings to + * integer properties. I filed a bug asking for this: + * https://bugzilla.gnome.org/show_bug.cgi?id=695217 */ + + nick = g_variant_get_string (variant, NULL); + enum_value = g_enum_get_value_by_nick (enum_class, nick); + g_return_val_if_fail (enum_value != NULL, FALSE); + g_value_set_int (value, enum_value->value); + + return TRUE; +} + +static GVariant * +calendar_preferences_map_integer_to_string (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GEnumClass *enum_class = G_ENUM_CLASS (user_data); + GEnumValue *enum_value; + + /* XXX GSettings should know how to bind enum settings to + * integer properties. I filed a bug asking for this: + * https://bugzilla.gnome.org/show_bug.cgi?id=695217 */ + + enum_value = g_enum_get_value (enum_class, g_value_get_int (value)); + g_return_val_if_fail (enum_value != NULL, NULL); + + return g_variant_new_string (enum_value->value_nick); +} + +static gboolean +calendar_preferences_map_string_to_icaltimezone (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GSettings *settings; + const gchar *location = NULL; + icaltimezone *timezone = NULL; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + if (g_settings_get_boolean (settings, "use-system-timezone")) + timezone = e_cal_util_get_system_timezone (); + else + location = g_variant_get_string (variant, NULL); + + if (location != NULL && *location != '\0') + timezone = icaltimezone_get_builtin_timezone (location); + + if (timezone == NULL) + timezone = icaltimezone_get_utc_timezone (); + + g_value_set_pointer (value, timezone); + + g_object_unref (settings); + + return TRUE; +} + +static GVariant * +calendar_preferences_map_icaltimezone_to_string (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariant *variant; + GSettings *settings; + const gchar *location = NULL; + gchar *location_str = NULL; + icaltimezone *timezone; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + if (g_settings_get_boolean (settings, "use-system-timezone")) { + location_str = g_settings_get_string (settings, "timezone"); + location = location_str; + } else { + timezone = g_value_get_pointer (value); + + if (timezone != NULL) + location = icaltimezone_get_location (timezone); + } + + if (location == NULL) + location = "UTC"; + + variant = g_variant_new_string (location); + + g_free (location_str); + + g_object_unref (settings); + + return variant; +} + +static gboolean +calendar_preferences_map_time_divisions_to_index (GValue *value, + GVariant *variant, + gpointer user_data) +{ + gboolean success = TRUE; + + switch (g_variant_get_int32 (variant)) { + case 60: + g_value_set_int (value, 0); + break; + case 30: + g_value_set_int (value, 1); + break; + case 15: + g_value_set_int (value, 2); + break; + case 10: + g_value_set_int (value, 3); + break; + case 5: + g_value_set_int (value, 4); + break; + default: + success = FALSE; + } + + return success; +} + +static GVariant * +calendar_preferences_map_index_to_time_divisions (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + switch (g_value_get_int (value)) { + case 0: + return g_variant_new_int32 (60); + case 1: + return g_variant_new_int32 (30); + case 2: + return g_variant_new_int32 (15); + case 3: + return g_variant_new_int32 (10); + case 4: + return g_variant_new_int32 (5); + default: + break; + } + + return NULL; +} + +static gboolean +calendar_preferences_map_string_to_gdk_color (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GdkColor color; + const gchar *string; + gboolean success = FALSE; + + string = g_variant_get_string (variant, NULL); + if (gdk_color_parse (string, &color)) { + g_value_set_boxed (value, &color); + success = TRUE; + } + + return success; +} + +static GVariant * +calendar_preferences_map_gdk_color_to_string (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariant *variant; + const GdkColor *color; + + color = g_value_get_boxed (value); + if (color == NULL) { + variant = g_variant_new_string (""); + } else { + gchar *string; + + string = gdk_color_to_string (color); + variant = g_variant_new_string (string); + g_free (string); + } + + return variant; +} + +static void +calendar_preferences_dispose (GObject *object) +{ + ECalendarPreferences *prefs = (ECalendarPreferences *) object; + + if (prefs->builder != NULL) { + g_object_unref (prefs->builder); + prefs->builder = NULL; + } + + if (prefs->registry != NULL) { + g_object_unref (prefs->registry); + prefs->registry = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_calendar_preferences_parent_class)->dispose (object); +} + +static void +e_calendar_preferences_class_init (ECalendarPreferencesClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = calendar_preferences_dispose; +} + +static void +e_calendar_preferences_class_finalize (ECalendarPreferencesClass *class) +{ +} + +static void +e_calendar_preferences_init (ECalendarPreferences *preferences) +{ +} + +static GtkWidget * +calendar_preferences_get_config_widget (EConfig *ec, + EConfigItem *item, + GtkWidget *parent, + GtkWidget *old, + gint position, + gpointer data) +{ + ECalendarPreferences *preferences = data; + + return e_builder_get_widget (preferences->builder, item->label); +} + +static void +update_day_second_zone_caption (ECalendarPreferences *prefs) +{ + gchar *location; + const gchar *caption; + icaltimezone *zone; + + g_return_if_fail (prefs != NULL); + + /* Translators: "None" indicates no second time zone set for a day view */ + caption = C_("cal-second-zone", "None"); + + location = calendar_config_get_day_second_zone (); + if (location && *location) { + zone = icaltimezone_get_builtin_timezone (location); + if (zone && icaltimezone_get_display_name (zone)) { + caption = icaltimezone_get_display_name (zone); + } + } + g_free (location); + + gtk_button_set_label (GTK_BUTTON (prefs->day_second_zone), caption); +} + +static void +on_set_day_second_zone (GtkWidget *item, + ECalendarPreferences *prefs) +{ + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) + return; + + calendar_config_set_day_second_zone (g_object_get_data (G_OBJECT (item), "timezone")); + update_day_second_zone_caption (prefs); +} + +static void +on_select_day_second_zone (GtkWidget *item, + ECalendarPreferences *prefs) +{ + g_return_if_fail (prefs != NULL); + + calendar_config_select_day_second_zone (); + update_day_second_zone_caption (prefs); +} + +static void +day_second_zone_clicked (GtkWidget *widget, + ECalendarPreferences *prefs) +{ + GtkWidget *menu, *item; + GSList *group = NULL, *recent_zones, *s; + gchar *location; + icaltimezone *zone, *second_zone = NULL; + + menu = gtk_menu_new (); + + location = calendar_config_get_day_second_zone (); + if (location && *location) + second_zone = icaltimezone_get_builtin_timezone (location); + g_free (location); + + group = NULL; + item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None")); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + if (!second_zone) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect ( + item, "toggled", + G_CALLBACK (on_set_day_second_zone), prefs); + + recent_zones = calendar_config_get_day_second_zones (); + for (s = recent_zones; s != NULL; s = s->next) { + zone = icaltimezone_get_builtin_timezone (s->data); + if (!zone) + continue; + + item = gtk_radio_menu_item_new_with_label (group, icaltimezone_get_display_name (zone)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + /* both comes from builtin, thus no problem to compare pointers */ + if (zone == second_zone) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_object_set_data_full (G_OBJECT (item), "timezone", g_strdup (s->data), g_free); + g_signal_connect ( + item, "toggled", + G_CALLBACK (on_set_day_second_zone), prefs); + } + calendar_config_free_day_second_zones (recent_zones); + + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_menu_item_new_with_label (_("Select...")); + g_signal_connect ( + item, "activate", + G_CALLBACK (on_select_day_second_zone), prefs); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + gtk_widget_show_all (menu); + + gtk_menu_popup ( + GTK_MENU (menu), NULL, NULL, NULL, NULL, + 0, gtk_get_current_event_time ()); +} + +static void +start_of_day_changed (GtkWidget *widget, + ECalendarPreferences *prefs) +{ + EDateEdit *start, *end; + GSettings *settings; + gint start_hour; + gint start_minute; + gint end_hour; + gint end_minute; + + start = E_DATE_EDIT (prefs->start_of_day); + end = E_DATE_EDIT (prefs->end_of_day); + + e_date_edit_get_time_of_day (start, &start_hour, &start_minute); + e_date_edit_get_time_of_day (end, &end_hour, &end_minute); + + if ((start_hour > end_hour) || (start_hour == end_hour && start_minute > end_minute)) { + if (start_hour < 23) + e_date_edit_set_time_of_day (end, start_hour + 1, start_minute); + else + e_date_edit_set_time_of_day (end, 23, 59); + + return; + } + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_set_int (settings, "day-start-hour", start_hour); + g_settings_set_int (settings, "day-start-minute", start_minute); + + g_object_unref (settings); +} + +static void +end_of_day_changed (GtkWidget *widget, + ECalendarPreferences *prefs) +{ + EDateEdit *start, *end; + GSettings *settings; + gint start_hour; + gint start_minute; + gint end_hour; + gint end_minute; + + start = E_DATE_EDIT (prefs->start_of_day); + end = E_DATE_EDIT (prefs->end_of_day); + + e_date_edit_get_time_of_day (start, &start_hour, &start_minute); + e_date_edit_get_time_of_day (end, &end_hour, &end_minute); + + if ((end_hour < start_hour) || (end_hour == start_hour && end_minute < start_minute)) { + if (end_hour < 1) + e_date_edit_set_time_of_day (start, 0, 0); + else + e_date_edit_set_time_of_day (start, end_hour - 1, end_minute); + + return; + } + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_set_int (settings, "day-end-hour", end_hour); + g_settings_set_int (settings, "day-end-minute", end_minute); + + g_object_unref (settings); +} + +static void +update_system_tz_widgets (GtkCheckButton *button, + ECalendarPreferences *prefs) +{ + GtkWidget *widget; + icaltimezone *zone; + const gchar *display_name; + gchar *text; + + widget = e_builder_get_widget (prefs->builder, "system-tz-label"); + g_return_if_fail (GTK_IS_LABEL (widget)); + + zone = e_cal_util_get_system_timezone (); + if (zone != NULL) + display_name = gettext (icaltimezone_get_display_name (zone)); + else + display_name = "UTC"; + + text = g_strdup_printf ("(%s)", display_name); + gtk_label_set_text (GTK_LABEL (widget), text); + g_free (text); +} + +static void +setup_changes (ECalendarPreferences *prefs) +{ + g_signal_connect ( + prefs->day_second_zone, "clicked", + G_CALLBACK (day_second_zone_clicked), prefs); + + g_signal_connect ( + prefs->start_of_day, "changed", + G_CALLBACK (start_of_day_changed), prefs); + + g_signal_connect ( + prefs->end_of_day, "changed", + G_CALLBACK (end_of_day_changed), prefs); +} + +static void +show_alarms_config (ECalendarPreferences *prefs) +{ + GtkWidget *widget; + + widget = e_alarm_selector_new (prefs->registry); + atk_object_set_name ( + gtk_widget_get_accessible (widget), + _("Selected Calendars for Alarms")); + gtk_container_add (GTK_CONTAINER (prefs->scrolled_window), widget); + gtk_widget_show (widget); +} + +/* Shows the current config settings in the dialog. */ +static void +show_config (ECalendarPreferences *prefs) +{ + GSettings *settings; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + /* Day's second zone */ + update_day_second_zone_caption (prefs); + + /* Start of Day. */ + e_date_edit_set_time_of_day ( + E_DATE_EDIT (prefs->start_of_day), + g_settings_get_int (settings, "day-start-hour"), + g_settings_get_int (settings, "day-start-minute")); + + /* End of Day. */ + e_date_edit_set_time_of_day ( + E_DATE_EDIT (prefs->end_of_day), + g_settings_get_int (settings, "day-end-hour"), + g_settings_get_int (settings, "day-end-minute")); + + /* Alarms list */ + show_alarms_config (prefs); + + g_object_unref (settings); +} + +/* plugin meta-data */ +static ECalConfigItem eccp_items[] = { + { E_CONFIG_BOOK, (gchar *) "", (gchar *) "toplevel-notebook", calendar_preferences_get_config_widget }, + { E_CONFIG_PAGE, (gchar *) "00.general", (gchar *) "general", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION_TABLE, (gchar *) "00.general/00.time", (gchar *) "time", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION_TABLE, (gchar *) "00.general/10.workWeek", (gchar *) "workWeek", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION, (gchar *) "00.general/20.alerts", (gchar *) "alerts", calendar_preferences_get_config_widget }, + { E_CONFIG_PAGE, (gchar *) "10.display", (gchar *) "display", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION, (gchar *) "10.display/00.general", (gchar *) "displayGeneral", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION, (gchar *) "10.display/10.taskList", (gchar *) "taskList", calendar_preferences_get_config_widget }, + { E_CONFIG_PAGE, (gchar *) "12.tasks", (gchar *) "tasks-vbox", calendar_preferences_get_config_widget }, + { E_CONFIG_PAGE, (gchar *) "15.alarms", (gchar *) "alarms", calendar_preferences_get_config_widget }, + { E_CONFIG_PAGE, (gchar *) "20.freeBusy", (gchar *) "freebusy", calendar_preferences_get_config_widget }, + { E_CONFIG_SECTION, (gchar *) "20.freeBusy/00.defaultServer", (gchar *) "default-freebusy-vbox", calendar_preferences_get_config_widget }, +}; + +static void +eccp_free (EConfig *ec, + GSList *items, + gpointer data) +{ + g_slist_free (items); +} + +static void +calendar_preferences_construct (ECalendarPreferences *prefs, + EShell *shell) +{ + ECalConfig *ec; + ECalConfigTargetPrefs *target; + GSettings *settings; + GSettings *eds_settings; + gboolean locale_supports_12_hour_format; + gint i; + GtkWidget *toplevel; + GtkWidget *widget; + GtkWidget *table; + GSList *l; + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + locale_supports_12_hour_format = + calendar_config_locale_supports_12_hour_format (); + + /* Force 24 hour format for locales which don't support 12 hour format */ + if (!locale_supports_12_hour_format) + g_settings_set_boolean (settings, "use-24hour-format", TRUE); + + /* Make sure our custom widget classes are registered with + * GType before we load the GtkBuilder definition file. */ + g_type_ensure (E_TYPE_DATE_EDIT); + g_type_ensure (E_TYPE_TIMEZONE_ENTRY); + + prefs->builder = gtk_builder_new (); + e_load_ui_builder_definition (prefs->builder, "e-calendar-preferences.ui"); + + /** @HookPoint-ECalConfig: Calendar Preferences Page + * @Id: org.gnome.evolution.calendar.prefs + * @Class: org.gnome.evolution.calendar.config:1.0 + * @Target: ECalConfigTargetPrefs + * + * The mail calendar preferences page + */ + ec = e_cal_config_new ("org.gnome.evolution.calendar.prefs"); + l = NULL; + for (i = 0; i < G_N_ELEMENTS (eccp_items); i++) + l = g_slist_prepend (l, &eccp_items[i]); + e_config_add_items ((EConfig *) ec, l, eccp_free, prefs); + + widget = e_builder_get_widget (prefs->builder, "use-system-tz-check"); + g_settings_bind ( + settings, "use-system-timezone", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + g_signal_connect ( + widget, "toggled", + G_CALLBACK (update_system_tz_widgets), prefs); + update_system_tz_widgets (GTK_CHECK_BUTTON (widget), prefs); + + widget = e_builder_get_widget (prefs->builder, "timezone"); + g_settings_bind_with_mapping ( + settings, "timezone", + widget, "timezone", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_icaltimezone, + calendar_preferences_map_icaltimezone_to_string, + NULL, (GDestroyNotify) NULL); + g_settings_bind ( + settings, "use-system-timezone", + widget, "sensitive", + G_SETTINGS_BIND_DEFAULT | + G_SETTINGS_BIND_INVERT_BOOLEAN); + + /* General tab */ + prefs->day_second_zone = e_builder_get_widget (prefs->builder, "day_second_zone"); + + widget = e_builder_get_widget (prefs->builder, "sun_button"); + g_settings_bind ( + settings, "work-day-sunday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "mon_button"); + g_settings_bind ( + settings, "work-day-monday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "tue_button"); + g_settings_bind ( + settings, "work-day-tuesday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "wed_button"); + g_settings_bind ( + settings, "work-day-wednesday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "thu_button"); + g_settings_bind ( + settings, "work-day-thursday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "fri_button"); + g_settings_bind ( + settings, "work-day-friday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "sat_button"); + g_settings_bind ( + settings, "work-day-saturday", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "week_start_day"); + g_settings_bind ( + settings, "week-start-day-name", + widget, "active-id", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "start_of_day"); + prefs->start_of_day = widget; /* XXX delete this */ + if (locale_supports_12_hour_format) + g_settings_bind ( + settings, "use-24hour-format", + widget, "use-24-hour-format", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "end_of_day"); + prefs->end_of_day = widget; /* XXX delete this */ + if (locale_supports_12_hour_format) + g_settings_bind ( + settings, "use-24hour-format", + widget, "use-24-hour-format", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "use_12_hour"); + gtk_widget_set_sensitive (widget, locale_supports_12_hour_format); + g_settings_bind ( + settings, "use-24hour-format", + widget, "active", + G_SETTINGS_BIND_DEFAULT | + G_SETTINGS_BIND_INVERT_BOOLEAN); + + widget = e_builder_get_widget (prefs->builder, "use_24_hour"); + gtk_widget_set_sensitive (widget, locale_supports_12_hour_format); + g_settings_bind ( + settings, "use-24hour-format", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "confirm_delete"); + g_settings_bind ( + settings, "confirm-delete", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "default_reminder"); + g_settings_bind ( + settings, "use-default-reminder", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "default_reminder_interval"); + g_settings_bind ( + settings, "default-reminder-interval", + widget, "value", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind ( + settings, "use-default-reminder", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "default_reminder_units"); + g_settings_bind_with_mapping ( + settings, "default-reminder-units", + widget, "active", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_integer, + calendar_preferences_map_integer_to_string, + g_type_class_ref (E_TYPE_DURATION_TYPE), + (GDestroyNotify) g_type_class_unref); + g_settings_bind ( + settings, "use-default-reminder", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + /* These settings control the "Birthdays & Anniversaries" backend. */ + + eds_settings = + g_settings_new ("org.gnome.evolution-data-server.calendar"); + + widget = e_builder_get_widget (prefs->builder, "ba_reminder"); + g_settings_bind ( + eds_settings, "contacts-reminder-enabled", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "ba_reminder_interval"); + g_settings_bind ( + eds_settings, "contacts-reminder-interval", + widget, "value", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind ( + eds_settings, "contacts-reminder-enabled", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "ba_reminder_units"); + g_settings_bind_with_mapping ( + eds_settings, "contacts-reminder-units", + widget, "active", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_integer, + calendar_preferences_map_integer_to_string, + g_type_class_ref (E_TYPE_DURATION_TYPE), + (GDestroyNotify) g_type_class_unref); + g_settings_bind ( + eds_settings, "contacts-reminder-enabled", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + g_object_unref (eds_settings); + + /* Display tab */ + widget = e_builder_get_widget (prefs->builder, "time_divisions"); + g_settings_bind_with_mapping ( + settings, "time-divisions", + widget, "active", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_time_divisions_to_index, + calendar_preferences_map_index_to_time_divisions, + NULL, (GDestroyNotify) NULL); + + widget = e_builder_get_widget (prefs->builder, "show_end_times"); + g_settings_bind ( + settings, "show-event-end", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "compress_weekend"); + g_settings_bind ( + settings, "compress-weekend", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "show_week_numbers"); + g_settings_bind ( + settings, "show-week-numbers", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "recur_events_italic"); + g_settings_bind ( + settings, "recur-events-italic", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "month_scroll_by_week"); + g_settings_bind ( + settings, "month-scroll-by-week", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "tasks_due_today_highlight"); + g_settings_bind ( + settings, "task-due-today-highlight", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "tasks_due_today_color"); + g_settings_bind_with_mapping ( + settings, "task-due-today-color", + widget, "color", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_gdk_color, + calendar_preferences_map_gdk_color_to_string, + NULL, (GDestroyNotify) NULL); + g_settings_bind ( + settings, "task-due-today-highlight", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "tasks_overdue_highlight"); + g_settings_bind ( + settings, "task-overdue-highlight", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "tasks_overdue_color"); + g_settings_bind_with_mapping ( + settings, "task-overdue-color", + widget, "color", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_gdk_color, + calendar_preferences_map_gdk_color_to_string, + NULL, (GDestroyNotify) NULL); + g_settings_bind ( + settings, "task-overdue-highlight", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "tasks_hide_completed"); + g_settings_bind ( + settings, "hide-completed-tasks", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + widget = e_builder_get_widget (prefs->builder, "tasks_hide_completed_interval"); + g_settings_bind ( + settings, "hide-completed-tasks-value", + widget, "value", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind ( + settings, "hide-completed-tasks", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + widget = e_builder_get_widget (prefs->builder, "tasks_hide_completed_units"); + g_settings_bind_with_mapping ( + settings, "hide-completed-tasks-units", + widget, "active", + G_SETTINGS_BIND_DEFAULT, + calendar_preferences_map_string_to_integer, + calendar_preferences_map_integer_to_string, + g_type_class_ref (E_TYPE_DURATION_TYPE), + (GDestroyNotify) g_type_class_unref); + g_settings_bind ( + settings, "hide-completed-tasks", + widget, "sensitive", + G_SETTINGS_BIND_GET); + + /* Alarms tab */ + widget = e_builder_get_widget (prefs->builder, "notify_with_tray"); + g_settings_bind ( + settings, "notify-with-tray", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + + prefs->scrolled_window = e_builder_get_widget (prefs->builder, "calendar-source-scrolled-window"); + + /* Free/Busy tab */ + widget = e_builder_get_widget (prefs->builder, "template_url"); + g_settings_bind ( + settings, "publish-template", + widget, "text", + G_SETTINGS_BIND_DEFAULT); + + /* date/time format */ + table = e_builder_get_widget (prefs->builder, "datetime_format_table"); + e_datetime_format_add_setup_widget (table, 0, "calendar", "table", DTFormatKindDateTime, _("Ti_me and date:")); + e_datetime_format_add_setup_widget (table, 1, "calendar", "table", DTFormatKindDate, _("_Date only:")); + + /* Hook up and add the toplevel widget */ + + target = e_cal_config_target_new_prefs (ec); + e_config_set_target ((EConfig *) ec, (EConfigTarget *) target); + toplevel = e_config_create_widget ((EConfig *) ec); + gtk_container_add (GTK_CONTAINER (prefs), toplevel); + + show_config (prefs); + /* FIXME: weakref? */ + setup_changes (prefs); + + g_object_unref (settings); +} + +void +e_calendar_preferences_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_calendar_preferences_register_type (type_module); +} + +GtkWidget * +e_calendar_preferences_new (EPreferencesWindow *window) +{ + EShell *shell; + ESourceRegistry *registry; + ECalendarPreferences *preferences; + + shell = e_preferences_window_get_shell (window); + + registry = e_shell_get_registry (shell); + + g_return_val_if_fail (E_IS_SHELL (shell), NULL); + + preferences = g_object_new (E_TYPE_CALENDAR_PREFERENCES, NULL); + + preferences->registry = g_object_ref (registry); + + /* FIXME Kill this function. */ + calendar_preferences_construct (preferences, shell); + + return GTK_WIDGET (preferences); +} diff --git a/modules/calendar/e-calendar-preferences.h b/modules/calendar/e-calendar-preferences.h new file mode 100644 index 0000000000..3920cd1916 --- /dev/null +++ b/modules/calendar/e-calendar-preferences.h @@ -0,0 +1,83 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * David Trowbridge <trowbrds cs colorado edu> + * Damon Chaplin <damon@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef CAL_PREFERENCES_H +#define CAL_PREFERENCES_H + +#include <shell/e-shell.h> + +/* Standard GObject macros */ +#define E_TYPE_CALENDAR_PREFERENCES \ + (e_calendar_preferences_get_type ()) +#define E_CALENDAR_PREFERENCES(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CALENDAR_PREFERENCES, ECalendarPreferences)) +#define E_CALENDAR_PREFERENCES_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CALENDAR_PREFERENCES, ECalendarPreferencesClass)) +#define E_CALENDAR_IS_PREFERENCES(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CALENDAR_PREFERENCES)) +#define E_CALENDAR_IS_PREFERENCES_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CALENDAR_PREFERENCES)) +#define E_CALENDAR_PREFERENCES_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CALENDAR_PREFERENCES, ECalendarPreferencesClass)) + +G_BEGIN_DECLS + +typedef struct _ECalendarPreferences ECalendarPreferences; +typedef struct _ECalendarPreferencesClass ECalendarPreferencesClass; + +struct _ECalendarPreferences { + GtkBox parent; + + GtkBuilder *builder; + + ESourceRegistry *registry; + + /* General tab */ + GtkWidget *day_second_zone; + GtkWidget *start_of_day; + GtkWidget *end_of_day; + GtkWidget *ba_reminder; + GtkWidget *ba_reminder_interval; + GtkWidget *ba_reminder_units; + + /* Alarms tab */ + GtkWidget *scrolled_window; +}; + +struct _ECalendarPreferencesClass { + GtkBoxClass parent; +}; + +GType e_calendar_preferences_get_type (void); +void e_calendar_preferences_type_register + (GTypeModule *type_module); +GtkWidget * e_calendar_preferences_new (EPreferencesWindow *window); + +G_END_DECLS + +#endif /* CAL_PREFERENCES_H */ diff --git a/modules/calendar/e-calendar-preferences.ui b/modules/calendar/e-calendar-preferences.ui new file mode 100644 index 0000000000..7af3f2036d --- /dev/null +++ b/modules/calendar/e-calendar-preferences.ui @@ -0,0 +1,1508 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <!-- interface-requires evolution 0.0 --> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkAdjustment" id="adjustment1"> + <property name="upper">9999</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkAdjustment" id="adjustment2"> + <property name="upper">9999</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkAdjustment" id="adjustment3"> + <property name="value">1</property> + <property name="upper">9999</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkListStore" id="model2"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Minutes</col> + </row> + <row> + <col id="0" translatable="yes">Hours</col> + </row> + <row> + <col id="0" translatable="yes">Days</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model3"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Minutes</col> + </row> + <row> + <col id="0" translatable="yes">Hours</col> + </row> + <row> + <col id="0" translatable="yes">Days</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model4"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">60 minutes</col> + </row> + <row> + <col id="0" translatable="yes">30 minutes</col> + </row> + <row> + <col id="0" translatable="yes">15 minutes</col> + </row> + <row> + <col id="0" translatable="yes">10 minutes</col> + </row> + <row> + <col id="0" translatable="yes">05 minutes</col> + </row> + </data> + </object> + <object class="GtkListStore" id="model5"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Minutes</col> + </row> + <row> + <col id="0" translatable="yes">Hours</col> + </row> + <row> + <col id="0" translatable="yes">Days</col> + </row> + </data> + </object> + <object class="GtkNotebook" id="toplevel-notebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkVBox" id="general"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Time</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="time"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="label_second_zone"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Se_cond zone:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">day_second_zone</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox_second_zone"> + <property name="visible">True</property> + <child> + <object class="GtkButton" id="day_second_zone"> + <property name="label" translatable="yes" context="cal-second-zone">None</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label64"> + <property name="visible">True</property> + <property name="xpad">6</property> + <property name="label" translatable="yes">(Shown in a Day View)</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="ETimezoneEntry" id="timezone"> + <property name="visible">True</property> + <accessibility> + <relation type="labelled-by" target="timezone_label"/> + </accessibility> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="timezone_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Time _zone:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">timezone</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox_use_system_timezone"> + <property name="visible">True</property> + <child> + <object class="GtkCheckButton" id="use-system-tz-check"> + <property name="label" translatable="yes">Use s_ystem time zone</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="system-tz-label"> + <property name="visible">True</property> + <property name="xpad">5</property> + <property name="label">(system/tz)</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label_time_format"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Time format:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox_time_format"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkRadioButton" id="use_12_hour"> + <property name="label" translatable="yes">_12 hour (AM/PM)</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="use_24_hour"> + <property name="label" translatable="yes">_24 hour</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <property name="group">use_12_hour</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Work Week</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="workWeek"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="A weekday like "Monday" follows">Wee_k starts on:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">week_start_day</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Work days:</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="day_start_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Day begins:</property> + <property name="mnemonic_widget">start_of_day</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox5"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="mon_button"> + <property name="label" translatable="yes" comments="Monday">_Mon</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="mon_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Monday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="tue_button"> + <property name="label" translatable="yes" comments="Tuesday">_Tue</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="tue_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Tuesday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="wed_button"> + <property name="label" translatable="yes" comments="Wednesday">_Wed</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="wed_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Wednesday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="thu_button"> + <property name="label" translatable="yes" comments="Thursday">T_hu</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="thu_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Thursday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="fri_button"> + <property name="label" translatable="yes" comments="Friday">_Fri</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="fri_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Friday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="sat_button"> + <property name="label" translatable="yes" comments="Saturday">_Sat</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="sat_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Saturday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="sun_button"> + <property name="label" translatable="yes" comments="Sunday">S_un</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="sun_button-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes">Sunday</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">6</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="week_start_day"> + <property name="visible">True</property> + <items> + <item translatable="yes" id="monday">Monday</item> + <item translatable="yes" id="tuesday">Tuesday</item> + <item translatable="yes" id="wednesday">Wednesday</item> + <item translatable="yes" id="thursday">Thursday</item> + <item translatable="yes" id="friday">Friday</item> + <item translatable="yes" id="saturday">Saturday</item> + <item translatable="yes" id="sunday">Sunday</item> + </items> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox13"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="EDateEdit" id="start_of_day"> + <property name="visible">True</property> + <property name="show_date">False</property> + <property name="show_week_numbers">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="day_end_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Day _ends:</property> + <property name="mnemonic_widget">end_of_day</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="EDateEdit" id="end_of_day"> + <property name="visible">True</property> + <property name="show_date">False</property> + <property name="show_week_numbers">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Alerts</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="alerts"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="confirm_delete"> + <property name="label" translatable="yes">_Ask for confirmation when deleting items</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="general-space-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">General</property> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="display"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label17"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">General</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox8"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label19"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="displayGeneral"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkHBox" id="hbox_time_divisions"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label23"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Time divisions:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">time_divisions</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="time_divisions"> + <property name="visible">True</property> + <property name="model">model4</property> + <child> + <object class="GtkCellRendererText" id="renderer4"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="position">1</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="show_end_times"> + <property name="label" translatable="yes">_Show appointment end times in week and month view</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="compress_weekend"> + <property name="label" translatable="yes">_Compress weekends in month view</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="show_week_numbers"> + <property name="label" translatable="yes">Show week _numbers</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="recur_events_italic"> + <property name="label" translatable="yes">Show r_ecurring events in italic in bottom left calendar</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="month_scroll_by_week"> + <property name="label" translatable="yes">Sc_roll Month View by a week</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label65"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Date/Time Format</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox27"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label66"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="datetime_format_table"> + <property name="visible">True</property> + <property name="n_columns">3</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Display</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="tasks-vbox"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Task List</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox9"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label20"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="taskList"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkHBox" id="hbox14"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkCheckButton" id="tasks_due_today_highlight"> + <property name="label" translatable="yes">Highlight t_asks due today</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="tasks_due_today_color"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="title" translatable="yes">Pick a color</property> + <property name="color">#000000000000</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox15"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <object class="GtkCheckButton" id="tasks_overdue_highlight"> + <property name="label" translatable="yes">Highlight _overdue tasks</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="tasks_overdue_color"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="title" translatable="yes">Pick a color</property> + <property name="color">#000000000000</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox11"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="tasks_hide_completed"> + <property name="label" translatable="yes">_Hide completed tasks after</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="tasks_hide_completed_interval"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">adjustment3</property> + <property name="climb_rate">1</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="tasks_hide_completed_units"> + <property name="visible">True</property> + <property name="model">model5</property> + <child> + <object class="GtkCellRendererText" id="renderer5"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="tasks-tab-label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Tasks</property> + </object> + <packing> + <property name="position">2</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="alarms"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label67"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reminders</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="left_padding">10</property> + <property name="bottom_padding">10</property> + <child> + <object class="GtkVBox" id="alarm_options_vbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">4</property> + <child> + <object class="GtkCheckButton" id="notify_with_tray"> + <property name="label" translatable="yes">Display reminders in _notification area only</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="fill">False</property> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox6"> + <property name="visible">True</property> + <property name="spacing">4</property> + <child> + <object class="GtkCheckButton" id="default_reminder"> + <property name="label" translatable="yes" comments="This is the first half of a user preference. "Show a reminder [time-period] before every appointment"">Sh_ow a reminder</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="default_reminder_interval"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">adjustment1</property> + <property name="climb_rate">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="default_reminder_units"> + <property name="visible">True</property> + <property name="model">model2</property> + <child> + <object class="GtkCellRendererText" id="renderer2"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label16"> + <property name="visible">True</property> + <property name="label" translatable="yes" comments="This is the last half of a user preference. "Show a reminder [time-period] before every appointment"">before every appointment</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="fill">False</property> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox7"> + <property name="visible">True</property> + <property name="spacing">4</property> + <child> + <object class="GtkCheckButton" id="ba_reminder"> + <property name="label" translatable="yes" comments="This is the first half of a user preference. "Show a reminder [time-period] before every anniversary/birthday"">Show a _reminder</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="ba_reminder_interval"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">adjustment2</property> + <property name="climb_rate">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="ba_reminder_units"> + <property name="visible">True</property> + <property name="model">model3</property> + <child> + <object class="GtkCellRendererText" id="renderer3"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="ba_reminder_label"> + <property name="visible">True</property> + <property name="label" translatable="yes" comments="This is the last half of a user preference. "Show a reminder [time-period] before every anniversary/birthday"">before every anniversary/birthday</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="fill">False</property> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="fill">False</property> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label62"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Select the calendars for reminder notification</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="calendar-source-scrolled-window"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <property name="shadow_type">in</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="alarm-label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Reminders</property> + </object> + <packing> + <property name="tab_fill">False</property> + <property name="tab_expand">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="freebusy"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="default-freebusy-header"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Default Free/Busy Server</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="position">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="default-freebusy-alignment"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <object class="GtkVBox" id="default-freebusy-vbox"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkHBox" id="hbox18"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label33"> + <property name="visible">True</property> + <property name="label" translatable="yes">Template:</property> + </object> + <packing> + <property name="position">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="template_url"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="position">1</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label32"> + <property name="visible">True</property> + <property name="label" translatable="yes">%u and %d will be replaced by user and domain from the email address.</property> + <property name="wrap">True</property> + <attributes> + <attribute name="style" value="italic"/> + </attributes> + </object> + <packing> + <property name="position">1</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="position">4</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Publishing Information</property> + </object> + <packing> + <property name="position">4</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + <object class="GtkSizeGroup" id="day_begins_ends_sizegroup"> + <widgets> + <widget name="day_end_label"/> + <widget name="day_start_label"/> + </widgets> + </object> +</interface> diff --git a/modules/calendar/e-memo-shell-backend.c b/modules/calendar/e-memo-shell-backend.c new file mode 100644 index 0000000000..a277569f0c --- /dev/null +++ b/modules/calendar/e-memo-shell-backend.c @@ -0,0 +1,488 @@ +/* + * e-memo-shell-backend.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-backend.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libecal/libecal.h> + +#include "shell/e-shell.h" +#include "shell/e-shell-backend.h" +#include "shell/e-shell-window.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/dialogs/memo-editor.h" + +#include "e-memo-shell-migrate.h" +#include "e-memo-shell-view.h" + +#define E_MEMO_SHELL_BACKEND_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendPrivate)) + +#define E_MEMO_SHELL_BACKEND_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendPrivate)) + +struct _EMemoShellBackendPrivate { + gint placeholder; +}; + +G_DEFINE_DYNAMIC_TYPE ( + EMemoShellBackend, + e_memo_shell_backend, + E_TYPE_SHELL_BACKEND) + +static void +memo_shell_backend_new_memo (ECalClient *cal_client, + EShell *shell, + CompEditorFlags flags) +{ + ECalComponent *comp; + CompEditor *editor; + + comp = cal_comp_memo_new_with_defaults (cal_client); + cal_comp_update_time_by_active_window (comp, shell); + editor = memo_editor_new (cal_client, shell, flags); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); +} + +static void +memo_shell_backend_memo_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + memo_shell_backend_new_memo ( + E_CAL_CLIENT (client), shell, flags); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +memo_shell_backend_memo_shared_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + flags |= COMP_EDITOR_IS_SHARED; + flags |= COMP_EDITOR_USER_ORG; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + memo_shell_backend_new_memo ( + E_CAL_CLIENT (client), shell, flags); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +action_memo_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + ESource *source; + ESourceRegistry *registry; + EClientCache *client_cache; + const gchar *action_name; + + /* This callback is used for both memos and shared memos. */ + + shell = e_shell_window_get_shell (shell_window); + client_cache = e_shell_get_client_cache (shell); + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_default_memo_list (registry); + + /* Use a callback function appropriate for the action. */ + action_name = gtk_action_get_name (action); + if (g_strcmp0 (action_name, "memo-shared-new") == 0) + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_MEMO_LIST, + NULL, + memo_shell_backend_memo_shared_new_cb, + g_object_ref (shell)); + else + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_MEMO_LIST, + NULL, + memo_shell_backend_memo_new_cb, + g_object_ref (shell)); + + g_object_unref (source); +} + +static void +action_memo_list_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Memo List")); + + gtk_widget_show (dialog); +} + +static GtkActionEntry item_entries[] = { + + { "memo-new", + "stock_insert-note", + NC_("New", "Mem_o"), + "<Shift><Control>o", + N_("Create a new memo"), + G_CALLBACK (action_memo_new_cb) }, + + { "memo-shared-new", + "stock_insert-note", + NC_("New", "_Shared Memo"), + "<Shift><Control>h", + N_("Create a new shared memo"), + G_CALLBACK (action_memo_new_cb) } +}; + +static GtkActionEntry source_entries[] = { + + { "memo-list-new", + "stock_notes", + NC_("New", "Memo Li_st"), + NULL, + N_("Create a new memo list"), + G_CALLBACK (action_memo_list_new_cb) } +}; + +static gboolean +memo_shell_backend_handle_uri_cb (EShellBackend *shell_backend, + const gchar *uri) +{ + EShell *shell; + CompEditor *editor; + CompEditorFlags flags = 0; + EClient *client; + EClientCache *client_cache; + ECalComponent *comp; + ESource *source; + ESourceRegistry *registry; + SoupURI *soup_uri; + icalcomponent *icalcomp; + const gchar *cp; + gchar *source_uid = NULL; + gchar *comp_uid = NULL; + gchar *comp_rid = NULL; + gboolean handled = FALSE; + GError *error = NULL; + + shell = e_shell_backend_get_shell (shell_backend); + client_cache = e_shell_get_client_cache (shell); + + if (strncmp (uri, "memo:", 5) != 0) + return FALSE; + + soup_uri = soup_uri_new (uri); + + if (soup_uri == NULL) + return FALSE; + + cp = soup_uri_get_query (soup_uri); + if (cp == NULL) + goto exit; + + while (*cp != '\0') { + gchar *header; + gchar *content; + gsize header_len; + gsize content_len; + + header_len = strcspn (cp, "=&"); + + /* If it's malformed, give up. */ + if (cp[header_len] != '=') + break; + + header = (gchar *) cp; + header[header_len] = '\0'; + cp += header_len + 1; + + content_len = strcspn (cp, "&"); + + content = g_strndup (cp, content_len); + if (g_ascii_strcasecmp (header, "source-uid") == 0) + source_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-uid") == 0) + comp_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-rid") == 0) + comp_rid = g_strdup (content); + g_free (content); + + cp += content_len; + if (*cp == '&') { + cp++; + if (strcmp (cp, "amp;") == 0) + cp += 4; + } + } + + if (source_uid == NULL || comp_uid == NULL) + goto exit; + + /* URI is valid, so consider it handled. Whether + * we successfully open it is another matter... */ + handled = TRUE; + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_source (registry, source_uid); + if (source == NULL) { + g_printerr ("No source for UID '%s'\n", source_uid); + goto exit; + } + + client = e_client_cache_get_client_sync ( + client_cache, source, + E_SOURCE_EXTENSION_MEMO_LIST, + NULL, &error); + + /* Sanity check. */ + g_return_val_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL)), FALSE); + + if (error != NULL) { + g_warning ( + "%s: Failed to create/open client: %s", + G_STRFUNC, error->message); + g_object_unref (source); + g_error_free (error); + goto exit; + } + + g_object_unref (source); + source = NULL; + + /* XXX Copied from e_memo_shell_view_open_memo(). + * Clearly a new utility function is needed. */ + + editor = comp_editor_find_instance (comp_uid); + + if (editor != NULL) + goto present; + + e_cal_client_get_object_sync ( + E_CAL_CLIENT (client), comp_uid, + comp_rid, &icalcomp, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to get object: %s", + G_STRFUNC, error->message); + g_object_unref (client); + g_error_free (error); + goto exit; + } + + comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { + g_warning ("%s: Failed to set icalcomp to comp\n", G_STRFUNC); + icalcomponent_free (icalcomp); + icalcomp = NULL; + } + + if (e_cal_component_has_organizer (comp)) + flags |= COMP_EDITOR_IS_SHARED; + + if (itip_organizer_is_user (registry, comp, E_CAL_CLIENT (client))) + flags |= COMP_EDITOR_USER_ORG; + + editor = memo_editor_new (E_CAL_CLIENT (client), shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +present: + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (client); + +exit: + g_free (source_uid); + g_free (comp_uid); + g_free (comp_rid); + + soup_uri_free (soup_uri); + + return handled; +} + +static void +memo_shell_backend_window_added_cb (EShellBackend *shell_backend, + GtkWindow *window) +{ + const gchar *module_name; + + if (!E_IS_SHELL_WINDOW (window)) + return; + + module_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name; + + e_shell_window_register_new_item_actions ( + E_SHELL_WINDOW (window), module_name, + item_entries, G_N_ELEMENTS (item_entries)); + + e_shell_window_register_new_source_actions ( + E_SHELL_WINDOW (window), module_name, + source_entries, G_N_ELEMENTS (source_entries)); +} + +static void +memo_shell_backend_constructed (GObject *object) +{ + EShell *shell; + EShellBackend *shell_backend; + + shell_backend = E_SHELL_BACKEND (object); + shell = e_shell_backend_get_shell (shell_backend); + + g_signal_connect_swapped ( + shell, "handle-uri", + G_CALLBACK (memo_shell_backend_handle_uri_cb), + shell_backend); + + g_signal_connect_swapped ( + shell, "window-added", + G_CALLBACK (memo_shell_backend_window_added_cb), + shell_backend); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_memo_shell_backend_parent_class)->constructed (object); +} + +static void +e_memo_shell_backend_class_init (EMemoShellBackendClass *class) +{ + GObjectClass *object_class; + EShellBackendClass *shell_backend_class; + + g_type_class_add_private (class, sizeof (EMemoShellBackendPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = memo_shell_backend_constructed; + + shell_backend_class = E_SHELL_BACKEND_CLASS (class); + shell_backend_class->shell_view_type = E_TYPE_MEMO_SHELL_VIEW; + shell_backend_class->name = "memos"; + shell_backend_class->aliases = ""; + shell_backend_class->schemes = "memo"; + shell_backend_class->sort_order = 600; + shell_backend_class->preferences_page = "calendar-and-tasks"; + shell_backend_class->start = NULL; + shell_backend_class->migrate = e_memo_shell_backend_migrate; + + /* Register relevant ESource extensions. */ + E_TYPE_SOURCE_MEMO_LIST; +} + +static void +e_memo_shell_backend_class_finalize (EMemoShellBackendClass *class) +{ +} + +static void +e_memo_shell_backend_init (EMemoShellBackend *memo_shell_backend) +{ + memo_shell_backend->priv = + E_MEMO_SHELL_BACKEND_GET_PRIVATE (memo_shell_backend); +} + +void +e_memo_shell_backend_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_memo_shell_backend_register_type (type_module); +} diff --git a/modules/calendar/e-memo-shell-backend.h b/modules/calendar/e-memo-shell-backend.h new file mode 100644 index 0000000000..f759e5e138 --- /dev/null +++ b/modules/calendar/e-memo-shell-backend.h @@ -0,0 +1,67 @@ +/* + * e-memo-shell-backend.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_BACKEND_H +#define E_MEMO_SHELL_BACKEND_H + +#include <shell/e-shell-backend.h> + +/* Standard GObject macros */ +#define E_TYPE_MEMO_SHELL_BACKEND \ + (e_memo_shell_backend_get_type ()) +#define E_MEMO_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackend)) +#define E_MEMO_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendClass)) +#define E_IS_MEMO_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_MEMO_SHELL_BACKEND)) +#define E_IS_MEMO_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_MEMO_SHELL_BACKEND)) +#define E_MEMO_SHELL_BACKEND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_MEMO_SHELL_BACKEND, EMemoShellBackendClass)) + +G_BEGIN_DECLS + +typedef struct _EMemoShellBackend EMemoShellBackend; +typedef struct _EMemoShellBackendClass EMemoShellBackendClass; +typedef struct _EMemoShellBackendPrivate EMemoShellBackendPrivate; + +struct _EMemoShellBackend { + EShellBackend parent; + EMemoShellBackendPrivate *priv; +}; + +struct _EMemoShellBackendClass { + EShellBackendClass parent_class; +}; + +GType e_memo_shell_backend_get_type (void); +void e_memo_shell_backend_type_register + (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_BACKEND_H */ diff --git a/modules/calendar/e-memo-shell-content.c b/modules/calendar/e-memo-shell-content.c new file mode 100644 index 0000000000..bf715ec4d9 --- /dev/null +++ b/modules/calendar/e-memo-shell-content.c @@ -0,0 +1,766 @@ +/* + * e-memo-shell-content.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-content.h" + +#include <glib/gi18n.h> + +#include "shell/e-shell-utils.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" +#include "calendar/gui/e-cal-model-memos.h" +#include "calendar/gui/e-memo-table.h" + +#define E_MEMO_SHELL_CONTENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentPrivate)) + +struct _EMemoShellContentPrivate { + GtkWidget *paned; + GtkWidget *memo_table; + GtkWidget *preview_pane; + + ECalModel *memo_model; + GtkOrientation orientation; + + gchar *current_uid; + + guint preview_visible : 1; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_ORIENTATION, + PROP_PREVIEW_VISIBLE +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + EMemoShellContent, + e_memo_shell_content, + E_TYPE_SHELL_CONTENT, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + GTK_TYPE_ORIENTABLE, NULL)) + +static void +memo_shell_content_display_view_cb (EMemoShellContent *memo_shell_content, + GalView *gal_view) +{ + EMemoTable *memo_table; + + if (!GAL_IS_VIEW_ETABLE (gal_view)) + return; + + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + gal_view_etable_attach_table ( + GAL_VIEW_ETABLE (gal_view), E_TABLE (memo_table)); +} + +static void +memo_shell_content_table_foreach_cb (gint model_row, + gpointer user_data) +{ + ECalModelComponent *comp_data; + icalcomponent *clone; + icalcomponent *vcal; + gchar *string; + + struct { + ECalModel *model; + GSList *list; + } *foreach_data = user_data; + + comp_data = e_cal_model_get_component_at ( + foreach_data->model, model_row); + + vcal = e_cal_util_new_top_level (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_util_add_timezones_from_component (vcal, comp_data->icalcomp); + icalcomponent_add_component (vcal, clone); + + /* String is owned by libical; do not free. */ + string = icalcomponent_as_ical_string (vcal); + if (string != NULL) { + ESource *source; + const gchar *source_uid; + + source = e_client_get_source (E_CLIENT (comp_data->client)); + source_uid = e_source_get_uid (source); + + foreach_data->list = g_slist_prepend ( + foreach_data->list, + g_strdup_printf ("%s\n%s", source_uid, string)); + } + + icalcomponent_free (vcal); +} + +static void +memo_shell_content_table_drag_data_get_cb (EMemoShellContent *memo_shell_content, + gint row, + gint col, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + EMemoTable *memo_table; + GdkAtom target; + + struct { + ECalModel *model; + GSList *list; + } foreach_data; + + /* Sanity check the selection target. */ + target = gtk_selection_data_get_target (selection_data); + if (!e_targets_include_calendar (&target, 1)) + return; + + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + foreach_data.model = e_memo_table_get_model (memo_table); + foreach_data.list = NULL; + + e_table_selected_row_foreach ( + E_TABLE (memo_table), + memo_shell_content_table_foreach_cb, + &foreach_data); + + if (foreach_data.list != NULL) { + cal_comp_selection_set_string_list ( + selection_data, foreach_data.list); + g_slist_foreach (foreach_data.list, (GFunc) g_free, NULL); + g_slist_free (foreach_data.list); + } +} + +static void +memo_shell_content_table_drag_data_delete_cb (EMemoShellContent *memo_shell_content, + gint row, + gint col, + GdkDragContext *context) +{ + /* Moved components are deleted from source immediately when moved, + * because some of them can be part of destination source, and we + * don't want to delete not-moved memos. There is no such information + * which event has been moved and which not, so skip this method. */ +} + +static void +memo_shell_content_cursor_change_cb (EMemoShellContent *memo_shell_content, + gint row, + ETable *table) +{ + ECalComponentPreview *memo_preview; + ECalModel *memo_model; + ECalModelComponent *comp_data; + EPreviewPane *preview_pane; + EWebView *web_view; + const gchar *uid; + + memo_model = e_memo_shell_content_get_memo_model (memo_shell_content); + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); + + if (e_table_selected_count (table) != 1) { + if (memo_shell_content->priv->preview_visible) + e_cal_component_preview_clear (memo_preview); + return; + } + + row = e_table_get_cursor_row (table); + comp_data = e_cal_model_get_component_at (memo_model, row); + + if (memo_shell_content->priv->preview_visible) { + ECalComponent *comp; + + comp = e_cal_component_new_from_icalcomponent ( + icalcomponent_new_clone (comp_data->icalcomp)); + + e_cal_component_preview_display ( + memo_preview, comp_data->client, comp, + e_cal_model_get_timezone (memo_model), + e_cal_model_get_use_24_hour_format (memo_model)); + + g_object_unref (comp); + } + + uid = icalcomponent_get_uid (comp_data->icalcomp); + g_free (memo_shell_content->priv->current_uid); + memo_shell_content->priv->current_uid = g_strdup (uid); +} + +static void +memo_shell_content_selection_change_cb (EMemoShellContent *memo_shell_content, + ETable *table) +{ + ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; + EWebView *web_view; + + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); + + /* XXX Old code emits a "selection-changed" signal here. */ + + if (e_table_selected_count (table) != 1) + e_cal_component_preview_clear (memo_preview); +} + +static void +memo_shell_content_model_row_changed_cb (EMemoShellContent *memo_shell_content, + gint row, + ETableModel *model) +{ + ECalModelComponent *comp_data; + EMemoTable *memo_table; + const gchar *current_uid; + const gchar *uid; + + current_uid = memo_shell_content->priv->current_uid; + if (current_uid == NULL) + return; + + comp_data = e_cal_model_get_component_at (E_CAL_MODEL (model), row); + if (comp_data == NULL) + return; + + uid = icalcomponent_get_uid (comp_data->icalcomp); + if (g_strcmp0 (uid, current_uid) != 0) + return; + + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + memo_shell_content_cursor_change_cb ( + memo_shell_content, 0, E_TABLE (memo_table)); +} + +static void +memo_shell_content_restore_state_cb (EShellWindow *shell_window, + EShellView *shell_view, + EShellContent *shell_content) +{ + EMemoShellContentPrivate *priv; + GSettings *settings; + + priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (shell_content); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "memo-hpane-position", + priv->paned, "hposition", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind ( + settings, "memo-vpane-position", + priv->paned, "vposition", + G_SETTINGS_BIND_DEFAULT); +} + +static void +memo_shell_content_is_editing_changed_cb (EMemoTable *memo_table, + GParamSpec *param, + EShellView *shell_view) +{ + g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); + + e_shell_view_update_actions (shell_view); +} + +static GtkOrientation +memo_shell_content_get_orientation (EMemoShellContent *memo_shell_content) +{ + return memo_shell_content->priv->orientation; +} + +static void +memo_shell_content_set_orientation (EMemoShellContent *memo_shell_content, + GtkOrientation orientation) +{ + if (memo_shell_content->priv->orientation == orientation) + return; + + memo_shell_content->priv->orientation = orientation; + + g_object_notify (G_OBJECT (memo_shell_content), "orientation"); +} + +static void +memo_shell_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ORIENTATION: + memo_shell_content_set_orientation ( + E_MEMO_SHELL_CONTENT (object), + g_value_get_enum (value)); + return; + + case PROP_PREVIEW_VISIBLE: + e_memo_shell_content_set_preview_visible ( + E_MEMO_SHELL_CONTENT (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +memo_shell_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_MODEL: + g_value_set_object ( + value, + e_memo_shell_content_get_memo_model ( + E_MEMO_SHELL_CONTENT (object))); + return; + + case PROP_ORIENTATION: + g_value_set_enum ( + value, + memo_shell_content_get_orientation ( + E_MEMO_SHELL_CONTENT (object))); + return; + + case PROP_PREVIEW_VISIBLE: + g_value_set_boolean ( + value, + e_memo_shell_content_get_preview_visible ( + E_MEMO_SHELL_CONTENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +memo_shell_content_dispose (GObject *object) +{ + EMemoShellContentPrivate *priv; + + priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object); + + if (priv->paned != NULL) { + g_object_unref (priv->paned); + priv->paned = NULL; + } + + if (priv->memo_table != NULL) { + g_object_unref (priv->memo_table); + priv->memo_table = NULL; + } + + if (priv->preview_pane != NULL) { + g_object_unref (priv->preview_pane); + priv->preview_pane = NULL; + } + + if (priv->memo_model != NULL) { + g_object_unref (priv->memo_model); + priv->memo_model = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_memo_shell_content_parent_class)->dispose (object); +} + +static void +memo_shell_content_finalize (GObject *object) +{ + EMemoShellContentPrivate *priv; + + priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object); + + g_free (priv->current_uid); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_memo_shell_content_parent_class)->finalize (object); +} + +static void +memo_shell_content_constructed (GObject *object) +{ + EMemoShellContentPrivate *priv; + EShell *shell; + EShellView *shell_view; + EShellContent *shell_content; + EShellTaskbar *shell_taskbar; + EShellWindow *shell_window; + ESourceRegistry *registry; + GalViewInstance *view_instance; + GtkTargetList *target_list; + GtkTargetEntry *targets; + GtkWidget *container; + GtkWidget *widget; + gint n_targets; + + priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_memo_shell_content_parent_class)->constructed (object); + + shell_content = E_SHELL_CONTENT (object); + shell_view = e_shell_content_get_shell_view (shell_content); + shell_taskbar = e_shell_view_get_shell_taskbar (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + priv->memo_model = e_cal_model_memos_new (registry); + + /* Build content widgets. */ + + container = GTK_WIDGET (object); + + widget = e_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->paned = g_object_ref (widget); + gtk_widget_show (widget); + + g_object_bind_property ( + object, "orientation", + widget, "orientation", + G_BINDING_SYNC_CREATE); + + container = priv->paned; + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE); + gtk_widget_show (widget); + + container = widget; + + widget = e_memo_table_new (shell_view, priv->memo_model); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->memo_table = g_object_ref (widget); + gtk_widget_show (widget); + + container = priv->paned; + + widget = e_cal_component_preview_new (); + gtk_widget_show (widget); + + g_signal_connect_swapped ( + widget, "status-message", + G_CALLBACK (e_shell_taskbar_set_message), + shell_taskbar); + + widget = e_preview_pane_new (E_WEB_VIEW (widget)); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); + + g_object_bind_property ( + object, "preview-visible", + widget, "visible", + G_BINDING_SYNC_CREATE); + + target_list = gtk_target_list_new (NULL, 0); + e_target_list_add_calendar_targets (target_list, 0); + targets = gtk_target_table_new_from_list (target_list, &n_targets); + + e_table_drag_source_set ( + E_TABLE (priv->memo_table), + GDK_BUTTON1_MASK, targets, n_targets, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_ASK); + + gtk_target_table_free (targets, n_targets); + gtk_target_list_unref (target_list); + + g_signal_connect_swapped ( + priv->memo_table, "table-drag-data-get", + G_CALLBACK (memo_shell_content_table_drag_data_get_cb), + object); + + g_signal_connect_swapped ( + priv->memo_table, "table-drag-data-delete", + G_CALLBACK (memo_shell_content_table_drag_data_delete_cb), + object); + + g_signal_connect_swapped ( + priv->memo_table, "cursor-change", + G_CALLBACK (memo_shell_content_cursor_change_cb), + object); + + g_signal_connect_swapped ( + priv->memo_table, "selection-change", + G_CALLBACK (memo_shell_content_selection_change_cb), + object); + + g_signal_connect ( + priv->memo_table, "notify::is-editing", + G_CALLBACK (memo_shell_content_is_editing_changed_cb), shell_view); + + g_signal_connect_swapped ( + priv->memo_model, "model-row-changed", + G_CALLBACK (memo_shell_content_model_row_changed_cb), + object); + + /* Load the view instance. */ + + view_instance = e_shell_view_new_view_instance (shell_view, NULL); + g_signal_connect_swapped ( + view_instance, "display-view", + G_CALLBACK (memo_shell_content_display_view_cb), + object); + e_shell_view_set_view_instance (shell_view, view_instance); + gal_view_instance_load (view_instance); + g_object_unref (view_instance); + + /* Restore pane positions from the last session once + * the shell view is fully initialized and visible. */ + g_signal_connect ( + shell_window, "shell-view-created::memos", + G_CALLBACK (memo_shell_content_restore_state_cb), + shell_content); +} + +static guint32 +memo_shell_content_check_state (EShellContent *shell_content) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + GSList *list, *iter; + gboolean editable = TRUE; + gboolean has_url = FALSE; + gint n_selected; + guint32 state = 0; + + memo_shell_content = E_MEMO_SHELL_CONTENT (shell_content); + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + n_selected = e_table_selected_count (E_TABLE (memo_table)); + + list = e_memo_table_get_selected (memo_table); + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + icalproperty *prop; + gboolean read_only; + + read_only = e_client_is_readonly (E_CLIENT (comp_data->client)); + editable &= !read_only; + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + has_url |= (prop != NULL); + } + g_slist_free (list); + + if (n_selected == 1) + state |= E_MEMO_SHELL_CONTENT_SELECTION_SINGLE; + if (n_selected > 1) + state |= E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE; + if (editable) + state |= E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT; + if (has_url) + state |= E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL; + + return state; +} + +static void +memo_shell_content_focus_search_results (EShellContent *shell_content) +{ + EMemoShellContentPrivate *priv; + + priv = E_MEMO_SHELL_CONTENT_GET_PRIVATE (shell_content); + + gtk_widget_grab_focus (priv->memo_table); +} + +static void +e_memo_shell_content_class_init (EMemoShellContentClass *class) +{ + GObjectClass *object_class; + EShellContentClass *shell_content_class; + + g_type_class_add_private (class, sizeof (EMemoShellContentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = memo_shell_content_set_property; + object_class->get_property = memo_shell_content_get_property; + object_class->dispose = memo_shell_content_dispose; + object_class->finalize = memo_shell_content_finalize; + object_class->constructed = memo_shell_content_constructed; + + shell_content_class = E_SHELL_CONTENT_CLASS (class); + shell_content_class->check_state = memo_shell_content_check_state; + shell_content_class->focus_search_results = + memo_shell_content_focus_search_results; + + g_object_class_install_property ( + object_class, + PROP_MODEL, + g_param_spec_object ( + "model", + "Model", + "The memo table model", + E_TYPE_CAL_MODEL, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_PREVIEW_VISIBLE, + g_param_spec_boolean ( + "preview-visible", + "Preview is Visible", + "Whether the preview pane is visible", + TRUE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_override_property ( + object_class, PROP_ORIENTATION, "orientation"); +} + +static void +e_memo_shell_content_class_finalize (EMemoShellContentClass *class) +{ +} + +static void +e_memo_shell_content_init (EMemoShellContent *memo_shell_content) +{ + memo_shell_content->priv = + E_MEMO_SHELL_CONTENT_GET_PRIVATE (memo_shell_content); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_memo_shell_content_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_memo_shell_content_register_type (type_module); +} + +GtkWidget * +e_memo_shell_content_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_MEMO_SHELL_CONTENT, + "shell-view", shell_view, NULL); +} + +ECalModel * +e_memo_shell_content_get_memo_model (EMemoShellContent *memo_shell_content) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); + + return memo_shell_content->priv->memo_model; +} + +EMemoTable * +e_memo_shell_content_get_memo_table (EMemoShellContent *memo_shell_content) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); + + return E_MEMO_TABLE (memo_shell_content->priv->memo_table); +} + +EPreviewPane * +e_memo_shell_content_get_preview_pane (EMemoShellContent *memo_shell_content) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); + + return E_PREVIEW_PANE (memo_shell_content->priv->preview_pane); +} + +gboolean +e_memo_shell_content_get_preview_visible (EMemoShellContent *memo_shell_content) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_CONTENT (memo_shell_content), FALSE); + + return memo_shell_content->priv->preview_visible; +} + +void +e_memo_shell_content_set_preview_visible (EMemoShellContent *memo_shell_content, + gboolean preview_visible) +{ + g_return_if_fail (E_IS_MEMO_SHELL_CONTENT (memo_shell_content)); + + if (memo_shell_content->priv->preview_visible == preview_visible) + return; + + memo_shell_content->priv->preview_visible = preview_visible; + + if (preview_visible && memo_shell_content->priv->preview_pane) { + memo_shell_content_cursor_change_cb ( + memo_shell_content, 0, + E_TABLE (memo_shell_content->priv->memo_table)); + } + + g_object_notify (G_OBJECT (memo_shell_content), "preview-visible"); +} + +EShellSearchbar * +e_memo_shell_content_get_searchbar (EMemoShellContent *memo_shell_content) +{ + EShellView *shell_view; + EShellContent *shell_content; + GtkWidget *widget; + + g_return_val_if_fail ( + E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); + + shell_content = E_SHELL_CONTENT (memo_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + widget = e_shell_view_get_searchbar (shell_view); + + return E_SHELL_SEARCHBAR (widget); +} + diff --git a/modules/calendar/e-memo-shell-content.h b/modules/calendar/e-memo-shell-content.h new file mode 100644 index 0000000000..29b6985c4c --- /dev/null +++ b/modules/calendar/e-memo-shell-content.h @@ -0,0 +1,94 @@ +/* + * e-memo-shell-content.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_CONTENT_H +#define E_MEMO_SHELL_CONTENT_H + +#include <shell/e-shell-content.h> +#include <shell/e-shell-searchbar.h> +#include <shell/e-shell-view.h> + +#include <calendar/gui/e-memo-table.h> + +/* Standard GObject macros */ +#define E_TYPE_MEMO_SHELL_CONTENT \ + (e_memo_shell_content_get_type ()) +#define E_MEMO_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContent)) +#define E_MEMO_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentClass)) +#define E_IS_MEMO_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_MEMO_SHELL_CONTENT)) +#define E_IS_MEMO_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_MEMO_SHELL_CONTENT)) +#define E_MEMO_SHELL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_MEMO_SHELL_CONTENT, EMemoShellContentClass)) + +G_BEGIN_DECLS + +typedef struct _EMemoShellContent EMemoShellContent; +typedef struct _EMemoShellContentClass EMemoShellContentClass; +typedef struct _EMemoShellContentPrivate EMemoShellContentPrivate; + +enum { + E_MEMO_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0, + E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1, + E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT = 1 << 2, + E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL = 1 << 3 +}; + +struct _EMemoShellContent { + EShellContent parent; + EMemoShellContentPrivate *priv; +}; + +struct _EMemoShellContentClass { + EShellContentClass parent_class; +}; + +GType e_memo_shell_content_get_type (void); +void e_memo_shell_content_type_register + (GTypeModule *type_module); +GtkWidget * e_memo_shell_content_new + (EShellView *shell_view); +ECalModel * e_memo_shell_content_get_memo_model + (EMemoShellContent *memo_shell_conent); +EMemoTable * e_memo_shell_content_get_memo_table + (EMemoShellContent *memo_shell_content); +EPreviewPane * e_memo_shell_content_get_preview_pane + (EMemoShellContent *memo_shell_content); +gboolean e_memo_shell_content_get_preview_visible + (EMemoShellContent *memo_shell_content); +void e_memo_shell_content_set_preview_visible + (EMemoShellContent *memo_shell_content, + gboolean preview_visible); +EShellSearchbar * + e_memo_shell_content_get_searchbar + (EMemoShellContent *memo_shell_content); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_CONTENT_H */ diff --git a/modules/calendar/e-memo-shell-migrate.c b/modules/calendar/e-memo-shell-migrate.c new file mode 100644 index 0000000000..08fe76775e --- /dev/null +++ b/modules/calendar/e-memo-shell-migrate.c @@ -0,0 +1,38 @@ +/* + * e-memo-shell-migrate.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-migrate.h" + +gboolean +e_memo_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint revision, + GError **error) +{ + g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE); + + return TRUE; +} diff --git a/modules/calendar/e-memo-shell-migrate.h b/modules/calendar/e-memo-shell-migrate.h new file mode 100644 index 0000000000..b75612539f --- /dev/null +++ b/modules/calendar/e-memo-shell-migrate.h @@ -0,0 +1,37 @@ +/* + * e-memo-shell-backend-migrate.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_BACKEND_MIGRATE_H +#define E_MEMO_SHELL_BACKEND_MIGRATE_H + +#include <shell/e-shell-backend.h> + +G_BEGIN_DECLS + +gboolean e_memo_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_BACKEND_MIGRATE_H */ diff --git a/modules/calendar/e-memo-shell-sidebar.c b/modules/calendar/e-memo-shell-sidebar.c new file mode 100644 index 0000000000..a3ae5079ed --- /dev/null +++ b/modules/calendar/e-memo-shell-sidebar.c @@ -0,0 +1,816 @@ +/* + * e-memo-shell-sidebar.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-sidebar.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "e-util/e-util.h" +#include "calendar/gui/e-memo-list-selector.h" +#include "calendar/gui/misc.h" + +#define E_MEMO_SHELL_SIDEBAR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarPrivate)) + +typedef struct _ConnectClosure ConnectClosure; + +struct _EMemoShellSidebarPrivate { + GtkWidget *selector; + + /* The default client is for ECalModel. It follows the + * sidebar's primary selection, even if the highlighted + * source is not selected. The tricky part is we don't + * update the property until the client is successfully + * opened. So the user first highlights a source, then + * sometime later we update our default-client property + * which is bound by an EBinding to ECalModel. */ + EClient *default_client; + + /* Not referenced, only for pointer comparison. */ + ESource *connecting_default_source_instance; + + EActivity *connecting_default_client; +}; + +struct _ConnectClosure { + EMemoShellSidebar *memo_shell_sidebar; + EActivity *activity; + + /* For error messages. */ + gchar *unique_display_name; +}; + +enum { + PROP_0, + PROP_DEFAULT_CLIENT, + PROP_SELECTOR +}; + +enum { + CLIENT_ADDED, + CLIENT_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_DYNAMIC_TYPE ( + EMemoShellSidebar, + e_memo_shell_sidebar, + E_TYPE_SHELL_SIDEBAR) + +static ConnectClosure * +connect_closure_new (EMemoShellSidebar *memo_shell_sidebar, + ESource *source) +{ + ConnectClosure *closure; + EAlertSink *alert_sink; + GCancellable *cancellable; + ESourceRegistry *registry; + ESourceSelector *selector; + EShellView *shell_view; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + gchar *text; + + shell_sidebar = E_SHELL_SIDEBAR (memo_shell_sidebar); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + registry = e_source_selector_get_registry (selector); + + closure = g_slice_new0 (ConnectClosure); + closure->memo_shell_sidebar = g_object_ref (memo_shell_sidebar); + closure->activity = e_activity_new (); + closure->unique_display_name = + e_source_registry_dup_unique_display_name ( + registry, source, E_SOURCE_EXTENSION_MEMO_LIST); + + text = g_strdup_printf ( + _("Opening memo list '%s'"), + closure->unique_display_name); + e_activity_set_text (closure->activity, text); + g_free (text); + + alert_sink = E_ALERT_SINK (shell_content); + e_activity_set_alert_sink (closure->activity, alert_sink); + + cancellable = g_cancellable_new (); + e_activity_set_cancellable (closure->activity, cancellable); + g_object_unref (cancellable); + + e_shell_backend_add_activity (shell_backend, closure->activity); + + return closure; +} + +static void +connect_closure_free (ConnectClosure *closure) +{ + g_clear_object (&closure->memo_shell_sidebar); + g_clear_object (&closure->activity); + + g_free (closure->unique_display_name); + + g_slice_free (ConnectClosure, closure); +} + +static gboolean +memo_shell_sidebar_map_uid_to_source (GValue *value, + GVariant *variant, + gpointer user_data) +{ + ESourceRegistry *registry; + ESource *source; + const gchar *uid; + + registry = E_SOURCE_REGISTRY (user_data); + uid = g_variant_get_string (variant, NULL); + if (uid != NULL && *uid != '\0') + source = e_source_registry_ref_source (registry, uid); + else + source = e_source_registry_ref_default_memo_list (registry); + g_value_take_object (value, source); + + return (source != NULL); +} + +static GVariant * +memo_shell_sidebar_map_source_to_uid (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariant *variant = NULL; + ESource *source; + + source = g_value_get_object (value); + + if (source != NULL) { + const gchar *uid; + + uid = e_source_get_uid (source); + variant = g_variant_new_string (uid); + } + + return variant; +} + +static void +memo_shell_sidebar_emit_client_added (EMemoShellSidebar *memo_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_ADDED]; + + g_signal_emit (memo_shell_sidebar, signal_id, 0, client); +} + +static void +memo_shell_sidebar_emit_client_removed (EMemoShellSidebar *memo_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_REMOVED]; + + g_signal_emit (memo_shell_sidebar, signal_id, 0, client); +} + +static void +memo_shell_sidebar_handle_connect_error (EActivity *activity, + const gchar *unique_display_name, + const GError *error) +{ + EAlertSink *alert_sink; + gboolean cancelled = FALSE; + gboolean offline_error; + + alert_sink = e_activity_get_alert_sink (activity); + + cancelled |= g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + cancelled |= g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED); + + offline_error = g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE); + + if (e_activity_handle_cancellation (activity, error)) { + /* do nothing */ + } else if (offline_error) { + e_alert_submit ( + alert_sink, + "calendar:prompt-no-contents-offline-memos", + unique_display_name, + NULL); + } else { + e_alert_submit ( + alert_sink, + "calendar:failed-open-memos", + unique_display_name, + error->message, + NULL); + } +} + +static void +memo_shell_sidebar_client_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ConnectClosure *closure = user_data; + GError *error = NULL; + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (error != NULL) { + memo_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + e_memo_shell_sidebar_add_client (closure->memo_shell_sidebar, client); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +memo_shell_sidebar_default_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ESource *source; + ConnectClosure *closure = user_data; + EMemoShellSidebarPrivate *priv; + GError *error = NULL; + + priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (closure->memo_shell_sidebar); + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + g_clear_object (&priv->connecting_default_client); + + if (error != NULL) { + memo_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + source = e_client_get_source (client); + + if (source == priv->connecting_default_source_instance) + priv->connecting_default_source_instance = NULL; + + if (priv->default_client != NULL) + g_object_unref (priv->default_client); + + priv->default_client = g_object_ref (client); + + g_object_notify ( + G_OBJECT (closure->memo_shell_sidebar), "default-client"); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +memo_shell_sidebar_set_default (EMemoShellSidebar *memo_shell_sidebar, + ESource *source) +{ + EMemoShellSidebarPrivate *priv; + ESourceSelector *selector; + ConnectClosure *closure; + + priv = memo_shell_sidebar->priv; + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + /* already loading that source as default source */ + if (source == priv->connecting_default_source_instance) + return; + + /* Cancel the previous request if unfinished. */ + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + closure = connect_closure_new (memo_shell_sidebar, source); + + /* it's only for pointer comparison, no need to ref it */ + priv->connecting_default_source_instance = source; + priv->connecting_default_client = g_object_ref (closure->activity); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + memo_shell_sidebar_default_connect_cb, closure); +} + +static void +memo_shell_sidebar_row_changed_cb (EMemoShellSidebar *memo_shell_sidebar, + GtkTreePath *tree_path, + GtkTreeIter *tree_iter, + GtkTreeModel *tree_model) +{ + ESourceSelector *selector; + ESource *source; + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + source = e_source_selector_ref_source_by_path (selector, tree_path); + + /* XXX This signal gets emitted a lot while the model is being + * rebuilt, during which time we won't get a valid ESource. + * ESourceSelector should probably block this signal while + * rebuilding the model, but we'll be forgiving and not + * emit a warning. */ + if (source == NULL) + return; + + if (e_source_selector_source_is_selected (selector, source)) + e_memo_shell_sidebar_add_source (memo_shell_sidebar, source); + else + e_memo_shell_sidebar_remove_source (memo_shell_sidebar, source); + + g_object_unref (source); +} + +static void +memo_shell_sidebar_primary_selection_changed_cb (EMemoShellSidebar *memo_shell_sidebar, + ESourceSelector *selector) +{ + ESource *source; + + source = e_source_selector_ref_primary_selection (selector); + if (source == NULL) + return; + + memo_shell_sidebar_set_default (memo_shell_sidebar, source); + + g_object_unref (source); +} + +static void +memo_shell_sidebar_restore_state_cb (EShellWindow *shell_window, + EShellView *shell_view, + EShellSidebar *shell_sidebar) +{ + EMemoShellSidebarPrivate *priv; + ESourceRegistry *registry; + ESourceSelector *selector; + GSettings *settings; + GtkTreeModel *model; + + priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (shell_sidebar); + + selector = E_SOURCE_SELECTOR (priv->selector); + registry = e_source_selector_get_registry (selector); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector)); + + g_signal_connect_swapped ( + registry, "source-removed", + G_CALLBACK (e_memo_shell_sidebar_remove_source), shell_sidebar); + + g_signal_connect_swapped ( + model, "row-changed", + G_CALLBACK (memo_shell_sidebar_row_changed_cb), + shell_sidebar); + + g_signal_connect_swapped ( + selector, "primary-selection-changed", + G_CALLBACK (memo_shell_sidebar_primary_selection_changed_cb), + shell_sidebar); + + /* This will trigger our "row-changed" signal handler for each + * memo list source, so the appropriate ECalClients get added to + * the ECalModel, which will then create view objects to display + * the memo list content. This all happens asynchronously. */ + e_source_selector_update_all_rows (selector); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind_with_mapping ( + settings, "primary-memos", + selector, "primary-selection", + G_SETTINGS_BIND_DEFAULT, + memo_shell_sidebar_map_uid_to_source, + memo_shell_sidebar_map_source_to_uid, + g_object_ref (registry), + (GDestroyNotify) g_object_unref); + + g_object_unref (settings); +} + +static void +memo_shell_sidebar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_DEFAULT_CLIENT: + g_value_set_object ( + value, + e_memo_shell_sidebar_get_default_client ( + E_MEMO_SHELL_SIDEBAR (object))); + return; + + case PROP_SELECTOR: + g_value_set_object ( + value, + e_memo_shell_sidebar_get_selector ( + E_MEMO_SHELL_SIDEBAR (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +memo_shell_sidebar_dispose (GObject *object) +{ + EMemoShellSidebarPrivate *priv; + + priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (object); + + if (priv->selector != NULL) { + g_object_unref (priv->selector); + priv->selector = NULL; + } + + if (priv->default_client != NULL) { + g_object_unref (priv->default_client); + priv->default_client = NULL; + } + + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_memo_shell_sidebar_parent_class)->dispose (object); +} + +static void +memo_shell_sidebar_constructed (GObject *object) +{ + EMemoShellSidebarPrivate *priv; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSidebar *shell_sidebar; + EClientCache *client_cache; + GtkContainer *container; + GtkWidget *widget; + AtkObject *a11y; + + priv = E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_memo_shell_sidebar_parent_class)->constructed (object); + + shell_sidebar = E_SHELL_SIDEBAR (object); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + container = GTK_CONTAINER (shell_sidebar); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_container_add (container, widget); + gtk_widget_show (widget); + + container = GTK_CONTAINER (widget); + + client_cache = e_shell_get_client_cache (shell); + widget = e_memo_list_selector_new (client_cache); + e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE); + gtk_container_add (container, widget); + a11y = gtk_widget_get_accessible (widget); + atk_object_set_name (a11y, _("Memo List Selector")); + priv->selector = g_object_ref (widget); + gtk_widget_show (widget); + + /* Restore widget state from the last session once + * the shell view is fully initialized and visible. */ + g_signal_connect ( + shell_window, "shell-view-created::memos", + G_CALLBACK (memo_shell_sidebar_restore_state_cb), + shell_sidebar); +} + +static guint32 +memo_shell_sidebar_check_state (EShellSidebar *shell_sidebar) +{ + EMemoShellSidebar *memo_shell_sidebar; + ESourceSelector *selector; + ESourceRegistry *registry; + ESource *source; + gboolean is_writable = FALSE; + gboolean is_removable = FALSE; + gboolean is_remote_creatable = FALSE; + gboolean is_remote_deletable = FALSE; + gboolean in_collection = FALSE; + gboolean refresh_supported = FALSE; + gboolean has_primary_source = FALSE; + guint32 state = 0; + + memo_shell_sidebar = E_MEMO_SHELL_SIDEBAR (shell_sidebar); + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + registry = e_source_selector_get_registry (selector); + + if (source != NULL) { + EClient *client; + ESource *collection; + + has_primary_source = TRUE; + is_writable = e_source_get_writable (source); + is_removable = e_source_get_removable (source); + is_remote_creatable = e_source_get_remote_creatable (source); + is_remote_deletable = e_source_get_remote_deletable (source); + + collection = e_source_registry_find_extension ( + registry, source, E_SOURCE_EXTENSION_COLLECTION); + if (collection != NULL) { + in_collection = TRUE; + g_object_unref (collection); + } + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + refresh_supported = + e_client_check_refresh_supported (client); + g_object_unref (client); + } + + g_object_unref (source); + } + + if (has_primary_source) + state |= E_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE; + if (is_writable) + state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE; + if (is_removable) + state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE; + if (is_remote_creatable) + state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE; + if (is_remote_deletable) + state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE; + if (in_collection) + state |= E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION; + if (refresh_supported) + state |= E_MEMO_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH; + + return state; +} + +static void +memo_shell_sidebar_client_removed (EMemoShellSidebar *memo_shell_sidebar, + ECalClient *client) +{ + ESourceSelector *selector; + ESource *source; + + source = e_client_get_source (E_CLIENT (client)); + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + e_source_selector_unselect_source (selector, source); +} + +static void +e_memo_shell_sidebar_class_init (EMemoShellSidebarClass *class) +{ + GObjectClass *object_class; + EShellSidebarClass *shell_sidebar_class; + + g_type_class_add_private (class, sizeof (EMemoShellSidebarPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = memo_shell_sidebar_get_property; + object_class->dispose = memo_shell_sidebar_dispose; + object_class->constructed = memo_shell_sidebar_constructed; + + shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class); + shell_sidebar_class->check_state = memo_shell_sidebar_check_state; + + class->client_removed = memo_shell_sidebar_client_removed; + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_CLIENT, + g_param_spec_object ( + "default-client", + "Default Memo ECalClient", + "Default client for memo operations", + E_TYPE_CAL_CLIENT, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_SELECTOR, + g_param_spec_object ( + "selector", + "Source Selector Widget", + "This widget displays groups of memo lists", + E_TYPE_SOURCE_SELECTOR, + G_PARAM_READABLE)); + + signals[CLIENT_ADDED] = g_signal_new ( + "client-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMemoShellSidebarClass, client_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); + + signals[CLIENT_REMOVED] = g_signal_new ( + "client-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMemoShellSidebarClass, client_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); +} + +static void +e_memo_shell_sidebar_class_finalize (EMemoShellSidebarClass *class) +{ +} + +static void +e_memo_shell_sidebar_init (EMemoShellSidebar *memo_shell_sidebar) +{ + memo_shell_sidebar->priv = + E_MEMO_SHELL_SIDEBAR_GET_PRIVATE (memo_shell_sidebar); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_memo_shell_sidebar_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_memo_shell_sidebar_register_type (type_module); +} + +GtkWidget * +e_memo_shell_sidebar_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_MEMO_SHELL_SIDEBAR, + "shell-view", shell_view, NULL); +} + +ECalClient * +e_memo_shell_sidebar_get_default_client (EMemoShellSidebar *memo_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar), NULL); + + return (ECalClient *) memo_shell_sidebar->priv->default_client; +} + +ESourceSelector * +e_memo_shell_sidebar_get_selector (EMemoShellSidebar *memo_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar), NULL); + + return E_SOURCE_SELECTOR (memo_shell_sidebar->priv->selector); +} + +void +e_memo_shell_sidebar_add_client (EMemoShellSidebar *memo_shell_sidebar, + EClient *client) +{ + ESource *source; + ESourceSelector *selector; + + g_return_if_fail (E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + source = e_client_get_source (client); + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + e_source_selector_select_source (selector, source); + + memo_shell_sidebar_emit_client_added (memo_shell_sidebar, client); +} + +void +e_memo_shell_sidebar_add_source (EMemoShellSidebar *memo_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + ConnectClosure *closure; + + g_return_if_fail (E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + e_source_selector_select_source (selector, source); + + closure = connect_closure_new (memo_shell_sidebar, source); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + memo_shell_sidebar_client_connect_cb, closure); +} + +void +e_memo_shell_sidebar_remove_source (EMemoShellSidebar *memo_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + EClient *client; + + g_return_if_fail (E_IS_MEMO_SHELL_SIDEBAR (memo_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + memo_shell_sidebar_emit_client_removed ( + memo_shell_sidebar, client); + g_object_unref (client); + } +} diff --git a/modules/calendar/e-memo-shell-sidebar.h b/modules/calendar/e-memo-shell-sidebar.h new file mode 100644 index 0000000000..c9441c7155 --- /dev/null +++ b/modules/calendar/e-memo-shell-sidebar.h @@ -0,0 +1,103 @@ +/* + * e-memo-shell-sidebar.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_SIDEBAR_H +#define E_MEMO_SHELL_SIDEBAR_H + +#include <libecal/libecal.h> + +#include <shell/e-shell-sidebar.h> +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_MEMO_SHELL_SIDEBAR \ + (e_memo_shell_sidebar_get_type ()) +#define E_MEMO_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebar)) +#define E_MEMO_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarClass)) +#define E_IS_MEMO_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_MEMO_SHELL_SIDEBAR)) +#define E_IS_MEMO_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_MEMO_SHELL_SIDEBAR)) +#define E_MEMO_SHELL_SIDEBAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_MEMO_SHELL_SIDEBAR, EMemoShellSidebarClass)) + +G_BEGIN_DECLS + +typedef struct _EMemoShellSidebar EMemoShellSidebar; +typedef struct _EMemoShellSidebarClass EMemoShellSidebarClass; +typedef struct _EMemoShellSidebarPrivate EMemoShellSidebarPrivate; + +enum { + E_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0, + E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE = 1 << 1, + E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE = 1 << 2, + E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE = 1 << 3, + E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE = 1 << 4, + E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION = 1 << 5, + E_MEMO_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH = 1 << 6 +}; + +struct _EMemoShellSidebar { + EShellSidebar parent; + EMemoShellSidebarPrivate *priv; +}; + +struct _EMemoShellSidebarClass { + EShellSidebarClass parent_class; + + /* Signals */ + void (*client_added) (EMemoShellSidebar *memo_shell_sidebar, + ECalClient *client); + void (*client_removed) + (EMemoShellSidebar *memo_shell_sidebar, + ECalClient *client); +}; + +GType e_memo_shell_sidebar_get_type (void); +void e_memo_shell_sidebar_type_register + (GTypeModule *type_module); +GtkWidget * e_memo_shell_sidebar_new + (EShellView *shell_view); +ECalClient * e_memo_shell_sidebar_get_default_client + (EMemoShellSidebar *memo_shell_sidebar); +ESourceSelector * + e_memo_shell_sidebar_get_selector + (EMemoShellSidebar *memo_shell_sidebar); +void e_memo_shell_sidebar_add_client + (EMemoShellSidebar *memo_shell_sidebar, + EClient *client); +void e_memo_shell_sidebar_add_source + (EMemoShellSidebar *memo_shell_sidebar, + ESource *source); +void e_memo_shell_sidebar_remove_source + (EMemoShellSidebar *memo_shell_sidebar, + ESource *source); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_SIDEBAR_H */ diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c new file mode 100644 index 0000000000..29dd68df9c --- /dev/null +++ b/modules/calendar/e-memo-shell-view-actions.c @@ -0,0 +1,1049 @@ +/* + * e-memo-shell-view-actions.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-view-private.h" + +static void +action_memo_delete_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + e_selectable_delete_selection (E_SELECTABLE (memo_table)); +} + +static void +action_memo_find_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EPreviewPane *preview_pane; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + + e_preview_pane_show_search_bar (preview_pane); +} + +static void +action_memo_forward_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + ECalComponent *comp; + icalcomponent *clone; + GSList *list; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only forward the first selected memo. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_PUBLISH, comp, + comp_data->client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (comp); +} + +static void +action_memo_list_copy_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellSidebar *memo_shell_sidebar; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ESourceSelector *selector; + ESource *source; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + copy_source_dialog ( + GTK_WINDOW (shell_window), registry, + source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS); + + g_object_unref (source); +} + +static void +action_memo_list_delete_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellSidebar *memo_shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + ESource *source; + ESourceSelector *selector; + gint response; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + if (e_source_get_remote_deletable (source)) { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-remote-memo-list", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remote_delete_source (shell_view, source); + + } else { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-memo-list", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remove_source (shell_view, source); + } + + g_object_unref (source); +} + +static void +action_memo_list_new_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Memo List")); + + gtk_widget_show (dialog); +} + +static void +action_memo_list_print_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + print_table ( + E_TABLE (memo_table), _("Print Memos"), _("Memos"), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); +} + +static void +action_memo_list_print_preview_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + print_table ( + E_TABLE (memo_table), _("Print Memos"), _("Memos"), + GTK_PRINT_OPERATION_ACTION_PREVIEW); +} + +static void +action_memo_list_properties_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + EMemoShellSidebar *memo_shell_sidebar; + ECalClientSourceType source_type; + ESource *source; + ESourceSelector *selector; + ESourceRegistry *registry; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; + registry = e_source_selector_get_registry (selector); + config = e_cal_source_config_new (registry, source, source_type); + + g_object_unref (source); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Memo List Properties")); + + gtk_widget_show (dialog); +} + +static void +action_memo_list_refresh_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellSidebar *memo_shell_sidebar; + ESourceSelector *selector; + EClient *client = NULL; + ESource *source; + GError *error = NULL; + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + + if (source != NULL) { + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + g_object_unref (source); + } + + if (client == NULL) + return; + + g_return_if_fail (e_client_check_refresh_supported (client)); + + e_client_refresh_sync (client, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to refresh '%s', %s", + G_STRFUNC, e_source_get_display_name (source), + error->message); + g_error_free (error); + } + + g_object_unref (client); +} + +static void +action_memo_list_rename_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellSidebar *memo_shell_sidebar; + ESourceSelector *selector; + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + e_source_selector_edit_primary_selection (selector); +} + +static void +action_memo_list_select_one_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellSidebar *memo_shell_sidebar; + ESourceSelector *selector; + ESource *primary; + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + selector = e_memo_shell_sidebar_get_selector (memo_shell_sidebar); + + primary = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (primary != NULL); + + e_source_selector_select_exclusive (selector, primary); + + g_object_unref (primary); +} + +static void +action_memo_new_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalClient *client; + ECalComponent *comp; + CompEditor *editor; + GSList *list; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + list = e_memo_table_get_selected (memo_table); + if (list == NULL) { + ECalModel *model; + + model = e_memo_table_get_model (memo_table); + client = e_cal_model_ref_default_client (model); + } else { + ECalModelComponent *comp_data; + + comp_data = list->data; + client = g_object_ref (comp_data->client); + g_slist_free (list); + } + + g_return_if_fail (client != NULL); + + comp = cal_comp_memo_new_with_defaults (client); + cal_comp_update_time_by_active_window (comp, shell); + editor = memo_editor_new (client, shell, COMP_EDITOR_NEW_ITEM); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); + + g_object_unref (client); +} + +static void +action_memo_open_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + GSList *list; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected memo. */ + e_memo_shell_view_open_memo (memo_shell_view, comp_data); +} + +static void +action_memo_open_url_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + icalproperty *prop; + const gchar *uri; + GSList *list; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the URI of the first selected memo. */ + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + g_return_if_fail (prop != NULL); + + uri = icalproperty_get_url (prop); + e_show_uri (GTK_WINDOW (shell_window), uri); +} + +static void +action_memo_preview_cb (GtkToggleAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + gboolean visible; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + visible = gtk_toggle_action_get_active (action); + e_memo_shell_content_set_preview_visible (memo_shell_content, visible); +} + +static void +action_memo_print_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + ECalComponent *comp; + ECalModel *model; + icalcomponent *clone; + GSList *list; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + model = e_memo_table_get_model (memo_table); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only print the first selected memo. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + print_comp ( + comp, comp_data->client, + e_cal_model_get_timezone (model), + e_cal_model_get_use_24_hour_format (model), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); + + g_object_unref (comp); +} + +static void +action_memo_save_as_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModelComponent *comp_data; + EActivity *activity; + GSList *list; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + list = e_memo_table_get_selected (memo_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* Translators: Default filename part saving a memo to a file when + * no summary is filed, the '.ics' extension is concatenated to it. */ + string = icalcomp_suggest_filename (comp_data->icalcomp, _("memo")); + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), string, + "*.ics:text/calendar", NULL, NULL); + g_free (string); + if (file == NULL) + return; + + /* XXX We only save the first selected memo. */ + string = e_cal_client_get_component_as_string ( + comp_data->client, comp_data->icalcomp); + if (string == NULL) { + g_warning ("Could not convert memo to a string"); + g_object_unref (file); + return; + } + + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); +} + +static void +action_memo_view_cb (GtkRadioAction *action, + GtkRadioAction *current, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + GtkOrientable *orientable; + GtkOrientation orientation; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + orientable = GTK_ORIENTABLE (memo_shell_content); + + switch (gtk_radio_action_get_current_value (action)) { + case 0: + orientation = GTK_ORIENTATION_VERTICAL; + break; + case 1: + orientation = GTK_ORIENTATION_HORIZONTAL; + break; + default: + g_return_if_reached (); + } + + gtk_orientable_set_orientation (orientable, orientation); +} + +static GtkActionEntry memo_entries[] = { + + { "memo-delete", + GTK_STOCK_DELETE, + N_("_Delete Memo"), + NULL, + N_("Delete selected memos"), + G_CALLBACK (action_memo_delete_cb) }, + + { "memo-find", + GTK_STOCK_FIND, + N_("_Find in Memo..."), + "<Shift><Control>f", + N_("Search for text in the displayed memo"), + G_CALLBACK (action_memo_find_cb) }, + + { "memo-forward", + "mail-forward", + N_("_Forward as iCalendar..."), + "<Control>f", + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_forward_cb) }, + + { "memo-list-copy", + GTK_STOCK_COPY, + N_("_Copy..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_list_copy_cb) }, + + { "memo-list-delete", + GTK_STOCK_DELETE, + N_("D_elete Memo List"), + NULL, + N_("Delete the selected memo list"), + G_CALLBACK (action_memo_list_delete_cb) }, + + { "memo-list-new", + "stock_notes", + N_("_New Memo List"), + NULL, + N_("Create a new memo list"), + G_CALLBACK (action_memo_list_new_cb) }, + + { "memo-list-properties", + GTK_STOCK_PROPERTIES, + NULL, + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_list_properties_cb) }, + + { "memo-list-refresh", + GTK_STOCK_REFRESH, + N_("Re_fresh"), + NULL, + N_("Refresh the selected memo list"), + G_CALLBACK (action_memo_list_refresh_cb) }, + + { "memo-list-rename", + NULL, + N_("_Rename..."), + "F2", + N_("Rename the selected memo list"), + G_CALLBACK (action_memo_list_rename_cb) }, + + { "memo-list-select-one", + "stock_check-filled", + N_("Show _Only This Memo List"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_list_select_one_cb) }, + + { "memo-new", + "stock_insert-note", + N_("New _Memo"), + NULL, + N_("Create a new memo"), + G_CALLBACK (action_memo_new_cb) }, + + { "memo-open", + GTK_STOCK_OPEN, + N_("_Open Memo"), + "<Control>o", + N_("View the selected memo"), + G_CALLBACK (action_memo_open_cb) }, + + { "memo-open-url", + "applications-internet", + N_("Open _Web Page"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_open_url_cb) }, + + /*** Menus ***/ + + { "memo-preview-menu", + NULL, + N_("_Preview"), + NULL, + NULL, + NULL } +}; + +static EPopupActionEntry memo_popup_entries[] = { + + { "memo-list-popup-copy", + NULL, + "memo-list-copy" }, + + { "memo-list-popup-delete", + N_("_Delete"), + "memo-list-delete" }, + + { "memo-list-popup-properties", + NULL, + "memo-list-properties" }, + + { "memo-list-popup-refresh", + NULL, + "memo-list-refresh" }, + + { "memo-list-popup-rename", + NULL, + "memo-list-rename" }, + + { "memo-list-popup-select-one", + NULL, + "memo-list-select-one" }, + + { "memo-popup-forward", + NULL, + "memo-forward" }, + + { "memo-popup-open", + NULL, + "memo-open" }, + + { "memo-popup-open-url", + NULL, + "memo-open-url" } +}; + +static GtkToggleActionEntry memo_toggle_entries[] = { + + { "memo-preview", + NULL, + N_("Memo _Preview"), + "<Control>m", + N_("Show memo preview pane"), + G_CALLBACK (action_memo_preview_cb), + TRUE } +}; + +static GtkRadioActionEntry memo_view_entries[] = { + + /* This action represents the initial active memo view. + * It should not be visible in the UI, nor should it be + * possible to switch to it from another shell view. */ + { "memo-view-initial", + NULL, + NULL, + NULL, + NULL, + -1 }, + + { "memo-view-classic", + NULL, + N_("_Classic View"), + NULL, + N_("Show memo preview below the memo list"), + 0 }, + + { "memo-view-vertical", + NULL, + N_("_Vertical View"), + NULL, + N_("Show memo preview alongside the memo list"), + 1 } +}; + +static GtkRadioActionEntry memo_filter_entries[] = { + + { "memo-filter-any-category", + NULL, + N_("Any Category"), + NULL, + NULL, + MEMO_FILTER_ANY_CATEGORY }, + + { "memo-filter-unmatched", + NULL, + N_("Unmatched"), + NULL, + NULL, + MEMO_FILTER_UNMATCHED } +}; + +static GtkRadioActionEntry memo_search_entries[] = { + + { "memo-search-advanced-hidden", + NULL, + N_("Advanced Search"), + NULL, + NULL, + MEMO_SEARCH_ADVANCED }, + + { "memo-search-any-field-contains", + NULL, + N_("Any field contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + MEMO_SEARCH_ANY_FIELD_CONTAINS }, + + { "memo-search-description-contains", + NULL, + N_("Description contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + MEMO_SEARCH_DESCRIPTION_CONTAINS }, + + { "memo-search-summary-contains", + NULL, + N_("Summary contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + MEMO_SEARCH_SUMMARY_CONTAINS } +}; + +static GtkActionEntry lockdown_printing_entries[] = { + + { "memo-list-print", + GTK_STOCK_PRINT, + NULL, + "<Control>p", + N_("Print the list of memos"), + G_CALLBACK (action_memo_list_print_cb) }, + + { "memo-list-print-preview", + GTK_STOCK_PRINT_PREVIEW, + NULL, + NULL, + N_("Preview the list of memos to be printed"), + G_CALLBACK (action_memo_list_print_preview_cb) }, + + { "memo-print", + GTK_STOCK_PRINT, + NULL, + NULL, + N_("Print the selected memo"), + G_CALLBACK (action_memo_print_cb) } +}; + +static EPopupActionEntry lockdown_printing_popup_entries[] = { + + { "memo-popup-print", + NULL, + "memo-print" } +}; + +static GtkActionEntry lockdown_save_to_disk_entries[] = { + + { "memo-save-as", + GTK_STOCK_SAVE_AS, + N_("_Save as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_memo_save_as_cb) }, +}; + +static EPopupActionEntry lockdown_save_to_disk_popup_entries[] = { + + { "memo-popup-save-as", + NULL, + "memo-save-as" } +}; + +void +e_memo_shell_view_actions_init (EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + EPreviewPane *preview_pane; + EWebView *web_view; + GtkActionGroup *action_group; + GSettings *memo_settings; + GtkAction *action; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + searchbar = e_memo_shell_content_get_searchbar (memo_shell_content); + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); + + /* Memo Actions */ + action_group = ACTION_GROUP (MEMOS); + gtk_action_group_add_actions ( + action_group, memo_entries, + G_N_ELEMENTS (memo_entries), memo_shell_view); + e_action_group_add_popup_actions ( + action_group, memo_popup_entries, + G_N_ELEMENTS (memo_popup_entries)); + gtk_action_group_add_toggle_actions ( + action_group, memo_toggle_entries, + G_N_ELEMENTS (memo_toggle_entries), memo_shell_view); + gtk_action_group_add_radio_actions ( + action_group, memo_view_entries, + G_N_ELEMENTS (memo_view_entries), -1, + G_CALLBACK (action_memo_view_cb), memo_shell_view); + gtk_action_group_add_radio_actions ( + action_group, memo_search_entries, + G_N_ELEMENTS (memo_search_entries), + -1, NULL, NULL); + + /* Advanced Search Action */ + action = ACTION (MEMO_SEARCH_ADVANCED_HIDDEN); + gtk_action_set_visible (action, FALSE); + e_shell_searchbar_set_search_option ( + searchbar, GTK_RADIO_ACTION (action)); + + /* Lockdown Printing Actions */ + action_group = ACTION_GROUP (LOCKDOWN_PRINTING); + gtk_action_group_add_actions ( + action_group, lockdown_printing_entries, + G_N_ELEMENTS (lockdown_printing_entries), + memo_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_printing_popup_entries, + G_N_ELEMENTS (lockdown_printing_popup_entries)); + + /* Lockdown Save-to-Disk Actions */ + action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK); + gtk_action_group_add_actions ( + action_group, lockdown_save_to_disk_entries, + G_N_ELEMENTS (lockdown_save_to_disk_entries), + memo_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_save_to_disk_popup_entries, + G_N_ELEMENTS (lockdown_save_to_disk_popup_entries)); + + /* Bind GObject properties to settings keys. */ + + memo_settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + memo_settings, "show-memo-preview", + ACTION (MEMO_PREVIEW), "active", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind ( + memo_settings, "memo-layout", + ACTION (MEMO_VIEW_VERTICAL), "current-value", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (memo_settings); + + /* Fine tuning. */ + + g_object_bind_property ( + ACTION (MEMO_PREVIEW), "active", + ACTION (MEMO_VIEW_CLASSIC), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + ACTION (MEMO_PREVIEW), "active", + ACTION (MEMO_VIEW_VERTICAL), "sensitive", + G_BINDING_SYNC_CREATE); + + e_web_view_set_open_proxy (web_view, ACTION (MEMO_OPEN)); + e_web_view_set_print_proxy (web_view, ACTION (MEMO_PRINT)); + e_web_view_set_save_as_proxy (web_view, ACTION (MEMO_SAVE_AS)); +} + +void +e_memo_shell_view_update_search_filter (EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GtkActionGroup *action_group; + GtkRadioAction *radio_action; + GList *list, *iter; + GSList *group; + gint ii; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + action_group = ACTION_GROUP (MEMOS_FILTER); + e_action_group_remove_all_actions (action_group); + + /* Add the standard filter actions. No callback is needed + * because changes in the EActionComboBox are detected and + * handled by EShellSearchbar. */ + gtk_action_group_add_radio_actions ( + action_group, memo_filter_entries, + G_N_ELEMENTS (memo_filter_entries), + MEMO_FILTER_ANY_CATEGORY, NULL, NULL); + + /* Retrieve the radio group from an action we just added. */ + list = gtk_action_group_list_actions (action_group); + radio_action = GTK_RADIO_ACTION (list->data); + group = gtk_radio_action_get_group (radio_action); + g_list_free (list); + + /* Build the category actions. */ + + list = e_util_get_searchable_categories (); + for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) { + const gchar *category_name = iter->data; + const gchar *filename; + GtkAction *action; + gchar *action_name; + + action_name = g_strdup_printf ( + "memo-filter-category-%d", ii); + radio_action = gtk_radio_action_new ( + action_name, category_name, NULL, NULL, ii); + g_free (action_name); + + /* Convert the category icon file to a themed icon name. */ + filename = e_categories_get_icon_file_for (category_name); + if (filename != NULL && *filename != '\0') { + gchar *basename; + gchar *cp; + + basename = g_path_get_basename (filename); + + /* Lose the file extension. */ + if ((cp = strrchr (basename, '.')) != NULL) + *cp = '\0'; + + g_object_set ( + radio_action, "icon-name", basename, NULL); + + g_free (basename); + } + + gtk_radio_action_set_group (radio_action, group); + group = gtk_radio_action_get_group (radio_action); + + /* The action group takes ownership of the action. */ + action = GTK_ACTION (radio_action); + gtk_action_group_add_action (action_group, action); + g_object_unref (radio_action); + } + g_list_free (list); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + searchbar = e_memo_shell_content_get_searchbar (memo_shell_content); + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + + e_shell_view_block_execute_search (shell_view); + + /* Use any action in the group; doesn't matter which. */ + e_action_combo_box_set_action (combo_box, radio_action); + + ii = MEMO_FILTER_UNMATCHED; + e_action_combo_box_add_separator_after (combo_box, ii); + + e_shell_view_unblock_execute_search (shell_view); +} diff --git a/modules/calendar/e-memo-shell-view-actions.h b/modules/calendar/e-memo-shell-view-actions.h new file mode 100644 index 0000000000..c9d144b5d4 --- /dev/null +++ b/modules/calendar/e-memo-shell-view-actions.h @@ -0,0 +1,91 @@ +/* + * e-memo-shell-view-actions.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_VIEW_ACTIONS_H +#define E_MEMO_SHELL_VIEW_ACTIONS_H + +#include <shell/e-shell-window-actions.h> + +/* Memo Actions */ +#define E_SHELL_WINDOW_ACTION_MEMO_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-delete") +#define E_SHELL_WINDOW_ACTION_MEMO_FIND(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-find") +#define E_SHELL_WINDOW_ACTION_MEMO_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-forward") +#define E_SHELL_WINDOW_ACTION_MEMO_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-new") +#define E_SHELL_WINDOW_ACTION_MEMO_OPEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-open") +#define E_SHELL_WINDOW_ACTION_MEMO_OPEN_URL(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-open-url") +#define E_SHELL_WINDOW_ACTION_MEMO_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-preview") +#define E_SHELL_WINDOW_ACTION_MEMO_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-print") +#define E_SHELL_WINDOW_ACTION_MEMO_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-save-as") +#define E_SHELL_WINDOW_ACTION_MEMO_VIEW_CLASSIC(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-view-classic") +#define E_SHELL_WINDOW_ACTION_MEMO_VIEW_VERTICAL(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-view-vertical") + +/* Memo List Actions */ +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_COPY(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-copy") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-delete") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-new") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-print") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PRINT_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-print-preview") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_PROPERTIES(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-properties") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_REFRESH(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-refresh") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_RENAME(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-rename") +#define E_SHELL_WINDOW_ACTION_MEMO_LIST_SELECT_ONE(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-list-select-one") + +/* Memo Query Actions */ +#define E_SHELL_WINDOW_ACTION_MEMO_FILTER_ANY_CATEGORY(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-filter-any-category") +#define E_SHELL_WINDOW_ACTION_MEMO_FILTER_UNMATCHED(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-filter-unmatched") +#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_ADVANCED_HIDDEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-search-advanced-hidden") +#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_ANY_FIELD_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-search-any-field-contains") +#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_DESCRIPTION_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-search-description-contains") +#define E_SHELL_WINDOW_ACTION_MEMO_SEARCH_SUMMARY_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-search-summary-contains") + +/* Action Groups */ +#define E_SHELL_WINDOW_ACTION_GROUP_MEMOS(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "memos") +#define E_SHELL_WINDOW_ACTION_GROUP_MEMOS_FILTER(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "memos-filter") + +#endif /* E_MEMO_SHELL_VIEW_ACTIONS_H */ diff --git a/modules/calendar/e-memo-shell-view-private.c b/modules/calendar/e-memo-shell-view-private.c new file mode 100644 index 0000000000..df5182731d --- /dev/null +++ b/modules/calendar/e-memo-shell-view-private.c @@ -0,0 +1,551 @@ +/* + * e-memo-shell-view-private.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-util/e-util-private.h" + +#include "e-memo-shell-view-private.h" + +static void +memo_shell_view_model_row_appended_cb (EMemoShellView *memo_shell_view, + ECalModel *model) +{ + EMemoShellSidebar *memo_shell_sidebar; + ECalClient *client; + + /* This is the "Click to Add" handler. */ + + client = e_cal_model_ref_default_client (model); + + memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; + e_memo_shell_sidebar_add_client (memo_shell_sidebar, E_CLIENT (client)); + + g_object_unref (client); +} + +static void +memo_shell_view_table_popup_event_cb (EShellView *shell_view, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/memo-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); +} + +static void +memo_shell_view_selector_client_added_cb (EMemoShellView *memo_shell_view, + ECalClient *client) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModel *model; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + model = e_memo_table_get_model (memo_table); + + e_cal_model_add_client (model, client); +} + +static void +memo_shell_view_selector_client_removed_cb (EMemoShellView *memo_shell_view, + ECalClient *client) +{ + EMemoShellContent *memo_shell_content; + EMemoTable *memo_table; + ECalModel *model; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + model = e_memo_table_get_model (memo_table); + + e_cal_model_remove_client (model, client); +} + +static gboolean +memo_shell_view_selector_popup_event_cb (EShellView *shell_view, + ESource *primary_source, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/memo-list-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); + + return TRUE; +} + +static void +memo_shell_view_backend_error_cb (EClientCache *client_cache, + EClient *client, + EAlert *alert, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + ESource *source; + const gchar *extension_name; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + + source = e_client_get_source (client); + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + + /* Only submit alerts from memo list backends. */ + if (e_source_has_extension (source, extension_name)) { + EAlertSink *alert_sink; + + alert_sink = E_ALERT_SINK (memo_shell_content); + e_alert_sink_submit_alert (alert_sink, alert); + } +} + +static void +memo_shell_view_notify_view_id_cb (EShellView *shell_view) +{ + GalViewInstance *view_instance; + const gchar *view_id; + + view_id = e_shell_view_get_view_id (shell_view); + view_instance = e_shell_view_get_view_instance (shell_view); + + /* A NULL view ID implies we're in a custom view. But you can + * only get to a custom view via the "Define Views" dialog, which + * would have already modified the view instance appropriately. + * Furthermore, there's no way to refer to a custom view by ID + * anyway, since custom views have no IDs. */ + if (view_id == NULL) + return; + + gal_view_instance_set_current_view_id (view_instance, view_id); +} + +void +e_memo_shell_view_private_init (EMemoShellView *memo_shell_view) +{ + g_signal_connect ( + memo_shell_view, "notify::view-id", + G_CALLBACK (memo_shell_view_notify_view_id_cb), NULL); +} + +void +e_memo_shell_view_private_constructed (EMemoShellView *memo_shell_view) +{ + EMemoShellViewPrivate *priv = memo_shell_view->priv; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + EShell *shell; + gulong handler_id; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + e_shell_window_add_action_group (shell_window, "memos"); + e_shell_window_add_action_group (shell_window, "memos-filter"); + + /* Cache these to avoid lots of awkward casting. */ + priv->memo_shell_backend = g_object_ref (shell_backend); + priv->memo_shell_content = g_object_ref (shell_content); + priv->memo_shell_sidebar = g_object_ref (shell_sidebar); + + handler_id = g_signal_connect_swapped ( + priv->memo_shell_sidebar, "client-added", + G_CALLBACK (memo_shell_view_selector_client_added_cb), + memo_shell_view); + priv->client_added_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_shell_sidebar, "client-removed", + G_CALLBACK (memo_shell_view_selector_client_removed_cb), + memo_shell_view); + priv->client_removed_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->client_cache = e_shell_get_client_cache (shell); + g_object_ref (priv->client_cache); + + handler_id = g_signal_connect ( + priv->client_cache, "backend-error", + G_CALLBACK (memo_shell_view_backend_error_cb), + memo_shell_view); + priv->backend_error_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->memo_table = e_memo_shell_content_get_memo_table ( + E_MEMO_SHELL_CONTENT (shell_content)); + g_object_ref (priv->memo_table); + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "open-component", + G_CALLBACK (e_memo_shell_view_open_memo), + memo_shell_view); + priv->open_component_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "popup-event", + G_CALLBACK (memo_shell_view_table_popup_event_cb), + memo_shell_view); + priv->popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "selection-change", + G_CALLBACK (e_memo_shell_view_update_sidebar), + memo_shell_view); + priv->selection_change_1_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "selection-change", + G_CALLBACK (e_shell_view_update_actions), + memo_shell_view); + priv->selection_change_2_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->memo_table, "status-message", + G_CALLBACK (e_memo_shell_view_set_status_message), + memo_shell_view); + priv->status_message_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->model = e_memo_table_get_model (priv->memo_table); + g_object_ref (priv->model); + + handler_id = g_signal_connect_swapped ( + priv->model, "model-changed", + G_CALLBACK (e_memo_shell_view_update_sidebar), + memo_shell_view); + priv->model_changed_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "model-rows-deleted", + G_CALLBACK (e_memo_shell_view_update_sidebar), + memo_shell_view); + priv->model_rows_deleted_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "model-rows-inserted", + G_CALLBACK (e_memo_shell_view_update_sidebar), + memo_shell_view); + priv->model_rows_inserted_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "row-appended", + G_CALLBACK (memo_shell_view_model_row_appended_cb), + memo_shell_view); + priv->row_appended_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->selector = e_memo_shell_sidebar_get_selector ( + E_MEMO_SHELL_SIDEBAR (shell_sidebar)); + g_object_ref (priv->selector); + + handler_id = g_signal_connect_swapped ( + priv->selector, "popup-event", + G_CALLBACK (memo_shell_view_selector_popup_event_cb), + memo_shell_view); + priv->selector_popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->selector, "primary-selection-changed", + G_CALLBACK (e_shell_view_update_actions), + memo_shell_view); + priv->primary_selection_changed_handler_id = handler_id; + + e_categories_add_change_hook ( + (GHookFunc) e_memo_shell_view_update_search_filter, + memo_shell_view); + + /* Keep the ECalModel in sync with the sidebar. */ + g_object_bind_property ( + shell_sidebar, "default-client", + priv->model, "default-client", + G_BINDING_SYNC_CREATE); + + e_memo_shell_view_actions_init (memo_shell_view); + e_memo_shell_view_update_sidebar (memo_shell_view); + e_memo_shell_view_update_search_filter (memo_shell_view); +} + +void +e_memo_shell_view_private_dispose (EMemoShellView *memo_shell_view) +{ + EMemoShellViewPrivate *priv = memo_shell_view->priv; + + if (priv->client_added_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_shell_sidebar, + priv->client_added_handler_id); + priv->client_added_handler_id = 0; + } + + if (priv->client_removed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_shell_sidebar, + priv->client_removed_handler_id); + priv->client_removed_handler_id = 0; + } + + if (priv->backend_error_handler_id > 0) { + g_signal_handler_disconnect ( + priv->client_cache, + priv->backend_error_handler_id); + priv->backend_error_handler_id = 0; + } + + if (priv->open_component_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->open_component_handler_id); + priv->open_component_handler_id = 0; + } + + if (priv->popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->popup_event_handler_id); + priv->popup_event_handler_id = 0; + } + + if (priv->selection_change_1_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->selection_change_1_handler_id); + priv->selection_change_1_handler_id = 0; + } + + if (priv->selection_change_2_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->selection_change_2_handler_id); + priv->selection_change_2_handler_id = 0; + } + + if (priv->status_message_handler_id > 0) { + g_signal_handler_disconnect ( + priv->memo_table, + priv->status_message_handler_id); + priv->status_message_handler_id = 0; + } + + if (priv->model_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_changed_handler_id); + priv->model_changed_handler_id = 0; + } + + if (priv->model_rows_deleted_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_rows_deleted_handler_id); + priv->model_rows_deleted_handler_id = 0; + } + + if (priv->model_rows_inserted_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_rows_inserted_handler_id); + priv->model_rows_inserted_handler_id = 0; + } + + if (priv->row_appended_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->row_appended_handler_id); + priv->row_appended_handler_id = 0; + } + + if (priv->selector_popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->selector, + priv->selector_popup_event_handler_id); + priv->selector_popup_event_handler_id = 0; + } + + if (priv->primary_selection_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->selector, + priv->primary_selection_changed_handler_id); + priv->primary_selection_changed_handler_id = 0; + } + + g_clear_object (&priv->memo_shell_backend); + g_clear_object (&priv->memo_shell_content); + g_clear_object (&priv->memo_shell_sidebar); + + g_clear_object (&priv->client_cache); + g_clear_object (&priv->memo_table); + g_clear_object (&priv->model); + g_clear_object (&priv->selector); + + if (memo_shell_view->priv->activity != NULL) { + /* XXX Activity is not cancellable. */ + e_activity_set_state ( + memo_shell_view->priv->activity, + E_ACTIVITY_COMPLETED); + g_object_unref (memo_shell_view->priv->activity); + memo_shell_view->priv->activity = NULL; + } +} + +void +e_memo_shell_view_private_finalize (EMemoShellView *memo_shell_view) +{ + /* XXX Nothing to do? */ +} + +void +e_memo_shell_view_open_memo (EMemoShellView *memo_shell_view, + ECalModelComponent *comp_data) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + CompEditor *editor; + CompEditorFlags flags = 0; + ECalComponent *comp; + icalcomponent *clone; + const gchar *uid; + + g_return_if_fail (E_IS_MEMO_SHELL_VIEW (memo_shell_view)); + g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data)); + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + uid = icalcomponent_get_uid (comp_data->icalcomp); + editor = comp_editor_find_instance (uid); + + if (editor != NULL) + goto exit; + + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + if (e_cal_component_has_organizer (comp)) + flags |= COMP_EDITOR_IS_SHARED; + + if (itip_organizer_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + editor = memo_editor_new (comp_data->client, shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +exit: + gtk_window_present (GTK_WINDOW (editor)); +} + +void +e_memo_shell_view_set_status_message (EMemoShellView *memo_shell_view, + const gchar *status_message, + gdouble percent) +{ + EActivity *activity; + EShellView *shell_view; + EShellBackend *shell_backend; + + g_return_if_fail (E_IS_MEMO_SHELL_VIEW (memo_shell_view)); + + activity = memo_shell_view->priv->activity; + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + + if (status_message == NULL || *status_message == '\0') { + if (activity != NULL) { + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_object_unref (activity); + activity = NULL; + } + + } else if (activity == NULL) { + activity = e_activity_new (); + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + e_shell_backend_add_activity (shell_backend, activity); + + } else { + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + } + + memo_shell_view->priv->activity = activity; +} + +void +e_memo_shell_view_update_sidebar (EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EShellView *shell_view; + EShellSidebar *shell_sidebar; + EMemoTable *memo_table; + ECalModel *model; + GString *string; + const gchar *format; + gint n_rows; + gint n_selected; + + shell_view = E_SHELL_VIEW (memo_shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + + model = e_memo_table_get_model (memo_table); + + n_rows = e_table_model_row_count (E_TABLE_MODEL (model)); + n_selected = e_table_selected_count (E_TABLE (memo_table)); + + string = g_string_sized_new (64); + + format = ngettext ("%d memo", "%d memos", n_rows); + g_string_append_printf (string, format, n_rows); + + if (n_selected > 0) { + format = _("%d selected"); + g_string_append_len (string, ", ", 2); + g_string_append_printf (string, format, n_selected); + } + + e_shell_sidebar_set_secondary_text (shell_sidebar, string->str); + + g_string_free (string, TRUE); +} + diff --git a/modules/calendar/e-memo-shell-view-private.h b/modules/calendar/e-memo-shell-view-private.h new file mode 100644 index 0000000000..aa04e60a11 --- /dev/null +++ b/modules/calendar/e-memo-shell-view-private.h @@ -0,0 +1,135 @@ +/* + * e-memo-shell-view-private.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_VIEW_PRIVATE_H +#define E_MEMO_SHELL_VIEW_PRIVATE_H + +#include "e-memo-shell-view.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "shell/e-shell-utils.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" +#include "calendar/gui/e-calendar-selector.h" +#include "calendar/gui/print.h" +#include "calendar/gui/dialogs/copy-source-dialog.h" +#include "calendar/gui/dialogs/memo-editor.h" + +#include "e-memo-shell-backend.h" +#include "e-memo-shell-content.h" +#include "e-memo-shell-sidebar.h" +#include "e-memo-shell-view-actions.h" + +#define E_MEMO_SHELL_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewPrivate)) + +/* Shorthand, requires a variable named "shell_window". */ +#define ACTION(name) \ + (E_SHELL_WINDOW_ACTION_##name (shell_window)) +#define ACTION_GROUP(name) \ + (E_SHELL_WINDOW_ACTION_GROUP_##name (shell_window)) + +/* ETable Specifications */ +#define ETSPEC_FILENAME "e-memo-table.etspec" + +G_BEGIN_DECLS + +/* Filter items are displayed in ascending order. + * Non-negative values are reserved for categories. */ +enum { + MEMO_FILTER_ANY_CATEGORY = -2, + MEMO_FILTER_UNMATCHED = -1 +}; + +/* Search items are displayed in ascending order. */ +enum { + MEMO_SEARCH_ADVANCED = -1, + MEMO_SEARCH_SUMMARY_CONTAINS, + MEMO_SEARCH_DESCRIPTION_CONTAINS, + MEMO_SEARCH_ANY_FIELD_CONTAINS +}; + +struct _EMemoShellViewPrivate { + + /* These are just for convenience. */ + EMemoShellBackend *memo_shell_backend; + EMemoShellContent *memo_shell_content; + EMemoShellSidebar *memo_shell_sidebar; + + /* sidebar signal handlers */ + gulong client_added_handler_id; + gulong client_removed_handler_id; + + EClientCache *client_cache; + gulong backend_error_handler_id; + + EMemoTable *memo_table; + gulong open_component_handler_id; + gulong popup_event_handler_id; + gulong selection_change_1_handler_id; + gulong selection_change_2_handler_id; + gulong status_message_handler_id; + + ECalModel *model; + gulong model_changed_handler_id; + gulong model_rows_deleted_handler_id; + gulong model_rows_inserted_handler_id; + gulong row_appended_handler_id; + + ESourceSelector *selector; + gulong selector_popup_event_handler_id; + gulong primary_selection_changed_handler_id; + + EActivity *activity; +}; + +void e_memo_shell_view_private_init + (EMemoShellView *memo_shell_view); +void e_memo_shell_view_private_constructed + (EMemoShellView *memo_shell_view); +void e_memo_shell_view_private_dispose + (EMemoShellView *memo_shell_view); +void e_memo_shell_view_private_finalize + (EMemoShellView *memo_shell_view); + +/* Private Utilities */ + +void e_memo_shell_view_actions_init + (EMemoShellView *memo_shell_view); +void e_memo_shell_view_open_memo + (EMemoShellView *memo_shell_view, + ECalModelComponent *comp_data); +void e_memo_shell_view_set_status_message + (EMemoShellView *memo_shell_view, + const gchar *status_message, + gdouble percent); +void e_memo_shell_view_update_sidebar + (EMemoShellView *memo_shell_view); +void e_memo_shell_view_update_search_filter + (EMemoShellView *memo_shell_view); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_VIEW_PRIVATE_H */ diff --git a/modules/calendar/e-memo-shell-view.c b/modules/calendar/e-memo-shell-view.c new file mode 100644 index 0000000000..5e4e48fe30 --- /dev/null +++ b/modules/calendar/e-memo-shell-view.c @@ -0,0 +1,344 @@ +/* + * e-memo-shell-view.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-memo-shell-view-private.h" + +G_DEFINE_DYNAMIC_TYPE ( + EMemoShellView, + e_memo_shell_view, + E_TYPE_SHELL_VIEW) + +static void +memo_shell_view_dispose (GObject *object) +{ + e_memo_shell_view_private_dispose (E_MEMO_SHELL_VIEW (object)); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_memo_shell_view_parent_class)->dispose (object); +} + +static void +memo_shell_view_finalize (GObject *object) +{ + e_memo_shell_view_private_finalize (E_MEMO_SHELL_VIEW (object)); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_memo_shell_view_parent_class)->finalize (object); +} + +static void +memo_shell_view_constructed (GObject *object) +{ + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_memo_shell_view_parent_class)->constructed (object); + + e_memo_shell_view_private_constructed (E_MEMO_SHELL_VIEW (object)); +} + +static void +memo_shell_view_execute_search (EShellView *shell_view) +{ + EMemoShellContent *memo_shell_content; + EShellWindow *shell_window; + EShellContent *shell_content; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GtkRadioAction *action; + ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; + EMemoTable *memo_table; + EWebView *web_view; + ECalModel *model; + gchar *query; + gchar *temp; + gint value; + + shell_content = e_shell_view_get_shell_content (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + memo_shell_content = E_MEMO_SHELL_CONTENT (shell_content); + searchbar = e_memo_shell_content_get_searchbar (memo_shell_content); + + action = GTK_RADIO_ACTION (ACTION (MEMO_SEARCH_ANY_FIELD_CONTAINS)); + value = gtk_radio_action_get_current_value (action); + + if (value == MEMO_SEARCH_ADVANCED) { + query = e_shell_view_get_search_query (shell_view); + + if (!query) + query = g_strdup (""); + } else { + const gchar *format; + const gchar *text; + GString *string; + + text = e_shell_searchbar_get_search_text (searchbar); + + if (text == NULL || *text == '\0') { + text = ""; + value = MEMO_SEARCH_SUMMARY_CONTAINS; + } + + switch (value) { + default: + text = ""; + /* fall through */ + + case MEMO_SEARCH_SUMMARY_CONTAINS: + format = "(contains? \"summary\" %s)"; + break; + + case MEMO_SEARCH_DESCRIPTION_CONTAINS: + format = "(contains? \"description\" %s)"; + break; + + case MEMO_SEARCH_ANY_FIELD_CONTAINS: + format = "(contains? \"any\" %s)"; + break; + } + + /* Build the query. */ + string = g_string_new (""); + e_sexp_encode_string (string, text); + query = g_strdup_printf (format, string->str); + g_string_free (string, TRUE); + } + + /* Apply selected filter. */ + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + value = e_action_combo_box_get_current_value (combo_box); + switch (value) { + case MEMO_FILTER_ANY_CATEGORY: + break; + + case MEMO_FILTER_UNMATCHED: + temp = g_strdup_printf ( + "(and (has-categories? #f) %s", query); + g_free (query); + query = temp; + break; + + default: + { + GList *categories; + const gchar *category_name; + + categories = e_util_get_searchable_categories (); + category_name = g_list_nth_data (categories, value); + g_list_free (categories); + + temp = g_strdup_printf ( + "(and (has-categories? \"%s\") %s)", + category_name, query); + g_free (query); + query = temp; + } + } + + /* Submit the query. */ + memo_table = e_memo_shell_content_get_memo_table (memo_shell_content); + model = e_memo_table_get_model (memo_table); + e_cal_model_set_search_query (model, query); + g_free (query); + + preview_pane = + e_memo_shell_content_get_preview_pane (memo_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); + e_cal_component_preview_clear (memo_preview); +} + +static void +memo_shell_view_update_actions (EShellView *shell_view) +{ + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + GtkAction *action; + const gchar *label; + gboolean sensitive; + guint32 state; + + /* Be descriptive. */ + gboolean any_memos_selected; + gboolean has_primary_source; + gboolean multiple_memos_selected; + gboolean primary_source_is_writable; + gboolean primary_source_is_removable; + gboolean primary_source_is_remote_deletable; + gboolean primary_source_in_collection; + gboolean selection_has_url; + gboolean single_memo_selected; + gboolean sources_are_editable; + gboolean refresh_supported; + + /* Chain up to parent's update_actions() method. */ + E_SHELL_VIEW_CLASS (e_memo_shell_view_parent_class)-> + update_actions (shell_view); + + shell_window = e_shell_view_get_shell_window (shell_view); + + shell_content = e_shell_view_get_shell_content (shell_view); + state = e_shell_content_check_state (shell_content); + + single_memo_selected = + (state & E_MEMO_SHELL_CONTENT_SELECTION_SINGLE); + multiple_memos_selected = + (state & E_MEMO_SHELL_CONTENT_SELECTION_MULTIPLE); + sources_are_editable = + (state & E_MEMO_SHELL_CONTENT_SELECTION_CAN_EDIT); + selection_has_url = + (state & E_MEMO_SHELL_CONTENT_SELECTION_HAS_URL); + + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + state = e_shell_sidebar_check_state (shell_sidebar); + + has_primary_source = + (state & E_MEMO_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE); + primary_source_is_writable = + (state & E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE); + primary_source_is_removable = + (state & E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE); + primary_source_is_remote_deletable = + (state & E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE); + primary_source_in_collection = + (state & E_MEMO_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION); + refresh_supported = + (state & E_MEMO_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH); + + any_memos_selected = + (single_memo_selected || multiple_memos_selected); + + action = ACTION (MEMO_DELETE); + sensitive = any_memos_selected && sources_are_editable; + gtk_action_set_sensitive (action, sensitive); + if (multiple_memos_selected) + label = _("Delete Memos"); + else + label = _("Delete Memo"); + gtk_action_set_label (action, label); + + action = ACTION (MEMO_FIND); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_FORWARD); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_LIST_COPY); + sensitive = has_primary_source; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_LIST_DELETE); + sensitive = + primary_source_is_removable || + primary_source_is_remote_deletable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_LIST_PROPERTIES); + sensitive = primary_source_is_writable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_LIST_REFRESH); + sensitive = refresh_supported; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_LIST_RENAME); + sensitive = + primary_source_is_writable && + !primary_source_in_collection; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_OPEN); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_OPEN_URL); + sensitive = single_memo_selected && selection_has_url; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_PRINT); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (MEMO_SAVE_AS); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); +} + +static void +e_memo_shell_view_class_init (EMemoShellViewClass *class) +{ + GObjectClass *object_class; + EShellViewClass *shell_view_class; + + g_type_class_add_private (class, sizeof (EMemoShellViewPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = memo_shell_view_dispose; + object_class->finalize = memo_shell_view_finalize; + object_class->constructed = memo_shell_view_constructed; + + shell_view_class = E_SHELL_VIEW_CLASS (class); + shell_view_class->label = _("Memos"); + shell_view_class->icon_name = "evolution-memos"; + shell_view_class->ui_definition = "evolution-memos.ui"; + shell_view_class->ui_manager_id = "org.gnome.evolution.memos"; + shell_view_class->search_options = "/memo-search-options"; + shell_view_class->search_rules = "memotypes.xml"; + shell_view_class->new_shell_content = e_memo_shell_content_new; + shell_view_class->new_shell_sidebar = e_memo_shell_sidebar_new; + shell_view_class->execute_search = memo_shell_view_execute_search; + shell_view_class->update_actions = memo_shell_view_update_actions; + + /* Ensure the GalView types we need are registered. */ + g_type_ensure (GAL_TYPE_VIEW_ETABLE); +} + +static void +e_memo_shell_view_class_finalize (EMemoShellViewClass *class) +{ +} + +static void +e_memo_shell_view_init (EMemoShellView *memo_shell_view) +{ + memo_shell_view->priv = + E_MEMO_SHELL_VIEW_GET_PRIVATE (memo_shell_view); + + e_memo_shell_view_private_init (memo_shell_view); +} + +void +e_memo_shell_view_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_memo_shell_view_register_type (type_module); +} + diff --git a/modules/calendar/e-memo-shell-view.h b/modules/calendar/e-memo-shell-view.h new file mode 100644 index 0000000000..52af4761f2 --- /dev/null +++ b/modules/calendar/e-memo-shell-view.h @@ -0,0 +1,66 @@ +/* + * e-memo-shell-view.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_VIEW_H +#define E_MEMO_SHELL_VIEW_H + +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_MEMO_SHELL_VIEW \ + (e_memo_shell_view_get_type ()) +#define E_MEMO_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellView)) +#define E_MEMO_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewClass)) +#define E_IS_MEMO_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_MEMO_SHELL_VIEW)) +#define E_IS_MEMO_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_MEMO_SHELL_VIEW)) +#define E_MEMO_SHELL_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_MEMO_SHELL_VIEW, EMemoShellViewClass)) + +G_BEGIN_DECLS + +typedef struct _EMemoShellView EMemoShellView; +typedef struct _EMemoShellViewClass EMemoShellViewClass; +typedef struct _EMemoShellViewPrivate EMemoShellViewPrivate; + +struct _EMemoShellView { + EShellView parent; + EMemoShellViewPrivate *priv; +}; + +struct _EMemoShellViewClass { + EShellViewClass parent_class; +}; + +GType e_memo_shell_view_get_type (void); +void e_memo_shell_view_type_register (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_VIEW_H */ diff --git a/modules/calendar/e-task-shell-backend.c b/modules/calendar/e-task-shell-backend.c new file mode 100644 index 0000000000..1ed485fcff --- /dev/null +++ b/modules/calendar/e-task-shell-backend.c @@ -0,0 +1,488 @@ +/* + * e-task-shell-backend.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-backend.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libecal/libecal.h> + +#include "shell/e-shell.h" +#include "shell/e-shell-backend.h" +#include "shell/e-shell-window.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/dialogs/task-editor.h" + +#include "e-task-shell-content.h" +#include "e-task-shell-migrate.h" +#include "e-task-shell-sidebar.h" +#include "e-task-shell-view.h" + +#define E_TASK_SHELL_BACKEND_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendPrivate)) + +struct _ETaskShellBackendPrivate { + gint placeholder; +}; + +G_DEFINE_DYNAMIC_TYPE ( + ETaskShellBackend, + e_task_shell_backend, + E_TYPE_SHELL_BACKEND) + +static void +task_shell_backend_new_task (ECalClient *cal_client, + EShell *shell, + CompEditorFlags flags) +{ + ECalComponent *comp; + CompEditor *editor; + + editor = task_editor_new (cal_client, shell, flags); + comp = cal_comp_task_new_with_defaults (cal_client); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); +} + +static void +task_shell_backend_task_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = user_data; + EClient *client; + CompEditorFlags flags = 0; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + task_shell_backend_new_task ( + E_CAL_CLIENT (client), shell, flags); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +task_shell_backend_task_assigned_new_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EShell *shell = E_SHELL (user_data); + EClient *client; + CompEditorFlags flags = 0; + GError *error = NULL; + + flags |= COMP_EDITOR_NEW_ITEM; + flags |= COMP_EDITOR_IS_ASSIGNED; + flags |= COMP_EDITOR_USER_ORG; + + client = e_client_cache_get_client_finish ( + E_CLIENT_CACHE (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (client != NULL) { + task_shell_backend_new_task ( + E_CAL_CLIENT (client), shell, flags); + g_object_unref (client); + } else { + /* XXX Handle errors better. */ + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_object_unref (shell); +} + +static void +action_task_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + ESource *source; + ESourceRegistry *registry; + EClientCache *client_cache; + const gchar *action_name; + + /* This callback is used for both tasks and assigned tasks. */ + + shell = e_shell_window_get_shell (shell_window); + client_cache = e_shell_get_client_cache (shell); + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_default_task_list (registry); + + /* Use a callback function appropriate for the action. */ + action_name = gtk_action_get_name (action); + if (strcmp (action_name, "task-assigned-new") == 0) + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_TASK_LIST, + NULL, + task_shell_backend_task_assigned_new_cb, + g_object_ref (shell)); + else + e_client_cache_get_client ( + client_cache, source, + E_SOURCE_EXTENSION_TASK_LIST, + NULL, + task_shell_backend_task_new_cb, + g_object_ref (shell)); + + g_object_unref (source); +} + +static void +action_task_list_new_cb (GtkAction *action, + EShellWindow *shell_window) +{ + EShell *shell; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Task List")); + + gtk_widget_show (dialog); +} + +static GtkActionEntry item_entries[] = { + + { "task-new", + "stock_task", + NC_("New", "_Task"), + "<Shift><Control>t", + N_("Create a new task"), + G_CALLBACK (action_task_new_cb) }, + + { "task-assigned-new", + "stock_task", + NC_("New", "Assigne_d Task"), + NULL, + N_("Create a new assigned task"), + G_CALLBACK (action_task_new_cb) } +}; + +static GtkActionEntry source_entries[] = { + + { "task-list-new", + "stock_todo", + NC_("New", "Tas_k List"), + NULL, + N_("Create a new task list"), + G_CALLBACK (action_task_list_new_cb) } +}; + +static gboolean +task_shell_backend_handle_uri_cb (EShellBackend *shell_backend, + const gchar *uri) +{ + EShell *shell; + CompEditor *editor; + CompEditorFlags flags = 0; + EClient *client; + EClientCache *client_cache; + ECalComponent *comp; + ESource *source; + ESourceRegistry *registry; + SoupURI *soup_uri; + icalcomponent *icalcomp; + icalproperty *icalprop; + const gchar *cp; + gchar *source_uid = NULL; + gchar *comp_uid = NULL; + gchar *comp_rid = NULL; + gboolean handled = FALSE; + GError *error = NULL; + + shell = e_shell_backend_get_shell (shell_backend); + client_cache = e_shell_get_client_cache (shell); + + if (strncmp (uri, "task:", 5) != 0) + return FALSE; + + soup_uri = soup_uri_new (uri); + + if (soup_uri == NULL) + return FALSE; + + cp = soup_uri_get_query (soup_uri); + if (cp == NULL) + goto exit; + + while (*cp != '\0') { + gchar *header; + gchar *content; + gsize header_len; + gsize content_len; + + header_len = strcspn (cp, "=&"); + + /* If it's malformed, give up. */ + if (cp[header_len] != '=') + break; + + header = (gchar *) cp; + header[header_len] = '\0'; + cp += header_len + 1; + + content_len = strcspn (cp, "&"); + + content = g_strndup (cp, content_len); + if (g_ascii_strcasecmp (header, "source-uid") == 0) + source_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-uid") == 0) + comp_uid = g_strdup (content); + else if (g_ascii_strcasecmp (header, "comp-rid") == 0) + comp_rid = g_strdup (content); + g_free (content); + + cp += content_len; + if (*cp == '&') { + cp++; + if (strcmp (cp, "amp;") == 0) + cp += 4; + } + } + + if (source_uid == NULL || comp_uid == NULL) + goto exit; + + /* URI is valid, so consider it handled. Whether + * we successfully open it is another matter... */ + handled = TRUE; + + registry = e_shell_get_registry (shell); + source = e_source_registry_ref_source (registry, source_uid); + if (source == NULL) { + g_printerr ("No source for UID '%s'\n", source_uid); + goto exit; + } + + client = e_client_cache_get_client_sync ( + client_cache, source, + E_SOURCE_EXTENSION_TASK_LIST, + NULL, &error); + + /* Sanity check. */ + g_return_val_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL)), FALSE); + + if (error != NULL) { + g_warning ( + "%s: Failed to create/open client: %s", + G_STRFUNC, error->message); + g_object_unref (source); + g_error_free (error); + goto exit; + } + + g_object_unref (source); + source = NULL; + + /* XXX Copied from e_task_shell_view_open_task(). + * Clearly a new utility function is needed. */ + + editor = comp_editor_find_instance (comp_uid); + + if (editor != NULL) + goto present; + + e_cal_client_get_object_sync ( + E_CAL_CLIENT (client), comp_uid, + comp_rid, &icalcomp, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to get object: %s", + G_STRFUNC, error->message); + g_object_unref (client); + g_error_free (error); + goto exit; + } + + comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { + g_warning ("%s: Failed to set icalcomp to comp\n", G_STRFUNC); + icalcomponent_free (icalcomp); + icalcomp = NULL; + } + + icalprop = icalcomp ? icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY) : NULL; + if (icalprop != NULL) + flags |= COMP_EDITOR_IS_ASSIGNED; + + if (itip_organizer_is_user (registry, comp, E_CAL_CLIENT (client))) + flags |= COMP_EDITOR_USER_ORG; + + if (!e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + + editor = task_editor_new (E_CAL_CLIENT (client), shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + +present: + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (client); + +exit: + g_free (source_uid); + g_free (comp_uid); + g_free (comp_rid); + + soup_uri_free (soup_uri); + + return handled; +} + +static void +task_shell_backend_window_added_cb (EShellBackend *shell_backend, + GtkWindow *window) +{ + const gchar *module_name; + + if (!E_IS_SHELL_WINDOW (window)) + return; + + module_name = E_SHELL_BACKEND_GET_CLASS (shell_backend)->name; + + e_shell_window_register_new_item_actions ( + E_SHELL_WINDOW (window), module_name, + item_entries, G_N_ELEMENTS (item_entries)); + + e_shell_window_register_new_source_actions ( + E_SHELL_WINDOW (window), module_name, + source_entries, G_N_ELEMENTS (source_entries)); +} + +static void +task_shell_backend_constructed (GObject *object) +{ + EShell *shell; + EShellBackend *shell_backend; + + shell_backend = E_SHELL_BACKEND (object); + shell = e_shell_backend_get_shell (shell_backend); + + g_signal_connect_swapped ( + shell, "handle-uri", + G_CALLBACK (task_shell_backend_handle_uri_cb), + shell_backend); + + g_signal_connect_swapped ( + shell, "window-added", + G_CALLBACK (task_shell_backend_window_added_cb), + shell_backend); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_task_shell_backend_parent_class)->constructed (object); +} + +static void +e_task_shell_backend_class_init (ETaskShellBackendClass *class) +{ + GObjectClass *object_class; + EShellBackendClass *shell_backend_class; + + g_type_class_add_private (class, sizeof (ETaskShellBackendPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = task_shell_backend_constructed; + + shell_backend_class = E_SHELL_BACKEND_CLASS (class); + shell_backend_class->shell_view_type = E_TYPE_TASK_SHELL_VIEW; + shell_backend_class->name = "tasks"; + shell_backend_class->aliases = ""; + shell_backend_class->schemes = "task"; + shell_backend_class->sort_order = 500; + shell_backend_class->preferences_page = "calendar-and-tasks"; + shell_backend_class->start = NULL; + shell_backend_class->migrate = e_task_shell_backend_migrate; +} + +static void +e_task_shell_backend_class_finalize (ETaskShellBackendClass *class) +{ +} + +static void +e_task_shell_backend_init (ETaskShellBackend *task_shell_backend) +{ + task_shell_backend->priv = + E_TASK_SHELL_BACKEND_GET_PRIVATE (task_shell_backend); +} + +void +e_task_shell_backend_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_task_shell_backend_register_type (type_module); +} diff --git a/modules/calendar/e-task-shell-backend.h b/modules/calendar/e-task-shell-backend.h new file mode 100644 index 0000000000..043623592c --- /dev/null +++ b/modules/calendar/e-task-shell-backend.h @@ -0,0 +1,67 @@ +/* + * e-task-shell-backend.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_BACKEND_H +#define E_TASK_SHELL_BACKEND_H + +#include <shell/e-shell-backend.h> + +/* Standard GObject macros */ +#define E_TYPE_TASK_SHELL_BACKEND \ + (e_task_shell_backend_get_type ()) +#define E_TASK_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackend)) +#define E_TASK_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendClass)) +#define E_IS_TASK_SHELL_BACKEND(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_TASK_SHELL_BACKEND)) +#define E_IS_TASK_SHELL_BACKEND_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_TASK_SHELL_BACKEND)) +#define E_TASK_SHELL_BACKEND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_TASK_SHELL_BACKEND, ETaskShellBackendClass)) + +G_BEGIN_DECLS + +typedef struct _ETaskShellBackend ETaskShellBackend; +typedef struct _ETaskShellBackendClass ETaskShellBackendClass; +typedef struct _ETaskShellBackendPrivate ETaskShellBackendPrivate; + +struct _ETaskShellBackend { + EShellBackend parent; + ETaskShellBackendPrivate *priv; +}; + +struct _ETaskShellBackendClass { + EShellBackendClass parent_class; +}; + +GType e_task_shell_backend_get_type (void); +void e_task_shell_backend_type_register + (GTypeModule *type_module); + +G_END_DECLS + +#endif /* E_TASK_SHELL_BACKEND_H */ diff --git a/modules/calendar/e-task-shell-content.c b/modules/calendar/e-task-shell-content.c new file mode 100644 index 0000000000..cff476d4b0 --- /dev/null +++ b/modules/calendar/e-task-shell-content.c @@ -0,0 +1,790 @@ +/* + * e-task-shell-content.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-content.h" + +#include <glib/gi18n.h> + +#include "shell/e-shell-utils.h" + +#include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" +#include "calendar/gui/e-cal-model-tasks.h" + +#define E_TASK_SHELL_CONTENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentPrivate)) + +struct _ETaskShellContentPrivate { + GtkWidget *paned; + GtkWidget *task_table; + GtkWidget *preview_pane; + + ECalModel *task_model; + GtkOrientation orientation; + + gchar *current_uid; + + guint preview_visible : 1; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_ORIENTATION, + PROP_PREVIEW_VISIBLE +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + ETaskShellContent, + e_task_shell_content, + E_TYPE_SHELL_CONTENT, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + GTK_TYPE_ORIENTABLE, NULL)) + +static void +task_shell_content_display_view_cb (ETaskShellContent *task_shell_content, + GalView *gal_view) +{ + ETaskTable *task_table; + + if (!GAL_IS_VIEW_ETABLE (gal_view)) + return; + + task_table = e_task_shell_content_get_task_table (task_shell_content); + + gal_view_etable_attach_table ( + GAL_VIEW_ETABLE (gal_view), E_TABLE (task_table)); +} + +static void +task_shell_content_table_foreach_cb (gint model_row, + gpointer user_data) +{ + ECalModelComponent *comp_data; + icalcomponent *clone; + icalcomponent *vcal; + gchar *string; + + struct { + ECalModel *model; + GSList *list; + } *foreach_data = user_data; + + comp_data = e_cal_model_get_component_at ( + foreach_data->model, model_row); + + vcal = e_cal_util_new_top_level (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_util_add_timezones_from_component (vcal, comp_data->icalcomp); + icalcomponent_add_component (vcal, clone); + + /* String is owned by libical; do not free. */ + string = icalcomponent_as_ical_string (vcal); + if (string != NULL) { + ESource *source; + const gchar *source_uid; + + source = e_client_get_source (E_CLIENT (comp_data->client)); + source_uid = e_source_get_uid (source); + + foreach_data->list = g_slist_prepend ( + foreach_data->list, + g_strdup_printf ("%s\n%s", source_uid, string)); + } + + icalcomponent_free (vcal); +} + +static void +task_shell_content_table_drag_data_get_cb (ETaskShellContent *task_shell_content, + gint row, + gint col, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + ETaskTable *task_table; + GdkAtom target; + + struct { + ECalModel *model; + GSList *list; + } foreach_data; + + /* Sanity check the selection target. */ + target = gtk_selection_data_get_target (selection_data); + if (!e_targets_include_calendar (&target, 1)) + return; + + task_table = e_task_shell_content_get_task_table (task_shell_content); + + foreach_data.model = e_task_table_get_model (task_table); + foreach_data.list = NULL; + + e_table_selected_row_foreach ( + E_TABLE (task_table), + task_shell_content_table_foreach_cb, + &foreach_data); + + if (foreach_data.list != NULL) { + cal_comp_selection_set_string_list ( + selection_data, foreach_data.list); + g_slist_foreach (foreach_data.list, (GFunc) g_free, NULL); + g_slist_free (foreach_data.list); + } +} + +static void +task_shell_content_table_drag_data_delete_cb (ETaskShellContent *task_shell_content, + gint row, + gint col, + GdkDragContext *context) +{ + /* Moved components are deleted from source immediately when moved, + * because some of them can be part of destination source, and we + * don't want to delete not-moved tasks. There is no such information + * which event has been moved and which not, so skip this method. */ +} + +static void +task_shell_content_cursor_change_cb (ETaskShellContent *task_shell_content, + gint row, + ETable *table) +{ + ECalComponentPreview *task_preview; + ECalModel *task_model; + ECalModelComponent *comp_data; + EPreviewPane *preview_pane; + EWebView *web_view; + const gchar *uid; + + task_model = e_task_shell_content_get_task_model (task_shell_content); + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); + + if (e_table_selected_count (table) != 1) { + if (task_shell_content->priv->preview_visible) + e_cal_component_preview_clear (task_preview); + return; + } + + row = e_table_get_cursor_row (table); + comp_data = e_cal_model_get_component_at (task_model, row); + + if (task_shell_content->priv->preview_visible) { + ECalComponent *comp; + + comp = e_cal_component_new_from_icalcomponent ( + icalcomponent_new_clone (comp_data->icalcomp)); + + e_cal_component_preview_display ( + task_preview, comp_data->client, comp, + e_cal_model_get_timezone (task_model), + e_cal_model_get_use_24_hour_format (task_model)); + + g_object_unref (comp); + } + + uid = icalcomponent_get_uid (comp_data->icalcomp); + g_free (task_shell_content->priv->current_uid); + task_shell_content->priv->current_uid = g_strdup (uid); +} + +static void +task_shell_content_selection_change_cb (ETaskShellContent *task_shell_content, + ETable *table) +{ + ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; + EWebView *web_view; + + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); + + if (e_table_selected_count (table) != 1) + e_cal_component_preview_clear (task_preview); +} + +static void +task_shell_content_model_row_changed_cb (ETaskShellContent *task_shell_content, + gint row, + ETableModel *model) +{ + ECalModelComponent *comp_data; + ETaskTable *task_table; + const gchar *current_uid; + const gchar *uid; + + current_uid = task_shell_content->priv->current_uid; + if (current_uid == NULL) + return; + + comp_data = e_cal_model_get_component_at (E_CAL_MODEL (model), row); + if (comp_data == NULL) + return; + + uid = icalcomponent_get_uid (comp_data->icalcomp); + if (g_strcmp0 (uid, current_uid) != 0) + return; + + task_table = e_task_shell_content_get_task_table (task_shell_content); + + task_shell_content_cursor_change_cb ( + task_shell_content, 0, E_TABLE (task_table)); +} + +static void +task_shell_content_restore_state_cb (EShellWindow *shell_window, + EShellView *shell_view, + EShellContent *shell_content) +{ + ETaskShellContentPrivate *priv; + GSettings *settings; + + priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (shell_content); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "task-hpane-position", + priv->paned, "hposition", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind ( + settings, "task-vpane-position", + priv->paned, "vposition", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); +} + +static void +task_shell_content_is_editing_changed_cb (ETaskTable *task_table, + GParamSpec *param, + EShellView *shell_view) +{ + g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); + + e_shell_view_update_actions (shell_view); +} + +static GtkOrientation +task_shell_content_get_orientation (ETaskShellContent *task_shell_content) +{ + return task_shell_content->priv->orientation; +} + +static void +task_shell_content_set_orientation (ETaskShellContent *task_shell_content, + GtkOrientation orientation) +{ + if (task_shell_content->priv->orientation == orientation) + return; + + task_shell_content->priv->orientation = orientation; + + g_object_notify (G_OBJECT (task_shell_content), "orientation"); +} + +static void +task_shell_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ORIENTATION: + task_shell_content_set_orientation ( + E_TASK_SHELL_CONTENT (object), + g_value_get_enum (value)); + return; + + case PROP_PREVIEW_VISIBLE: + e_task_shell_content_set_preview_visible ( + E_TASK_SHELL_CONTENT (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +task_shell_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_MODEL: + g_value_set_object ( + value, + e_task_shell_content_get_task_model ( + E_TASK_SHELL_CONTENT (object))); + return; + + case PROP_ORIENTATION: + g_value_set_enum ( + value, + task_shell_content_get_orientation ( + E_TASK_SHELL_CONTENT (object))); + return; + + case PROP_PREVIEW_VISIBLE: + g_value_set_boolean ( + value, + e_task_shell_content_get_preview_visible ( + E_TASK_SHELL_CONTENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +task_shell_content_dispose (GObject *object) +{ + ETaskShellContentPrivate *priv; + + priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object); + + if (priv->paned != NULL) { + g_object_unref (priv->paned); + priv->paned = NULL; + } + + if (priv->task_table != NULL) { + g_object_unref (priv->task_table); + priv->task_table = NULL; + } + + if (priv->preview_pane != NULL) { + g_object_unref (priv->preview_pane); + priv->preview_pane = NULL; + } + + if (priv->task_model != NULL) { + g_object_unref (priv->task_model); + priv->task_model = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_task_shell_content_parent_class)->dispose (object); +} + +static void +task_shell_content_finalize (GObject *object) +{ + ETaskShellContentPrivate *priv; + + priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object); + + g_free (priv->current_uid); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_task_shell_content_parent_class)->finalize (object); +} + +static void +task_shell_content_constructed (GObject *object) +{ + ETaskShellContentPrivate *priv; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellContent *shell_content; + EShellTaskbar *shell_taskbar; + ESourceRegistry *registry; + GalViewInstance *view_instance; + GtkTargetList *target_list; + GtkTargetEntry *targets; + GtkWidget *container; + GtkWidget *widget; + gint n_targets; + + priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_task_shell_content_parent_class)->constructed (object); + + shell_content = E_SHELL_CONTENT (object); + shell_view = e_shell_content_get_shell_view (shell_content); + shell_taskbar = e_shell_view_get_shell_taskbar (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + priv->task_model = e_cal_model_tasks_new (registry); + + /* Build content widgets. */ + + container = GTK_WIDGET (object); + + widget = e_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->paned = g_object_ref (widget); + gtk_widget_show (widget); + + g_object_bind_property ( + object, "orientation", + widget, "orientation", + G_BINDING_SYNC_CREATE); + + container = priv->paned; + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE); + gtk_widget_show (widget); + + container = widget; + + widget = e_task_table_new (shell_view, priv->task_model); + gtk_container_add (GTK_CONTAINER (container), widget); + priv->task_table = g_object_ref (widget); + gtk_widget_show (widget); + + container = priv->paned; + + widget = e_cal_component_preview_new (); + gtk_widget_show (widget); + + g_signal_connect_swapped ( + widget, "status-message", + G_CALLBACK (e_shell_taskbar_set_message), + shell_taskbar); + + widget = e_preview_pane_new (E_WEB_VIEW (widget)); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); + + g_object_bind_property ( + object, "preview-visible", + widget, "visible", + G_BINDING_SYNC_CREATE); + + target_list = gtk_target_list_new (NULL, 0); + e_target_list_add_calendar_targets (target_list, 0); + targets = gtk_target_table_new_from_list (target_list, &n_targets); + + e_table_drag_source_set ( + E_TABLE (priv->task_table), + GDK_BUTTON1_MASK, targets, n_targets, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_ASK); + + gtk_target_table_free (targets, n_targets); + gtk_target_list_unref (target_list); + + g_signal_connect_swapped ( + priv->task_table, "table-drag-data-get", + G_CALLBACK (task_shell_content_table_drag_data_get_cb), + object); + + g_signal_connect_swapped ( + priv->task_table, "table-drag-data-delete", + G_CALLBACK (task_shell_content_table_drag_data_delete_cb), + object); + + g_signal_connect_swapped ( + priv->task_table, "cursor-change", + G_CALLBACK (task_shell_content_cursor_change_cb), + object); + + g_signal_connect_swapped ( + priv->task_table, "selection-change", + G_CALLBACK (task_shell_content_selection_change_cb), + object); + + g_signal_connect ( + priv->task_table, "notify::is-editing", + G_CALLBACK (task_shell_content_is_editing_changed_cb), shell_view); + + g_signal_connect_swapped ( + priv->task_model, "model-row-changed", + G_CALLBACK (task_shell_content_model_row_changed_cb), + object); + + /* Load the view instance. */ + + view_instance = e_shell_view_new_view_instance (shell_view, NULL); + g_signal_connect_swapped ( + view_instance, "display-view", + G_CALLBACK (task_shell_content_display_view_cb), + object); + e_shell_view_set_view_instance (shell_view, view_instance); + gal_view_instance_load (view_instance); + g_object_unref (view_instance); + + /* Restore pane positions from the last session once + * the shell view is fully initialized and visible. */ + g_signal_connect ( + shell_window, "shell-view-created::tasks", + G_CALLBACK (task_shell_content_restore_state_cb), + shell_content); +} + +static guint32 +task_shell_content_check_state (EShellContent *shell_content) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + GSList *list, *iter; + gboolean assignable = TRUE; + gboolean editable = TRUE; + gboolean has_url = FALSE; + gint n_selected; + gint n_complete = 0; + gint n_incomplete = 0; + guint32 state = 0; + + task_shell_content = E_TASK_SHELL_CONTENT (shell_content); + task_table = e_task_shell_content_get_task_table (task_shell_content); + + n_selected = e_table_selected_count (E_TABLE (task_table)); + + list = e_task_table_get_selected (task_table); + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + icalproperty *prop; + const gchar *cap; + gboolean read_only; + + read_only = e_client_is_readonly (E_CLIENT (comp_data->client)); + editable &= !read_only; + + cap = CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT; + if (e_client_check_capability (E_CLIENT (comp_data->client), cap)) + assignable = FALSE; + + cap = CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK; + if (e_client_check_capability (E_CLIENT (comp_data->client), cap)) + assignable = FALSE; + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + has_url |= (prop != NULL); + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_COMPLETED_PROPERTY); + if (prop != NULL) + n_complete++; + else + n_incomplete++; + } + g_slist_free (list); + + if (n_selected == 1) + state |= E_TASK_SHELL_CONTENT_SELECTION_SINGLE; + if (n_selected > 1) + state |= E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE; + if (assignable) + state |= E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN; + if (editable) + state |= E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT; + if (n_complete > 0) + state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE; + if (n_incomplete > 0) + state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE; + if (has_url) + state |= E_TASK_SHELL_CONTENT_SELECTION_HAS_URL; + + return state; +} + +static void +task_shell_content_focus_search_results (EShellContent *shell_content) +{ + ETaskShellContentPrivate *priv; + + priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (shell_content); + + gtk_widget_grab_focus (priv->task_table); +} + +static void +e_task_shell_content_class_init (ETaskShellContentClass *class) +{ + GObjectClass *object_class; + EShellContentClass *shell_content_class; + + g_type_class_add_private (class, sizeof (ETaskShellContentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = task_shell_content_set_property; + object_class->get_property = task_shell_content_get_property; + object_class->dispose = task_shell_content_dispose; + object_class->finalize = task_shell_content_finalize; + object_class->constructed = task_shell_content_constructed; + + shell_content_class = E_SHELL_CONTENT_CLASS (class); + shell_content_class->check_state = task_shell_content_check_state; + shell_content_class->focus_search_results = + task_shell_content_focus_search_results; + + g_object_class_install_property ( + object_class, + PROP_MODEL, + g_param_spec_object ( + "model", + "Model", + "The task table model", + E_TYPE_CAL_MODEL, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_PREVIEW_VISIBLE, + g_param_spec_boolean ( + "preview-visible", + "Preview is Visible", + "Whether the preview pane is visible", + TRUE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_override_property ( + object_class, PROP_ORIENTATION, "orientation"); +} + +static void +e_task_shell_content_class_finalize (ETaskShellContentClass *class) +{ +} + +static void +e_task_shell_content_init (ETaskShellContent *task_shell_content) +{ + task_shell_content->priv = + E_TASK_SHELL_CONTENT_GET_PRIVATE (task_shell_content); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_task_shell_content_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_task_shell_content_register_type (type_module); +} + +GtkWidget * +e_task_shell_content_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_TASK_SHELL_CONTENT, + "shell-view", shell_view, NULL); +} + +ECalModel * +e_task_shell_content_get_task_model (ETaskShellContent *task_shell_content) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); + + return task_shell_content->priv->task_model; +} + +ETaskTable * +e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); + + return E_TASK_TABLE (task_shell_content->priv->task_table); +} + +EPreviewPane * +e_task_shell_content_get_preview_pane (ETaskShellContent *task_shell_content) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); + + return E_PREVIEW_PANE (task_shell_content->priv->preview_pane); +} + +gboolean +e_task_shell_content_get_preview_visible (ETaskShellContent *task_shell_content) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_CONTENT (task_shell_content), FALSE); + + return task_shell_content->priv->preview_visible; +} + +void +e_task_shell_content_set_preview_visible (ETaskShellContent *task_shell_content, + gboolean preview_visible) +{ + g_return_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content)); + + if (task_shell_content->priv->preview_visible == preview_visible) + return; + + task_shell_content->priv->preview_visible = preview_visible; + + if (preview_visible && task_shell_content->priv->preview_pane) { + task_shell_content_cursor_change_cb ( + task_shell_content, 0, + E_TABLE (task_shell_content->priv->task_table)); + } + + g_object_notify (G_OBJECT (task_shell_content), "preview-visible"); +} + +EShellSearchbar * +e_task_shell_content_get_searchbar (ETaskShellContent *task_shell_content) +{ + EShellView *shell_view; + EShellContent *shell_content; + GtkWidget *widget; + + g_return_val_if_fail ( + E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); + + shell_content = E_SHELL_CONTENT (task_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + widget = e_shell_view_get_searchbar (shell_view); + + return E_SHELL_SEARCHBAR (widget); +} + diff --git a/modules/calendar/e-task-shell-content.h b/modules/calendar/e-task-shell-content.h new file mode 100644 index 0000000000..7f5fac68b1 --- /dev/null +++ b/modules/calendar/e-task-shell-content.h @@ -0,0 +1,98 @@ +/* + * e-task-shell-content.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_CONTENT_H +#define E_TASK_SHELL_CONTENT_H + +#include <shell/e-shell-content.h> +#include <shell/e-shell-searchbar.h> +#include <shell/e-shell-view.h> + +#include <calendar/gui/e-cal-model.h> +#include <calendar/gui/e-task-table.h> + +/* Standard GObject macros */ +#define E_TYPE_TASK_SHELL_CONTENT \ + (e_task_shell_content_get_type ()) +#define E_TASK_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContent)) +#define E_TASK_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentClass)) +#define E_IS_TASK_SHELL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_TASK_SHELL_CONTENT)) +#define E_IS_TASK_SHELL_CONTENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_TASK_SHELL_CONTENT)) +#define E_TASK_SHELL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentClass)) + +G_BEGIN_DECLS + +typedef struct _ETaskShellContent ETaskShellContent; +typedef struct _ETaskShellContentClass ETaskShellContentClass; +typedef struct _ETaskShellContentPrivate ETaskShellContentPrivate; + +enum { + E_TASK_SHELL_CONTENT_SELECTION_SINGLE = 1 << 0, + E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE = 1 << 1, + E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN = 1 << 2, + E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT = 1 << 3, + E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE = 1 << 4, + E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE = 1 << 5, + E_TASK_SHELL_CONTENT_SELECTION_HAS_URL = 1 << 6 +}; + +struct _ETaskShellContent { + EShellContent parent; + ETaskShellContentPrivate *priv; +}; + +struct _ETaskShellContentClass { + EShellContentClass parent_class; +}; + +GType e_task_shell_content_get_type (void); +void e_task_shell_content_type_register + (GTypeModule *type_module); +GtkWidget * e_task_shell_content_new + (EShellView *shell_view); +ECalModel * e_task_shell_content_get_task_model + (ETaskShellContent *task_shell_content); +ETaskTable * e_task_shell_content_get_task_table + (ETaskShellContent *task_shell_content); +EPreviewPane * e_task_shell_content_get_preview_pane + (ETaskShellContent *task_shell_content); +gboolean e_task_shell_content_get_preview_visible + (ETaskShellContent *task_shell_content); +void e_task_shell_content_set_preview_visible + (ETaskShellContent *task_shell_content, + gboolean preview_visible); +EShellSearchbar * + e_task_shell_content_get_searchbar + (ETaskShellContent *task_shell_content); + +G_END_DECLS + +#endif /* E_TASK_SHELL_CONTENT_H */ diff --git a/modules/calendar/e-task-shell-migrate.c b/modules/calendar/e-task-shell-migrate.c new file mode 100644 index 0000000000..40b731a0d0 --- /dev/null +++ b/modules/calendar/e-task-shell-migrate.c @@ -0,0 +1,38 @@ +/* + * e-task-shell-backend-migrate.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-migrate.h" + +gboolean +e_task_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error) +{ + g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE); + + return TRUE; +} diff --git a/modules/calendar/e-task-shell-migrate.h b/modules/calendar/e-task-shell-migrate.h new file mode 100644 index 0000000000..f33aa82fcc --- /dev/null +++ b/modules/calendar/e-task-shell-migrate.h @@ -0,0 +1,37 @@ +/* + * e-task-shell-backend-migrate.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_BACKEND_MIGRATE_H +#define E_TASK_SHELL_BACKEND_MIGRATE_H + +#include <shell/e-shell-backend.h> + +G_BEGIN_DECLS + +gboolean e_task_shell_backend_migrate (EShellBackend *shell_backend, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_TASK_SHELL_BACKEND_MIGRATE_H */ diff --git a/modules/calendar/e-task-shell-sidebar.c b/modules/calendar/e-task-shell-sidebar.c new file mode 100644 index 0000000000..c12bef75f5 --- /dev/null +++ b/modules/calendar/e-task-shell-sidebar.c @@ -0,0 +1,816 @@ +/* + * e-task-shell-sidebar.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-sidebar.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "e-util/e-util.h" +#include "calendar/gui/e-task-list-selector.h" +#include "calendar/gui/misc.h" + +#define E_TASK_SHELL_SIDEBAR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarPrivate)) + +typedef struct _ConnectClosure ConnectClosure; + +struct _ETaskShellSidebarPrivate { + GtkWidget *selector; + + /* The default client is for ECalModel. It follows the + * sidebar's primary selection, even if the highlighted + * source is not selected. The tricky part is we don't + * update the property until the client is successfully + * opened. So the user first highlights a source, then + * sometime later we update our default-client property + * which is bound by an EBinding to ECalModel. */ + EClient *default_client; + + /* Not referenced, only for pointer comparison. */ + ESource *connecting_default_source_instance; + + EActivity *connecting_default_client; +}; + +struct _ConnectClosure { + ETaskShellSidebar *task_shell_sidebar; + EActivity *activity; + + /* For error messages. */ + gchar *unique_display_name; +}; + +enum { + PROP_0, + PROP_DEFAULT_CLIENT, + PROP_SELECTOR +}; + +enum { + CLIENT_ADDED, + CLIENT_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_DYNAMIC_TYPE ( + ETaskShellSidebar, + e_task_shell_sidebar, + E_TYPE_SHELL_SIDEBAR) + +static ConnectClosure * +connect_closure_new (ETaskShellSidebar *task_shell_sidebar, + ESource *source) +{ + ConnectClosure *closure; + EAlertSink *alert_sink; + GCancellable *cancellable; + ESourceRegistry *registry; + ESourceSelector *selector; + EShellView *shell_view; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + gchar *text; + + shell_sidebar = E_SHELL_SIDEBAR (task_shell_sidebar); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + registry = e_source_selector_get_registry (selector); + + closure = g_slice_new0 (ConnectClosure); + closure->task_shell_sidebar = g_object_ref (task_shell_sidebar); + closure->activity = e_activity_new (); + closure->unique_display_name = + e_source_registry_dup_unique_display_name ( + registry, source, E_SOURCE_EXTENSION_TASK_LIST); + + text = g_strdup_printf ( + _("Opening task list '%s'"), + closure->unique_display_name); + e_activity_set_text (closure->activity, text); + g_free (text); + + alert_sink = E_ALERT_SINK (shell_content); + e_activity_set_alert_sink (closure->activity, alert_sink); + + cancellable = g_cancellable_new (); + e_activity_set_cancellable (closure->activity, cancellable); + g_object_unref (cancellable); + + e_shell_backend_add_activity (shell_backend, closure->activity); + + return closure; +} + +static void +connect_closure_free (ConnectClosure *closure) +{ + g_clear_object (&closure->task_shell_sidebar); + g_clear_object (&closure->activity); + + g_free (closure->unique_display_name); + + g_slice_free (ConnectClosure, closure); +} + +static gboolean +task_shell_sidebar_map_uid_to_source (GValue *value, + GVariant *variant, + gpointer user_data) +{ + ESourceRegistry *registry; + ESource *source; + const gchar *uid; + + registry = E_SOURCE_REGISTRY (user_data); + uid = g_variant_get_string (variant, NULL); + if (uid != NULL && *uid != '\0') + source = e_source_registry_ref_source (registry, uid); + else + source = e_source_registry_ref_default_task_list (registry); + g_value_take_object (value, source); + + return (source != NULL); +} + +static GVariant * +task_shell_sidebar_map_source_to_uid (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariant *variant = NULL; + ESource *source; + + source = g_value_get_object (value); + + if (source != NULL) { + const gchar *uid; + + uid = e_source_get_uid (source); + variant = g_variant_new_string (uid); + } + + return variant; +} + +static void +task_shell_sidebar_emit_client_added (ETaskShellSidebar *task_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_ADDED]; + + g_signal_emit (task_shell_sidebar, signal_id, 0, client); +} + +static void +task_shell_sidebar_emit_client_removed (ETaskShellSidebar *task_shell_sidebar, + EClient *client) +{ + guint signal_id = signals[CLIENT_REMOVED]; + + g_signal_emit (task_shell_sidebar, signal_id, 0, client); +} + +static void +task_shell_sidebar_handle_connect_error (EActivity *activity, + const gchar *unique_display_name, + const GError *error) +{ + EAlertSink *alert_sink; + gboolean cancelled = FALSE; + gboolean offline_error; + + alert_sink = e_activity_get_alert_sink (activity); + + cancelled |= g_error_matches ( + error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + cancelled |= g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED); + + offline_error = g_error_matches ( + error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE); + + if (e_activity_handle_cancellation (activity, error)) { + /* do nothing */ + } else if (offline_error) { + e_alert_submit ( + alert_sink, + "calendar:prompt-no-contents-offline-tasks", + unique_display_name, + NULL); + } else { + e_alert_submit ( + alert_sink, + "calendar:failed-open-tasks", + unique_display_name, + error->message, + NULL); + } +} + +static void +task_shell_sidebar_client_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ConnectClosure *closure = user_data; + GError *error = NULL; + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + if (error != NULL) { + task_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + e_task_shell_sidebar_add_client (closure->task_shell_sidebar, client); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +task_shell_sidebar_default_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EClient *client; + ESource *source; + ConnectClosure *closure = user_data; + ETaskShellSidebarPrivate *priv; + GError *error = NULL; + + priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (closure->task_shell_sidebar); + + client = e_client_selector_get_client_finish ( + E_CLIENT_SELECTOR (source_object), result, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((client != NULL) && (error == NULL)) || + ((client == NULL) && (error != NULL))); + + g_clear_object (&priv->connecting_default_client); + + if (error != NULL) { + task_shell_sidebar_handle_connect_error ( + closure->activity, + closure->unique_display_name, + error); + g_error_free (error); + goto exit; + } + + e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED); + + source = e_client_get_source (client); + + if (source == priv->connecting_default_source_instance) + priv->connecting_default_source_instance = NULL; + + if (priv->default_client != NULL) + g_object_unref (priv->default_client); + + priv->default_client = g_object_ref (client); + + g_object_notify ( + G_OBJECT (closure->task_shell_sidebar), "default-client"); + + g_object_unref (client); + +exit: + connect_closure_free (closure); +} + +static void +task_shell_sidebar_set_default (ETaskShellSidebar *task_shell_sidebar, + ESource *source) +{ + ETaskShellSidebarPrivate *priv; + ESourceSelector *selector; + ConnectClosure *closure; + + priv = task_shell_sidebar->priv; + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + /* already loading that source as default source */ + if (source == priv->connecting_default_source_instance) + return; + + /* Cancel the previous request if unfinished. */ + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + closure = connect_closure_new (task_shell_sidebar, source); + + /* it's only for pointer comparison, no need to ref it */ + priv->connecting_default_source_instance = source; + priv->connecting_default_client = g_object_ref (closure->activity); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + task_shell_sidebar_default_connect_cb, closure); +} + +static void +task_shell_sidebar_row_changed_cb (ETaskShellSidebar *task_shell_sidebar, + GtkTreePath *tree_path, + GtkTreeIter *tree_iter, + GtkTreeModel *tree_model) +{ + ESourceSelector *selector; + ESource *source; + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + source = e_source_selector_ref_source_by_path (selector, tree_path); + + /* XXX This signal gets emitted a lot while the model is being + * rebuilt, during which time we won't get a valid ESource. + * ESourceSelector should probably block this signal while + * rebuilding the model, but we'll be forgiving and not + * emit a warning. */ + if (source == NULL) + return; + + if (e_source_selector_source_is_selected (selector, source)) + e_task_shell_sidebar_add_source (task_shell_sidebar, source); + else + e_task_shell_sidebar_remove_source (task_shell_sidebar, source); + + g_object_unref (source); +} + +static void +task_shell_sidebar_primary_selection_changed_cb (ETaskShellSidebar *task_shell_sidebar, + ESourceSelector *selector) +{ + ESource *source; + + source = e_source_selector_ref_primary_selection (selector); + if (source == NULL) + return; + + task_shell_sidebar_set_default (task_shell_sidebar, source); + + g_object_unref (source); +} + +static void +task_shell_sidebar_restore_state_cb (EShellWindow *shell_window, + EShellView *shell_view, + EShellSidebar *shell_sidebar) +{ + ETaskShellSidebarPrivate *priv; + ESourceRegistry *registry; + ESourceSelector *selector; + GSettings *settings; + GtkTreeModel *model; + + priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (shell_sidebar); + + selector = E_SOURCE_SELECTOR (priv->selector); + registry = e_source_selector_get_registry (selector); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector)); + + g_signal_connect_swapped ( + registry, "source-removed", + G_CALLBACK (e_task_shell_sidebar_remove_source), shell_sidebar); + + g_signal_connect_swapped ( + model, "row-changed", + G_CALLBACK (task_shell_sidebar_row_changed_cb), + shell_sidebar); + + g_signal_connect_swapped ( + selector, "primary-selection-changed", + G_CALLBACK (task_shell_sidebar_primary_selection_changed_cb), + shell_sidebar); + + /* This will trigger our "row-changed" signal handler for each + * task list source, so the appropriate ECalClients get added to + * the ECalModel, which will then create view objects to display + * the task list content. This all happens asynchronously. */ + e_source_selector_update_all_rows (selector); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind_with_mapping ( + settings, "primary-tasks", + selector, "primary-selection", + G_SETTINGS_BIND_DEFAULT, + task_shell_sidebar_map_uid_to_source, + task_shell_sidebar_map_source_to_uid, + g_object_ref (registry), + (GDestroyNotify) g_object_unref); + + g_object_unref (settings); +} + +static void +task_shell_sidebar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_DEFAULT_CLIENT: + g_value_set_object ( + value, + e_task_shell_sidebar_get_default_client ( + E_TASK_SHELL_SIDEBAR (object))); + return; + + case PROP_SELECTOR: + g_value_set_object ( + value, + e_task_shell_sidebar_get_selector ( + E_TASK_SHELL_SIDEBAR (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +task_shell_sidebar_dispose (GObject *object) +{ + ETaskShellSidebarPrivate *priv; + + priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (object); + + if (priv->selector != NULL) { + g_object_unref (priv->selector); + priv->selector = NULL; + } + + if (priv->default_client != NULL) { + g_object_unref (priv->default_client); + priv->default_client = NULL; + } + + if (priv->connecting_default_client != NULL) { + e_activity_cancel (priv->connecting_default_client); + g_object_unref (priv->connecting_default_client); + priv->connecting_default_client = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_task_shell_sidebar_parent_class)->dispose (object); +} + +static void +task_shell_sidebar_constructed (GObject *object) +{ + ETaskShellSidebarPrivate *priv; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSidebar *shell_sidebar; + EClientCache *client_cache; + GtkContainer *container; + GtkWidget *widget; + AtkObject *a11y; + + priv = E_TASK_SHELL_SIDEBAR_GET_PRIVATE (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_task_shell_sidebar_parent_class)->constructed (object); + + shell_sidebar = E_SHELL_SIDEBAR (object); + shell_view = e_shell_sidebar_get_shell_view (shell_sidebar); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + container = GTK_CONTAINER (shell_sidebar); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ( + GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); + gtk_container_add (container, widget); + gtk_widget_show (widget); + + container = GTK_CONTAINER (widget); + + client_cache = e_shell_get_client_cache (shell); + widget = e_task_list_selector_new (client_cache); + e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE); + gtk_container_add (container, widget); + a11y = gtk_widget_get_accessible (widget); + atk_object_set_name (a11y, _("Task List Selector")); + priv->selector = g_object_ref (widget); + gtk_widget_show (widget); + + /* Restore widget state from the last session once + * the shell view is fully initialized and visible. */ + g_signal_connect ( + shell_window, "shell-view-created::tasks", + G_CALLBACK (task_shell_sidebar_restore_state_cb), + shell_sidebar); +} + +static guint32 +task_shell_sidebar_check_state (EShellSidebar *shell_sidebar) +{ + ETaskShellSidebar *task_shell_sidebar; + ESourceSelector *selector; + ESourceRegistry *registry; + ESource *source; + gboolean is_writable = FALSE; + gboolean is_removable = FALSE; + gboolean is_remote_creatable = FALSE; + gboolean is_remote_deletable = FALSE; + gboolean in_collection = FALSE; + gboolean refresh_supported = FALSE; + gboolean has_primary_source = FALSE; + guint32 state = 0; + + task_shell_sidebar = E_TASK_SHELL_SIDEBAR (shell_sidebar); + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + registry = e_source_selector_get_registry (selector); + + if (source != NULL) { + EClient *client; + ESource *collection; + + has_primary_source = TRUE; + is_writable = e_source_get_writable (source); + is_removable = e_source_get_removable (source); + is_remote_creatable = e_source_get_remote_creatable (source); + is_remote_deletable = e_source_get_remote_deletable (source); + + collection = e_source_registry_find_extension ( + registry, source, E_SOURCE_EXTENSION_COLLECTION); + if (collection != NULL) { + in_collection = TRUE; + g_object_unref (collection); + } + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + refresh_supported = + e_client_check_refresh_supported (client); + g_object_unref (client); + } + + g_object_unref (source); + } + + if (has_primary_source) + state |= E_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE; + if (is_writable) + state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE; + if (is_removable) + state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE; + if (is_remote_creatable) + state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE; + if (is_remote_deletable) + state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE; + if (in_collection) + state |= E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION; + if (refresh_supported) + state |= E_TASK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH; + + return state; +} + +static void +task_shell_sidebar_client_removed (ETaskShellSidebar *task_shell_sidebar, + ECalClient *client) +{ + ESourceSelector *selector; + ESource *source; + + source = e_client_get_source (E_CLIENT (client)); + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + e_source_selector_unselect_source (selector, source); +} + +static void +e_task_shell_sidebar_class_init (ETaskShellSidebarClass *class) +{ + GObjectClass *object_class; + EShellSidebarClass *shell_sidebar_class; + + g_type_class_add_private (class, sizeof (ETaskShellSidebarPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = task_shell_sidebar_get_property; + object_class->dispose = task_shell_sidebar_dispose; + object_class->constructed = task_shell_sidebar_constructed; + + shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class); + shell_sidebar_class->check_state = task_shell_sidebar_check_state; + + class->client_removed = task_shell_sidebar_client_removed; + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_CLIENT, + g_param_spec_object ( + "default-client", + "Default Task ECalClient", + "Default client for task operations", + E_TYPE_CAL_CLIENT, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_SELECTOR, + g_param_spec_object ( + "selector", + "Source Selector Widget", + "This widget displays groups of task lists", + E_TYPE_SOURCE_SELECTOR, + G_PARAM_READABLE)); + + signals[CLIENT_ADDED] = g_signal_new ( + "client-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETaskShellSidebarClass, client_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); + + signals[CLIENT_REMOVED] = g_signal_new ( + "client-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETaskShellSidebarClass, client_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_CAL_CLIENT); +} + +static void +e_task_shell_sidebar_class_finalize (ETaskShellSidebarClass *class) +{ +} + +static void +e_task_shell_sidebar_init (ETaskShellSidebar *task_shell_sidebar) +{ + task_shell_sidebar->priv = + E_TASK_SHELL_SIDEBAR_GET_PRIVATE (task_shell_sidebar); + + /* Postpone widget construction until we have a shell view. */ +} + +void +e_task_shell_sidebar_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_task_shell_sidebar_register_type (type_module); +} + +GtkWidget * +e_task_shell_sidebar_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_TASK_SHELL_SIDEBAR, + "shell-view", shell_view, NULL); +} + +ECalClient * +e_task_shell_sidebar_get_default_client (ETaskShellSidebar *task_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar), NULL); + + return (ECalClient *) task_shell_sidebar->priv->default_client; +} + +ESourceSelector * +e_task_shell_sidebar_get_selector (ETaskShellSidebar *task_shell_sidebar) +{ + g_return_val_if_fail ( + E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar), NULL); + + return E_SOURCE_SELECTOR (task_shell_sidebar->priv->selector); +} + +void +e_task_shell_sidebar_add_client (ETaskShellSidebar *task_shell_sidebar, + EClient *client) +{ + ESource *source; + ESourceSelector *selector; + + g_return_if_fail (E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + + source = e_client_get_source (client); + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + e_source_selector_select_source (selector, source); + + task_shell_sidebar_emit_client_added (task_shell_sidebar, client); +} + +void +e_task_shell_sidebar_add_source (ETaskShellSidebar *task_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + ConnectClosure *closure; + + g_return_if_fail (E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + e_source_selector_select_source (selector, source); + + closure = connect_closure_new (task_shell_sidebar, source); + + e_client_selector_get_client ( + E_CLIENT_SELECTOR (selector), source, + e_activity_get_cancellable (closure->activity), + task_shell_sidebar_client_connect_cb, closure); +} + +void +e_task_shell_sidebar_remove_source (ETaskShellSidebar *task_shell_sidebar, + ESource *source) +{ + ESourceSelector *selector; + EClient *client; + + g_return_if_fail (E_IS_TASK_SHELL_SIDEBAR (task_shell_sidebar)); + g_return_if_fail (E_IS_SOURCE (source)); + + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + + if (client != NULL) { + task_shell_sidebar_emit_client_removed ( + task_shell_sidebar, client); + g_object_unref (client); + } +} diff --git a/modules/calendar/e-task-shell-sidebar.h b/modules/calendar/e-task-shell-sidebar.h new file mode 100644 index 0000000000..c92d21fd68 --- /dev/null +++ b/modules/calendar/e-task-shell-sidebar.h @@ -0,0 +1,103 @@ +/* + * e-task-shell-sidebar.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_SIDEBAR_H +#define E_TASK_SHELL_SIDEBAR_H + +#include <libecal/libecal.h> + +#include <shell/e-shell-sidebar.h> +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_TASK_SHELL_SIDEBAR \ + (e_task_shell_sidebar_get_type ()) +#define E_TASK_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebar)) +#define E_TASK_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarClass)) +#define E_IS_TASK_SHELL_SIDEBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_TASK_SHELL_SIDEBAR)) +#define E_IS_TASK_SHELL_SIDEBAR_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_TASK_SHELL_SIDEBAR)) +#define E_TASK_SHELL_SIDEBAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_TASK_SHELL_SIDEBAR, ETaskShellSidebarClass)) + +G_BEGIN_DECLS + +typedef struct _ETaskShellSidebar ETaskShellSidebar; +typedef struct _ETaskShellSidebarClass ETaskShellSidebarClass; +typedef struct _ETaskShellSidebarPrivate ETaskShellSidebarPrivate; + +enum { + E_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE = 1 << 0, + E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE = 1 << 1, + E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE = 1 << 2, + E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE = 1 << 3, + E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE = 1 << 4, + E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION = 1 << 5, + E_TASK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH = 1 << 6 +}; + +struct _ETaskShellSidebar { + EShellSidebar parent; + ETaskShellSidebarPrivate *priv; +}; + +struct _ETaskShellSidebarClass { + EShellSidebarClass parent_class; + + /* Signals */ + void (*client_added) (ETaskShellSidebar *task_shell_sidebar, + ECalClient *client); + void (*client_removed) + (ETaskShellSidebar *task_shell_sidebar, + ECalClient *client); +}; + +GType e_task_shell_sidebar_get_type (void); +void e_task_shell_sidebar_type_register + (GTypeModule *type_module); +GtkWidget * e_task_shell_sidebar_new + (EShellView *shell_view); +ECalClient * e_task_shell_sidebar_get_default_client + (ETaskShellSidebar *task_shell_sidebar); +ESourceSelector * + e_task_shell_sidebar_get_selector + (ETaskShellSidebar *task_shell_sidebar); +void e_task_shell_sidebar_add_client + (ETaskShellSidebar *task_shell_sidebar, + EClient *client); +void e_task_shell_sidebar_add_source + (ETaskShellSidebar *task_shell_sidebar, + ESource *source); +void e_task_shell_sidebar_remove_source + (ETaskShellSidebar *task_shell_sidebar, + ESource *source); + +G_END_DECLS + +#endif /* E_TASK_SHELL_SIDEBAR_H */ diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c new file mode 100644 index 0000000000..fe0dd051b2 --- /dev/null +++ b/modules/calendar/e-task-shell-view-actions.c @@ -0,0 +1,1251 @@ +/* + * e-task-shell-view-actions.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-view-private.h" + +static void +action_task_assign_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + GSList *list; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected task. */ + e_task_shell_view_open_task (task_shell_view, comp_data); + + /* FIXME Need to actually assign the task. */ +} + +static void +action_task_delete_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + e_selectable_delete_selection (E_SELECTABLE (task_table)); +} + +static void +action_task_find_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EPreviewPane *preview_pane; + + task_shell_content = task_shell_view->priv->task_shell_content; + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + + e_preview_pane_show_search_bar (preview_pane); +} + +static void +action_task_forward_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ECalModelComponent *comp_data; + ETaskTable *task_table; + ECalComponent *comp; + icalcomponent *clone; + GSList *list; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only forward the first selected task. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_PUBLISH, comp, + comp_data->client, NULL, NULL, NULL, TRUE, FALSE); + + g_object_unref (comp); +} + +static void +action_task_list_copy_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellSidebar *task_shell_sidebar; + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ESourceSelector *selector; + ESource *source; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + copy_source_dialog ( + GTK_WINDOW (shell_window), registry, + source, E_CAL_CLIENT_SOURCE_TYPE_TASKS); + + g_object_unref (source); +} + +static void +action_task_list_delete_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellSidebar *task_shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + ESource *source; + ESourceSelector *selector; + gint response; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + if (e_source_get_remote_deletable (source)) { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-remote-task-list", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remote_delete_source (shell_view, source); + + } else { + response = e_alert_run_dialog_for_args ( + GTK_WINDOW (shell_window), + "calendar:prompt-delete-task-list", + e_source_get_display_name (source), NULL); + + if (response == GTK_RESPONSE_YES) + e_shell_view_remove_source (shell_view, source); + } + + g_object_unref (source); +} + +static void +action_task_list_new_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + config = e_cal_source_config_new (registry, NULL, source_type); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("New Task List")); + + gtk_widget_show (dialog); +} + +static void +action_task_list_print_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + print_table ( + E_TABLE (task_table), _("Print Tasks"), _("Tasks"), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); +} + +static void +action_task_list_print_preview_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + print_table ( + E_TABLE (task_table), _("Print Tasks"), _("Tasks"), + GTK_PRINT_OPERATION_ACTION_PREVIEW); +} + +static void +action_task_list_properties_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ETaskShellSidebar *task_shell_sidebar; + ECalClientSourceType source_type; + ESource *source; + ESourceSelector *selector; + ESourceRegistry *registry; + GtkWidget *config; + GtkWidget *dialog; + const gchar *icon_name; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + source = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (source != NULL); + + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + registry = e_source_selector_get_registry (selector); + config = e_cal_source_config_new (registry, source, source_type); + + g_object_unref (source); + + dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config)); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), GTK_WINDOW (shell_window)); + + icon_name = gtk_action_get_icon_name (action); + gtk_window_set_icon_name (GTK_WINDOW (dialog), icon_name); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Task List Properties")); + + gtk_widget_show (dialog); +} + +static void +action_task_list_refresh_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellSidebar *task_shell_sidebar; + ESourceSelector *selector; + EClient *client = NULL; + ESource *source; + GError *error = NULL; + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + source = e_source_selector_ref_primary_selection (selector); + + if (source != NULL) { + client = e_client_selector_ref_cached_client ( + E_CLIENT_SELECTOR (selector), source); + g_object_unref (source); + } + + if (client == NULL) + return; + + g_return_if_fail (e_client_check_refresh_supported (client)); + + e_client_refresh_sync (client, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to refresh '%s', %s", + G_STRFUNC, e_source_get_display_name (source), + error->message); + g_error_free (error); + } + + g_object_unref (client); +} + +static void +action_task_list_rename_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellSidebar *task_shell_sidebar; + ESourceSelector *selector; + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + e_source_selector_edit_primary_selection (selector); +} + +static void +action_task_list_select_one_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellSidebar *task_shell_sidebar; + ESourceSelector *selector; + ESource *primary; + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + selector = e_task_shell_sidebar_get_selector (task_shell_sidebar); + + primary = e_source_selector_ref_primary_selection (selector); + g_return_if_fail (primary != NULL); + + e_source_selector_select_exclusive (selector, primary); + + g_object_unref (primary); +} + +static void +action_task_mark_complete_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalModel *model; + GSList *list, *iter; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + list = e_task_table_get_selected (task_table); + model = e_task_table_get_model (task_table); + + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + e_cal_model_tasks_mark_comp_complete ( + E_CAL_MODEL_TASKS (model), comp_data); + } + + g_slist_free (list); +} + +static void +action_task_mark_incomplete_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalModel *model; + GSList *list, *iter; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + list = e_task_table_get_selected (task_table); + model = e_task_table_get_model (task_table); + + for (iter = list; iter != NULL; iter = iter->next) { + ECalModelComponent *comp_data = iter->data; + e_cal_model_tasks_mark_comp_incomplete ( + E_CAL_MODEL_TASKS (model), comp_data); + } + + g_slist_free (list); +} + +static void +action_task_new_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalClient *client; + ECalComponent *comp; + CompEditor *editor; + GSList *list; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + if (list == NULL) { + ECalModel *model; + + model = e_task_table_get_model (task_table); + client = e_cal_model_ref_default_client (model); + } else { + ECalModelComponent *comp_data; + + comp_data = list->data; + client = g_object_ref (comp_data->client); + g_slist_free (list); + } + + g_return_if_fail (client != NULL); + + editor = task_editor_new (client, shell, COMP_EDITOR_NEW_ITEM); + comp = cal_comp_task_new_with_defaults (client); + comp_editor_edit_comp (editor, comp); + + gtk_window_present (GTK_WINDOW (editor)); + + g_object_unref (comp); + + g_object_unref (client); +} + +static void +action_task_open_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + GSList *list; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only open the first selected task. */ + e_task_shell_view_open_task (task_shell_view, comp_data); +} + +static void +action_task_open_url_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + ETaskShellContent *task_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + icalproperty *prop; + const gchar *uri; + GSList *list; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + + /* XXX We only open the URI of the first selected task. */ + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_URL_PROPERTY); + g_return_if_fail (prop != NULL); + + uri = icalproperty_get_url (prop); + e_show_uri (GTK_WINDOW (shell_window), uri); +} + +static void +action_task_preview_cb (GtkToggleAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + gboolean visible; + + task_shell_content = task_shell_view->priv->task_shell_content; + visible = gtk_toggle_action_get_active (action); + e_task_shell_content_set_preview_visible (task_shell_content, visible); +} + +static void +action_task_print_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ECalModelComponent *comp_data; + ECalComponent *comp; + ECalModel *model; + ETaskTable *task_table; + icalcomponent *clone; + GSList *list; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + model = e_task_table_get_model (task_table); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* XXX We only print the first selected task. */ + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + print_comp ( + comp, comp_data->client, + e_cal_model_get_timezone (model), + e_cal_model_get_use_24_hour_format (model), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); + + g_object_unref (comp); +} + +static void +action_task_purge_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShellView *shell_view; + EShellWindow *shell_window; + GtkWidget *content_area; + GtkWidget *dialog; + GtkWidget *widget; + gboolean active; + gint response; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + if (!e_task_shell_view_get_confirm_purge (task_shell_view)) + goto purge; + + /* XXX This needs reworked. The dialog looks like ass. */ + + dialog = gtk_message_dialog_new ( + GTK_WINDOW (shell_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_YES_NO, + "%s", _("This operation will permanently erase all tasks " + "marked as completed. If you continue, you will not be able " + "to recover these tasks.\n\nReally erase these tasks?")); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + widget = gtk_check_button_new_with_label (_("Do not ask me again")); + gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 6); + gtk_widget_show (widget); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + gtk_widget_destroy (dialog); + + if (response != GTK_RESPONSE_YES) + return; + + if (active) + e_task_shell_view_set_confirm_purge (task_shell_view, FALSE); + +purge: + e_task_shell_view_delete_completed (task_shell_view); +} + +static void +action_task_save_as_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + EShellBackend *shell_backend; + ETaskShellContent *task_shell_content; + ECalModelComponent *comp_data; + ETaskTable *task_table; + EActivity *activity; + GSList *list; + GFile *file; + gchar *string; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_window_get_shell (shell_window); + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + list = e_task_table_get_selected (task_table); + g_return_if_fail (list != NULL); + comp_data = list->data; + g_slist_free (list); + + /* Translators: Default filename part saving a task to a file when + * no summary is filed, the '.ics' extension is concatenated to it */ + string = icalcomp_suggest_filename (comp_data->icalcomp, _("task")); + file = e_shell_run_save_dialog ( + shell, _("Save as iCalendar"), string, + "*.ics:text/calendar", NULL, NULL); + g_free (string); + if (file == NULL) + return; + + /* XXX We only save the first selected task. */ + string = e_cal_client_get_component_as_string ( + comp_data->client, comp_data->icalcomp); + if (string == NULL) { + g_warning ("Could not convert task to a string"); + g_object_unref (file); + return; + } + + /* XXX No callback means errors are discarded. */ + activity = e_file_replace_contents_async ( + file, string, strlen (string), NULL, FALSE, + G_FILE_CREATE_NONE, (GAsyncReadyCallback) NULL, NULL); + e_shell_backend_add_activity (shell_backend, activity); + + /* Free the string when the activity is finalized. */ + g_object_set_data_full ( + G_OBJECT (activity), + "file-content", string, + (GDestroyNotify) g_free); + + g_object_unref (file); +} + +static void +action_task_view_cb (GtkRadioAction *action, + GtkRadioAction *current, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + GtkOrientable *orientable; + GtkOrientation orientation; + + task_shell_content = task_shell_view->priv->task_shell_content; + orientable = GTK_ORIENTABLE (task_shell_content); + + switch (gtk_radio_action_get_current_value (action)) { + case 0: + orientation = GTK_ORIENTATION_VERTICAL; + break; + case 1: + orientation = GTK_ORIENTATION_HORIZONTAL; + break; + default: + g_return_if_reached (); + } + + gtk_orientable_set_orientation (orientable, orientation); +} + +static GtkActionEntry task_entries[] = { + + { "task-assign", + NULL, + N_("_Assign Task"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_assign_cb) }, + + { "task-delete", + GTK_STOCK_DELETE, + N_("_Delete Task"), + NULL, + N_("Delete selected tasks"), + G_CALLBACK (action_task_delete_cb) }, + + { "task-find", + GTK_STOCK_FIND, + N_("_Find in Task..."), + "<Shift><Control>f", + N_("Search for text in the displayed task"), + G_CALLBACK (action_task_find_cb) }, + + { "task-forward", + "mail-forward", + N_("_Forward as iCalendar..."), + "<Control>f", + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_forward_cb) }, + + { "task-list-copy", + GTK_STOCK_COPY, + N_("Copy..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_list_copy_cb) }, + + { "task-list-delete", + GTK_STOCK_DELETE, + N_("D_elete Task List"), + NULL, + N_("Delete the selected task list"), + G_CALLBACK (action_task_list_delete_cb) }, + + { "task-list-new", + "stock_todo", + N_("_New Task List"), + NULL, + N_("Create a new task list"), + G_CALLBACK (action_task_list_new_cb) }, + + { "task-list-properties", + GTK_STOCK_PROPERTIES, + NULL, + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_list_properties_cb) }, + + { "task-list-refresh", + GTK_STOCK_REFRESH, + N_("Re_fresh"), + NULL, + N_("Refresh the selected task list"), + G_CALLBACK (action_task_list_refresh_cb) }, + + { "task-list-rename", + NULL, + N_("_Rename..."), + "F2", + N_("Rename the selected task list"), + G_CALLBACK (action_task_list_rename_cb) }, + + { "task-list-select-one", + "stock_check-filled", + N_("Show _Only This Task List"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_list_select_one_cb) }, + + { "task-mark-complete", + NULL, + N_("_Mark as Complete"), + "<Control>k", + N_("Mark selected tasks as complete"), + G_CALLBACK (action_task_mark_complete_cb) }, + + { "task-mark-incomplete", + NULL, + N_("Mar_k as Incomplete"), + NULL, + N_("Mark selected tasks as incomplete"), + G_CALLBACK (action_task_mark_incomplete_cb) }, + + { "task-new", + "stock_task", + N_("New _Task"), + NULL, + N_("Create a new task"), + G_CALLBACK (action_task_new_cb) }, + + { "task-open", + GTK_STOCK_OPEN, + N_("_Open Task"), + "<Control>o", + N_("View the selected task"), + G_CALLBACK (action_task_open_cb) }, + + { "task-open-url", + "applications-internet", + N_("Open _Web Page"), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_open_url_cb) }, + + { "task-purge", + NULL, + N_("Purg_e"), + "<Control>e", + N_("Delete completed tasks"), + G_CALLBACK (action_task_purge_cb) }, + + /*** Menus ***/ + + { "task-actions-menu", + NULL, + N_("_Actions"), + NULL, + NULL, + NULL }, + + { "task-preview-menu", + NULL, + N_("_Preview"), + NULL, + NULL, + NULL } +}; + +static EPopupActionEntry task_popup_entries[] = { + + { "task-list-popup-copy", + NULL, + "task-list-copy" }, + + { "task-list-popup-delete", + N_("_Delete"), + "task-list-delete" }, + + { "task-list-popup-properties", + NULL, + "task-list-properties" }, + + { "task-list-popup-refresh", + NULL, + "task-list-refresh" }, + + { "task-list-popup-rename", + NULL, + "task-list-rename" }, + + { "task-list-popup-select-one", + NULL, + "task-list-select-one" }, + + { "task-popup-assign", + NULL, + "task-assign" }, + + { "task-popup-forward", + NULL, + "task-forward" }, + + { "task-popup-mark-complete", + NULL, + "task-mark-complete" }, + + { "task-popup-mark-incomplete", + NULL, + "task-mark-incomplete" }, + + { "task-popup-open", + NULL, + "task-open" }, + + { "task-popup-open-url", + NULL, + "task-open-url" } +}; + +static GtkToggleActionEntry task_toggle_entries[] = { + + { "task-preview", + NULL, + N_("Task _Preview"), + "<Control>m", + N_("Show task preview pane"), + G_CALLBACK (action_task_preview_cb), + TRUE } +}; + +static GtkRadioActionEntry task_view_entries[] = { + + /* This action represents the inital active memo view. + * It should not be visible in the UI, nor should it be + * possible to switch to it from another shell view. */ + { "task-view-initial", + NULL, + NULL, + NULL, + NULL, + -1 }, + + { "task-view-classic", + NULL, + N_("_Classic View"), + NULL, + N_("Show task preview below the task list"), + 0 }, + + { "task-view-vertical", + NULL, + N_("_Vertical View"), + NULL, + N_("Show task preview alongside the task list"), + 1 } +}; + +static GtkRadioActionEntry task_filter_entries[] = { + + { "task-filter-active-tasks", + NULL, + N_("Active Tasks"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_ACTIVE_TASKS }, + + { "task-filter-any-category", + NULL, + N_("Any Category"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_ANY_CATEGORY }, + + { "task-filter-completed-tasks", + NULL, + N_("Completed Tasks"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_COMPLETED_TASKS }, + + { "task-filter-next-7-days-tasks", + NULL, + N_("Next 7 Days' Tasks"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_NEXT_7_DAYS_TASKS }, + + { "task-filter-overdue-tasks", + NULL, + N_("Overdue Tasks"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_OVERDUE_TASKS }, + + { "task-filter-tasks-with-attachments", + NULL, + N_("Tasks with Attachments"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_TASKS_WITH_ATTACHMENTS }, + + { "task-filter-unmatched", + NULL, + N_("Unmatched"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_FILTER_UNMATCHED } +}; + +static GtkRadioActionEntry task_search_entries[] = { + + { "task-search-advanced-hidden", + NULL, + N_("Advanced Search"), + NULL, + NULL, + TASK_SEARCH_ADVANCED }, + + { "task-search-any-field-contains", + NULL, + N_("Any field contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_SEARCH_ANY_FIELD_CONTAINS }, + + { "task-search-description-contains", + NULL, + N_("Description contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_SEARCH_DESCRIPTION_CONTAINS }, + + { "task-search-summary-contains", + NULL, + N_("Summary contains"), + NULL, + NULL, /* XXX Add a tooltip! */ + TASK_SEARCH_SUMMARY_CONTAINS } +}; + +static GtkActionEntry lockdown_printing_entries[] = { + + { "task-list-print", + GTK_STOCK_PRINT, + NULL, + "<Control>p", + N_("Print the list of tasks"), + G_CALLBACK (action_task_list_print_cb) }, + + { "task-list-print-preview", + GTK_STOCK_PRINT_PREVIEW, + NULL, + NULL, + N_("Preview the list of tasks to be printed"), + G_CALLBACK (action_task_list_print_preview_cb) }, + + { "task-print", + GTK_STOCK_PRINT, + NULL, + NULL, + N_("Print the selected task"), + G_CALLBACK (action_task_print_cb) } +}; + +static EPopupActionEntry lockdown_printing_popup_entries[] = { + + { "task-popup-print", + NULL, + "task-print" } +}; + +static GtkActionEntry lockdown_save_to_disk_entries[] = { + + { "task-save-as", + GTK_STOCK_SAVE_AS, + N_("_Save as iCalendar..."), + NULL, + NULL, /* XXX Add a tooltip! */ + G_CALLBACK (action_task_save_as_cb) } +}; + +static EPopupActionEntry lockdown_save_to_disk_popup_entries[] = { + + { "task-popup-save-as", + NULL, + "task-save-as" }, +}; + +void +e_task_shell_view_actions_init (ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + EPreviewPane *preview_pane; + EWebView *web_view; + GtkActionGroup *action_group; + GSettings *settings; + GtkAction *action; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + task_shell_content = task_shell_view->priv->task_shell_content; + searchbar = e_task_shell_content_get_searchbar (task_shell_content); + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); + + /* Task Actions */ + action_group = ACTION_GROUP (TASKS); + gtk_action_group_add_actions ( + action_group, task_entries, + G_N_ELEMENTS (task_entries), task_shell_view); + e_action_group_add_popup_actions ( + action_group, task_popup_entries, + G_N_ELEMENTS (task_popup_entries)); + gtk_action_group_add_toggle_actions ( + action_group, task_toggle_entries, + G_N_ELEMENTS (task_toggle_entries), task_shell_view); + gtk_action_group_add_radio_actions ( + action_group, task_view_entries, + G_N_ELEMENTS (task_view_entries), -1, + G_CALLBACK (action_task_view_cb), task_shell_view); + gtk_action_group_add_radio_actions ( + action_group, task_search_entries, + G_N_ELEMENTS (task_search_entries), + -1, NULL, NULL); + + /* Advanced Search Action */ + action = ACTION (TASK_SEARCH_ADVANCED_HIDDEN); + gtk_action_set_visible (action, FALSE); + e_shell_searchbar_set_search_option ( + searchbar, GTK_RADIO_ACTION (action)); + + /* Lockdown Printing Actions */ + action_group = ACTION_GROUP (LOCKDOWN_PRINTING); + gtk_action_group_add_actions ( + action_group, lockdown_printing_entries, + G_N_ELEMENTS (lockdown_printing_entries), + task_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_printing_popup_entries, + G_N_ELEMENTS (lockdown_printing_popup_entries)); + + /* Lockdown Save-to-Disk Actions */ + action_group = ACTION_GROUP (LOCKDOWN_SAVE_TO_DISK); + gtk_action_group_add_actions ( + action_group, lockdown_save_to_disk_entries, + G_N_ELEMENTS (lockdown_save_to_disk_entries), + task_shell_view); + e_action_group_add_popup_actions ( + action_group, lockdown_save_to_disk_popup_entries, + G_N_ELEMENTS (lockdown_save_to_disk_popup_entries)); + + /* Bind GObject properties to settings keys. */ + + settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "show-task-preview", + ACTION (TASK_PREVIEW), "active", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind ( + settings, "task-layout", + ACTION (TASK_VIEW_VERTICAL), "current-value", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); + + /* Fine tuning. */ + + g_object_bind_property ( + ACTION (TASK_PREVIEW), "active", + ACTION (TASK_VIEW_CLASSIC), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + ACTION (TASK_PREVIEW), "active", + ACTION (TASK_VIEW_VERTICAL), "sensitive", + G_BINDING_SYNC_CREATE); + + e_web_view_set_open_proxy (web_view, ACTION (TASK_OPEN)); + e_web_view_set_print_proxy (web_view, ACTION (TASK_PRINT)); + e_web_view_set_save_as_proxy (web_view, ACTION (TASK_SAVE_AS)); +} + +void +e_task_shell_view_update_search_filter (ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GtkActionGroup *action_group; + GtkRadioAction *radio_action; + GList *list, *iter; + GSList *group; + gint ii; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + + action_group = ACTION_GROUP (TASKS_FILTER); + e_action_group_remove_all_actions (action_group); + + /* Add the standard filter actions. No callback is needed + * because changes in the EActionComboBox are detected and + * handled by EShellSearchbar. */ + gtk_action_group_add_radio_actions ( + action_group, task_filter_entries, + G_N_ELEMENTS (task_filter_entries), + TASK_FILTER_ANY_CATEGORY, NULL, NULL); + + /* Retrieve the radio group from an action we just added. */ + list = gtk_action_group_list_actions (action_group); + radio_action = GTK_RADIO_ACTION (list->data); + group = gtk_radio_action_get_group (radio_action); + g_list_free (list); + + /* Build the category actions. */ + + list = e_util_get_searchable_categories (); + for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) { + const gchar *category_name = iter->data; + const gchar *filename; + GtkAction *action; + gchar *action_name; + + action_name = g_strdup_printf ( + "task-filter-category-%d", ii); + radio_action = gtk_radio_action_new ( + action_name, category_name, NULL, NULL, ii); + g_free (action_name); + + /* Convert the category icon file to a themed icon name. */ + filename = e_categories_get_icon_file_for (category_name); + if (filename != NULL && *filename != '\0') { + gchar *basename; + gchar *cp; + + basename = g_path_get_basename (filename); + + /* Lose the file extension. */ + if ((cp = strrchr (basename, '.')) != NULL) + *cp = '\0'; + + g_object_set ( + radio_action, "icon-name", basename, NULL); + + g_free (basename); + } + + gtk_radio_action_set_group (radio_action, group); + group = gtk_radio_action_get_group (radio_action); + + /* The action group takes ownership of the action. */ + action = GTK_ACTION (radio_action); + gtk_action_group_add_action (action_group, action); + g_object_unref (radio_action); + } + g_list_free (list); + + task_shell_content = task_shell_view->priv->task_shell_content; + searchbar = e_task_shell_content_get_searchbar (task_shell_content); + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + + e_shell_view_block_execute_search (shell_view); + + /* Use any action in the group; doesn't matter which. */ + e_action_combo_box_set_action (combo_box, radio_action); + + ii = TASK_FILTER_UNMATCHED; + e_action_combo_box_add_separator_after (combo_box, ii); + + ii = TASK_FILTER_TASKS_WITH_ATTACHMENTS; + e_action_combo_box_add_separator_after (combo_box, ii); + + e_shell_view_unblock_execute_search (shell_view); +} diff --git a/modules/calendar/e-task-shell-view-actions.h b/modules/calendar/e-task-shell-view-actions.h new file mode 100644 index 0000000000..afa9fe3e1b --- /dev/null +++ b/modules/calendar/e-task-shell-view-actions.h @@ -0,0 +1,109 @@ +/* + * e-task-shell-view-actions.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_VIEW_ACTIONS_H +#define E_TASK_SHELL_VIEW_ACTIONS_H + +#include <shell/e-shell-window-actions.h> + +/* Task Actions */ +#define E_SHELL_WINDOW_ACTION_TASK_ASSIGN(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-assign") +#define E_SHELL_WINDOW_ACTION_TASK_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-delete") +#define E_SHELL_WINDOW_ACTION_TASK_FIND(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-find") +#define E_SHELL_WINDOW_ACTION_TASK_FORWARD(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-forward") +#define E_SHELL_WINDOW_ACTION_TASK_MARK_COMPLETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-mark-complete") +#define E_SHELL_WINDOW_ACTION_TASK_MARK_INCOMPLETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-mark-incomplete") +#define E_SHELL_WINDOW_ACTION_TASK_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-new") +#define E_SHELL_WINDOW_ACTION_TASK_OPEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-open") +#define E_SHELL_WINDOW_ACTION_TASK_OPEN_URL(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-open-url") +#define E_SHELL_WINDOW_ACTION_TASK_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-preview") +#define E_SHELL_WINDOW_ACTION_TASK_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-print") +#define E_SHELL_WINDOW_ACTION_TASK_PURGE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-purge") +#define E_SHELL_WINDOW_ACTION_TASK_SAVE_AS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-save-as") +#define E_SHELL_WINDOW_ACTION_TASK_VIEW_CLASSIC(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-view-classic") +#define E_SHELL_WINDOW_ACTION_TASK_VIEW_VERTICAL(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-view-vertical") + +/* Task List Actions */ +#define E_SHELL_WINDOW_ACTION_TASK_LIST_COPY(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-copy") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_DELETE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-delete") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_NEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-new") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_PRINT(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-print") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_PRINT_PREVIEW(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-print-preview") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_PROPERTIES(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-properties") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_REFRESH(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-refresh") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_RENAME(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-rename") +#define E_SHELL_WINDOW_ACTION_TASK_LIST_SELECT_ONE(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-list-select-one") + +/* Task Query Actions */ +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_ACTIVE_TASKS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-active-tasks") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_ANY_CATEGORY(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-any-category") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_COMPLETED_TASKS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-completed-tasks") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_NEXT_7_DAYS_TASKS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-next-7-days-tasks") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_OVERDUE_TASKS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-overdue-tasks") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_TASKS_WITH_ATTACHMENTS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-tasks-with-attachments") +#define E_SHELL_WINDOW_ACTION_TASK_FILTER_UNMATCHED(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-filter-unmatched") +#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_ADVANCED_HIDDEN(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-search-advanced-hidden") +#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_ANY_FIELD_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-search-any-field-contains") +#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_DESCRIPTION_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-search-description-contains") +#define E_SHELL_WINDOW_ACTION_TASK_SEARCH_SUMMARY_CONTAINS(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-search-summary-contains") + +/* Action Groups */ +#define E_SHELL_WINDOW_ACTION_GROUP_TASKS(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "tasks") +#define E_SHELL_WINDOW_ACTION_GROUP_TASKS_FILTER(window) \ + E_SHELL_WINDOW_ACTION_GROUP ((window), "tasks-filter") + +#endif /* E_TASK_SHELL_VIEW_ACTIONS_H */ diff --git a/modules/calendar/e-task-shell-view-private.c b/modules/calendar/e-task-shell-view-private.c new file mode 100644 index 0000000000..820339fa30 --- /dev/null +++ b/modules/calendar/e-task-shell-view-private.c @@ -0,0 +1,755 @@ +/* + * e-task-shell-view-private.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-view-private.h" + +#include "e-util/e-util-private.h" + +static void +task_shell_view_model_row_appended_cb (ETaskShellView *task_shell_view, + ECalModel *model) +{ + ETaskShellSidebar *task_shell_sidebar; + ECalClient *client; + + /* This is the "Click to Add" handler. */ + + client = e_cal_model_ref_default_client (model); + + task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; + e_task_shell_sidebar_add_client (task_shell_sidebar, E_CLIENT (client)); + + g_object_unref (client); +} + +static gboolean +task_shell_view_process_completed_tasks_cb (gpointer user_data) +{ + ETaskShellContent *task_shell_content; + ETaskShellView *task_shell_view; + ETaskTable *task_table; + + task_shell_view = E_TASK_SHELL_VIEW (user_data); + + task_shell_view->priv->update_completed_timeout = 0; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + e_task_table_process_completed_tasks (task_table, TRUE); + + /* Search query takes whether to show completed tasks into account, + * so if the preference has changed we need to update the query. */ + e_shell_view_execute_search (E_SHELL_VIEW (task_shell_view)); + + return FALSE; +} + +static void +task_shell_view_process_completed_tasks (ETaskShellView *task_shell_view) +{ + guint source_id; + + source_id = task_shell_view->priv->update_completed_timeout; + + if (source_id > 0) + g_source_remove (source_id); + + source_id = g_timeout_add_seconds ( + 1, task_shell_view_process_completed_tasks_cb, + task_shell_view); + + task_shell_view->priv->update_completed_timeout = source_id; +} + +static void +task_shell_view_hide_completed_tasks_changed_cb (GSettings *settings, + const gchar *key, + ETaskShellView *task_shell_view) +{ + task_shell_view_process_completed_tasks (task_shell_view); +} + +static void +task_shell_view_table_popup_event_cb (EShellView *shell_view, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/task-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); +} + +static void +task_shell_view_selector_client_added_cb (ETaskShellView *task_shell_view, + ECalClient *client) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalModel *model; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + model = e_task_table_get_model (task_table); + + e_cal_model_add_client (model, client); +} + +static void +task_shell_view_selector_client_removed_cb (ETaskShellView *task_shell_view, + ECalClient *client) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalModel *model; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + model = e_task_table_get_model (task_table); + + e_cal_model_remove_client (model, client); +} + +static gboolean +task_shell_view_selector_popup_event_cb (EShellView *shell_view, + ESource *primary_source, + GdkEvent *button_event) +{ + const gchar *widget_path; + + widget_path = "/task-list-popup"; + e_shell_view_show_popup_menu (shell_view, widget_path, button_event); + + return TRUE; +} + +static gboolean +task_shell_view_update_timeout_cb (ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ETaskTable *task_table; + ECalModel *model; + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + model = e_task_table_get_model (task_table); + + e_task_table_process_completed_tasks (task_table, FALSE); + e_cal_model_tasks_update_due_tasks (E_CAL_MODEL_TASKS (model)); + + return TRUE; +} + +static void +task_shell_view_backend_error_cb (EClientCache *client_cache, + EClient *client, + EAlert *alert, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ESource *source; + const gchar *extension_name; + + task_shell_content = task_shell_view->priv->task_shell_content; + + source = e_client_get_source (client); + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + + /* Only submit alerts from task list backends. */ + if (e_source_has_extension (source, extension_name)) { + EAlertSink *alert_sink; + + alert_sink = E_ALERT_SINK (task_shell_content); + e_alert_sink_submit_alert (alert_sink, alert); + } +} + +static void +task_shell_view_notify_view_id_cb (EShellView *shell_view) +{ + GalViewInstance *view_instance; + const gchar *view_id; + + view_id = e_shell_view_get_view_id (shell_view); + view_instance = e_shell_view_get_view_instance (shell_view); + + /* A NULL view ID implies we're in a custom view. But you can + * only get to a custom view via the "Define Views" dialog, which + * would have already modified the view instance appropriately. + * Furthermore, there's no way to refer to a custom view by ID + * anyway, since custom views have no IDs. */ + if (view_id == NULL) + return; + + gal_view_instance_set_current_view_id (view_instance, view_id); +} + +void +e_task_shell_view_private_init (ETaskShellView *task_shell_view) +{ + g_signal_connect ( + task_shell_view, "notify::view-id", + G_CALLBACK (task_shell_view_notify_view_id_cb), NULL); +} + +void +e_task_shell_view_private_constructed (ETaskShellView *task_shell_view) +{ + ETaskShellViewPrivate *priv = task_shell_view->priv; + EShellBackend *shell_backend; + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + EShellView *shell_view; + EShell *shell; + gulong handler_id; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + e_shell_window_add_action_group (shell_window, "tasks"); + e_shell_window_add_action_group (shell_window, "tasks-filter"); + + /* Cache these to avoid lots of awkward casting. */ + priv->task_shell_backend = g_object_ref (shell_backend); + priv->task_shell_content = g_object_ref (shell_content); + priv->task_shell_sidebar = g_object_ref (shell_sidebar); + + priv->settings = g_settings_new ("org.gnome.evolution.calendar"); + + handler_id = g_signal_connect_object ( + priv->task_shell_sidebar, "client-added", + G_CALLBACK (task_shell_view_selector_client_added_cb), + task_shell_view, G_CONNECT_SWAPPED); + priv->client_added_handler_id = handler_id; + + handler_id = g_signal_connect_object ( + priv->task_shell_sidebar, "client-removed", + G_CALLBACK (task_shell_view_selector_client_removed_cb), + task_shell_view, G_CONNECT_SWAPPED); + priv->client_removed_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->client_cache = e_shell_get_client_cache (shell); + g_object_ref (priv->client_cache); + + handler_id = g_signal_connect ( + priv->client_cache, "backend-error", + G_CALLBACK (task_shell_view_backend_error_cb), + task_shell_view); + priv->backend_error_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->task_table = e_task_shell_content_get_task_table ( + E_TASK_SHELL_CONTENT (shell_content)); + g_object_ref (priv->task_table); + + handler_id = g_signal_connect_swapped ( + priv->task_table, "open-component", + G_CALLBACK (e_task_shell_view_open_task), + task_shell_view); + priv->open_component_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "popup-event", + G_CALLBACK (task_shell_view_table_popup_event_cb), + task_shell_view); + priv->popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "selection-change", + G_CALLBACK (e_task_shell_view_update_sidebar), + task_shell_view); + priv->selection_change_1_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "selection-change", + G_CALLBACK (e_shell_view_update_actions), + task_shell_view); + priv->selection_change_2_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->task_table, "status-message", + G_CALLBACK (e_task_shell_view_set_status_message), + task_shell_view); + priv->status_message_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->model = e_task_table_get_model (priv->task_table); + g_object_ref (priv->model); + + handler_id = g_signal_connect_swapped ( + priv->model, "model-changed", + G_CALLBACK (e_task_shell_view_update_sidebar), + task_shell_view); + priv->model_changed_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "model-rows-deleted", + G_CALLBACK (e_task_shell_view_update_sidebar), + task_shell_view); + priv->model_rows_deleted_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "model-rows-inserted", + G_CALLBACK (e_task_shell_view_update_sidebar), + task_shell_view); + priv->model_rows_inserted_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->model, "row-appended", + G_CALLBACK (task_shell_view_model_row_appended_cb), + task_shell_view); + priv->rows_appended_handler_id = handler_id; + + /* Keep our own reference to this so we can + * disconnect our signal handlers in dispose(). */ + priv->selector = e_task_shell_sidebar_get_selector ( + E_TASK_SHELL_SIDEBAR (shell_sidebar)); + g_object_ref (priv->selector); + + handler_id = g_signal_connect_swapped ( + priv->selector, "popup-event", + G_CALLBACK (task_shell_view_selector_popup_event_cb), + task_shell_view); + priv->selector_popup_event_handler_id = handler_id; + + handler_id = g_signal_connect_swapped ( + priv->selector, "primary-selection-changed", + G_CALLBACK (e_shell_view_update_actions), + task_shell_view); + priv->primary_selection_changed_handler_id = handler_id; + + e_categories_add_change_hook ( + (GHookFunc) e_task_shell_view_update_search_filter, + task_shell_view); + + /* Listen for configuration changes. */ + g_settings_bind ( + priv->settings, "confirm-purge", + shell_view, "confirm-purge", + G_SETTINGS_BIND_DEFAULT); + + /* Keep the ECalModel in sync with the sidebar. */ + g_object_bind_property ( + shell_sidebar, "default-client", + priv->model, "default-client", + G_BINDING_SYNC_CREATE); + + /* Hide Completed Tasks (enable/units/value) */ + handler_id = g_signal_connect ( + priv->settings, "changed::hide-completed-tasks", + G_CALLBACK (task_shell_view_hide_completed_tasks_changed_cb), + task_shell_view); + priv->settings_hide_completed_tasks_handler_id = handler_id; + handler_id = g_signal_connect ( + priv->settings, "changed::hide-completed-tasks-units", + G_CALLBACK (task_shell_view_hide_completed_tasks_changed_cb), + task_shell_view); + priv->settings_hide_completed_tasks_units_handler_id = handler_id; + handler_id = g_signal_connect ( + priv->settings, "changed::hide-completed-tasks-value", + G_CALLBACK (task_shell_view_hide_completed_tasks_changed_cb), + task_shell_view); + priv->settings_hide_completed_tasks_value_handler_id = handler_id; + + e_task_shell_view_actions_init (task_shell_view); + e_task_shell_view_update_sidebar (task_shell_view); + e_task_shell_view_update_search_filter (task_shell_view); + + /* Call this when everything is ready, like actions in + * action groups and such. */ + task_shell_view_update_timeout_cb (task_shell_view); + priv->update_timeout = g_timeout_add_full ( + G_PRIORITY_LOW, 60000, (GSourceFunc) + task_shell_view_update_timeout_cb, + task_shell_view, NULL); +} + +void +e_task_shell_view_private_dispose (ETaskShellView *task_shell_view) +{ + ETaskShellViewPrivate *priv = task_shell_view->priv; + + if (priv->client_added_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_shell_sidebar, + priv->client_added_handler_id); + priv->client_added_handler_id = 0; + } + + if (priv->client_removed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_shell_sidebar, + priv->client_removed_handler_id); + priv->client_removed_handler_id = 0; + } + + if (priv->backend_error_handler_id > 0) { + g_signal_handler_disconnect ( + priv->client_cache, + priv->backend_error_handler_id); + priv->backend_error_handler_id = 0; + } + + if (priv->open_component_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->open_component_handler_id); + priv->open_component_handler_id = 0; + } + + if (priv->popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->popup_event_handler_id); + priv->popup_event_handler_id = 0; + } + + if (priv->selection_change_1_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->selection_change_1_handler_id); + priv->selection_change_1_handler_id = 0; + } + + if (priv->selection_change_2_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->selection_change_2_handler_id); + priv->selection_change_2_handler_id = 0; + } + + if (priv->status_message_handler_id > 0) { + g_signal_handler_disconnect ( + priv->task_table, + priv->status_message_handler_id); + priv->status_message_handler_id = 0; + } + + if (priv->model_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_changed_handler_id); + priv->model_changed_handler_id = 0; + } + + if (priv->model_rows_deleted_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_rows_deleted_handler_id); + priv->model_rows_deleted_handler_id = 0; + } + + if (priv->model_rows_inserted_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->model_rows_inserted_handler_id); + priv->model_rows_inserted_handler_id = 0; + } + + if (priv->rows_appended_handler_id > 0) { + g_signal_handler_disconnect ( + priv->model, + priv->rows_appended_handler_id); + priv->rows_appended_handler_id = 0; + } + + if (priv->selector_popup_event_handler_id > 0) { + g_signal_handler_disconnect ( + priv->selector, + priv->selector_popup_event_handler_id); + priv->selector_popup_event_handler_id = 0; + } + + if (priv->primary_selection_changed_handler_id > 0) { + g_signal_handler_disconnect ( + priv->selector, + priv->primary_selection_changed_handler_id); + priv->primary_selection_changed_handler_id = 0; + } + + if (priv->settings_hide_completed_tasks_handler_id > 0) { + g_signal_handler_disconnect ( + priv->settings, + priv->settings_hide_completed_tasks_handler_id); + priv->settings_hide_completed_tasks_handler_id = 0; + } + + if (priv->settings_hide_completed_tasks_units_handler_id > 0) { + g_signal_handler_disconnect ( + priv->settings, + priv->settings_hide_completed_tasks_units_handler_id); + priv->settings_hide_completed_tasks_units_handler_id = 0; + } + + if (priv->settings_hide_completed_tasks_value_handler_id > 0) { + g_signal_handler_disconnect ( + priv->settings, + priv->settings_hide_completed_tasks_value_handler_id); + priv->settings_hide_completed_tasks_units_handler_id = 0; + } + + g_clear_object (&priv->task_shell_backend); + g_clear_object (&priv->task_shell_content); + g_clear_object (&priv->task_shell_sidebar); + + g_clear_object (&priv->client_cache); + g_clear_object (&priv->task_table); + g_clear_object (&priv->model); + g_clear_object (&priv->selector); + g_clear_object (&priv->settings); + + if (task_shell_view->priv->activity != NULL) { + /* XXX Activity is not cancellable. */ + e_activity_set_state ( + task_shell_view->priv->activity, + E_ACTIVITY_COMPLETED); + g_object_unref (task_shell_view->priv->activity); + task_shell_view->priv->activity = NULL; + } + + if (priv->update_timeout > 0) { + g_source_remove (priv->update_timeout); + priv->update_timeout = 0; + } + + if (priv->update_completed_timeout > 0) { + g_source_remove (priv->update_completed_timeout); + priv->update_completed_timeout = 0; + } +} + +void +e_task_shell_view_private_finalize (ETaskShellView *task_shell_view) +{ + /* XXX Nothing to do? */ +} + +void +e_task_shell_view_open_task (ETaskShellView *task_shell_view, + ECalModelComponent *comp_data) +{ + EShell *shell; + EShellView *shell_view; + EShellWindow *shell_window; + ESourceRegistry *registry; + CompEditor *editor; + CompEditorFlags flags = 0; + ECalComponent *comp; + icalcomponent *clone; + icalproperty *prop; + const gchar *uid; + + g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view)); + g_return_if_fail (E_IS_CAL_MODEL_COMPONENT (comp_data)); + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + registry = e_shell_get_registry (shell); + + uid = icalcomponent_get_uid (comp_data->icalcomp); + editor = comp_editor_find_instance (uid); + + if (editor != NULL) + goto exit; + + comp = e_cal_component_new (); + clone = icalcomponent_new_clone (comp_data->icalcomp); + e_cal_component_set_icalcomponent (comp, clone); + + prop = icalcomponent_get_first_property ( + comp_data->icalcomp, ICAL_ATTENDEE_PROPERTY); + if (prop != NULL) + flags |= COMP_EDITOR_IS_ASSIGNED; + + if (itip_organizer_is_user (registry, comp, comp_data->client)) + flags |= COMP_EDITOR_USER_ORG; + + if (!e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + + editor = task_editor_new (comp_data->client, shell, flags); + comp_editor_edit_comp (editor, comp); + + g_object_unref (comp); + + if (flags & COMP_EDITOR_IS_ASSIGNED) + task_editor_show_assignment (TASK_EDITOR (editor)); + +exit: + gtk_window_present (GTK_WINDOW (editor)); +} + +void +e_task_shell_view_delete_completed (ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + ECalModel *model; + GList *list, *link; + const gchar *sexp; + + g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view)); + + sexp = "(is-completed?)"; + + task_shell_content = task_shell_view->priv->task_shell_content; + model = e_task_shell_content_get_task_model (task_shell_content); + + e_task_shell_view_set_status_message ( + task_shell_view, _("Expunging"), -1.0); + + list = e_cal_model_list_clients (model); + + for (link = list; link != NULL; link = g_list_next (link)) { + ECalClient *client = E_CAL_CLIENT (link->data); + GSList *objects, *obj; + GError *error = NULL; + + if (e_client_is_readonly (E_CLIENT (client))) + continue; + + e_cal_client_get_object_list_sync ( + client, sexp, &objects, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to get object list: %s", + G_STRFUNC, error->message); + g_clear_error (&error); + continue; + } + + for (obj = objects; obj != NULL; obj = obj->next) { + icalcomponent *component = obj->data; + const gchar *uid; + + uid = icalcomponent_get_uid (component); + + e_cal_client_remove_object_sync ( + client, uid, NULL, + CALOBJ_MOD_THIS, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to remove object: %s", + G_STRFUNC, error->message); + g_clear_error (&error); + } + } + + e_cal_client_free_icalcomp_slist (objects); + } + + g_list_free_full (list, (GDestroyNotify) g_object_unref); + + e_task_shell_view_set_status_message (task_shell_view, NULL, -1.0); +} + +void +e_task_shell_view_set_status_message (ETaskShellView *task_shell_view, + const gchar *status_message, + gdouble percent) +{ + EActivity *activity; + EShellView *shell_view; + EShellBackend *shell_backend; + + g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view)); + + activity = task_shell_view->priv->activity; + shell_view = E_SHELL_VIEW (task_shell_view); + shell_backend = e_shell_view_get_shell_backend (shell_view); + + if (status_message == NULL || *status_message == '\0') { + if (activity != NULL) { + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_object_unref (activity); + activity = NULL; + } + + } else if (activity == NULL) { + activity = e_activity_new (); + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + e_shell_backend_add_activity (shell_backend, activity); + + } else { + e_activity_set_percent (activity, percent); + e_activity_set_text (activity, status_message); + } + + task_shell_view->priv->activity = activity; +} + +void +e_task_shell_view_update_sidebar (ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EShellView *shell_view; + EShellSidebar *shell_sidebar; + ETaskTable *task_table; + ECalModel *model; + GString *string; + const gchar *format; + gint n_rows; + gint n_selected; + + shell_view = E_SHELL_VIEW (task_shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + + task_shell_content = task_shell_view->priv->task_shell_content; + task_table = e_task_shell_content_get_task_table (task_shell_content); + + model = e_task_table_get_model (task_table); + + n_rows = e_table_model_row_count (E_TABLE_MODEL (model)); + n_selected = e_table_selected_count (E_TABLE (task_table)); + + string = g_string_sized_new (64); + + format = ngettext ("%d task", "%d tasks", n_rows); + g_string_append_printf (string, format, n_rows); + + if (n_selected > 0) { + format = _("%d selected"); + g_string_append_len (string, ", ", 2); + g_string_append_printf (string, format, n_selected); + } + + e_shell_sidebar_set_secondary_text (shell_sidebar, string->str); + + g_string_free (string, TRUE); +} + diff --git a/modules/calendar/e-task-shell-view-private.h b/modules/calendar/e-task-shell-view-private.h new file mode 100644 index 0000000000..71772f55bb --- /dev/null +++ b/modules/calendar/e-task-shell-view-private.h @@ -0,0 +1,155 @@ +/* + * e-task-shell-view-private.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_VIEW_PRIVATE_H +#define E_TASK_SHELL_VIEW_PRIVATE_H + +#include "e-task-shell-view.h" + +#include <string.h> +#include <glib/gi18n.h> +#include <libecal/libecal.h> + +#include "shell/e-shell-utils.h" + +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" +#include "calendar/gui/e-cal-model-tasks.h" +#include "calendar/gui/e-calendar-selector.h" +#include "calendar/gui/print.h" +#include "calendar/gui/dialogs/copy-source-dialog.h" +#include "calendar/gui/dialogs/task-editor.h" + +#include "e-task-shell-backend.h" +#include "e-task-shell-content.h" +#include "e-task-shell-sidebar.h" +#include "e-task-shell-view-actions.h" + +#define E_TASK_SHELL_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewPrivate)) + +/* Shorthand, requires a variable named "shell_window". */ +#define ACTION(name) \ + (E_SHELL_WINDOW_ACTION_##name (shell_window)) +#define ACTION_GROUP(name) \ + (E_SHELL_WINDOW_ACTION_GROUP_##name (shell_window)) + +/* ETable Specifications */ +#define ETSPEC_FILENAME "e-calendar-table.etspec" + +G_BEGIN_DECLS + +/* Filter items are displayed in ascending order. + * Non-negative values are reserved for categories. */ +enum { + TASK_FILTER_ANY_CATEGORY = -7, + TASK_FILTER_UNMATCHED = -6, + TASK_FILTER_NEXT_7_DAYS_TASKS = -5, + TASK_FILTER_ACTIVE_TASKS = -4, + TASK_FILTER_OVERDUE_TASKS = -3, + TASK_FILTER_COMPLETED_TASKS = -2, + TASK_FILTER_TASKS_WITH_ATTACHMENTS = -1 +}; + +/* Search items are displayed in ascending order. */ +enum { + TASK_SEARCH_ADVANCED = -1, + TASK_SEARCH_SUMMARY_CONTAINS, + TASK_SEARCH_DESCRIPTION_CONTAINS, + TASK_SEARCH_ANY_FIELD_CONTAINS +}; + +struct _ETaskShellViewPrivate { + + /* These are just for convenience. */ + ETaskShellBackend *task_shell_backend; + ETaskShellContent *task_shell_content; + ETaskShellSidebar *task_shell_sidebar; + + /* sidebar signal handlers */ + gulong client_added_handler_id; + gulong client_removed_handler_id; + + EClientCache *client_cache; + gulong backend_error_handler_id; + + ETaskTable *task_table; + gulong open_component_handler_id; + gulong popup_event_handler_id; + gulong selection_change_1_handler_id; + gulong selection_change_2_handler_id; + gulong status_message_handler_id; + + ECalModel *model; + gulong model_changed_handler_id; + gulong model_rows_deleted_handler_id; + gulong model_rows_inserted_handler_id; + gulong rows_appended_handler_id; + + ESourceSelector *selector; + gulong selector_popup_event_handler_id; + gulong primary_selection_changed_handler_id; + + /* org.gnome.evolution.calendar */ + GSettings *settings; + gulong settings_hide_completed_tasks_handler_id; + gulong settings_hide_completed_tasks_units_handler_id; + gulong settings_hide_completed_tasks_value_handler_id; + + EActivity *activity; + guint update_timeout; + guint update_completed_timeout; + + guint confirm_purge : 1; +}; + +void e_task_shell_view_private_init + (ETaskShellView *task_shell_view); +void e_task_shell_view_private_constructed + (ETaskShellView *task_shell_view); +void e_task_shell_view_private_dispose + (ETaskShellView *task_shell_view); +void e_task_shell_view_private_finalize + (ETaskShellView *task_shell_view); + +/* Private Utilities */ + +void e_task_shell_view_actions_init + (ETaskShellView *task_shell_view); +void e_task_shell_view_open_task + (ETaskShellView *task_shell_view, + ECalModelComponent *comp_data); +void e_task_shell_view_delete_completed + (ETaskShellView *task_shell_view); +void e_task_shell_view_set_status_message + (ETaskShellView *task_shell_view, + const gchar *status_message, + gdouble percent); +void e_task_shell_view_update_sidebar + (ETaskShellView *task_shell_view); +void e_task_shell_view_update_search_filter + (ETaskShellView *task_shell_view); + +G_END_DECLS + +#endif /* E_TASK_SHELL_VIEW_PRIVATE_H */ diff --git a/modules/calendar/e-task-shell-view.c b/modules/calendar/e-task-shell-view.c new file mode 100644 index 0000000000..14dbfd5b24 --- /dev/null +++ b/modules/calendar/e-task-shell-view.c @@ -0,0 +1,530 @@ +/* + * e-task-shell-view.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-task-shell-view-private.h" + +enum { + PROP_0, + PROP_CONFIRM_PURGE +}; + +G_DEFINE_DYNAMIC_TYPE ( + ETaskShellView, + e_task_shell_view, + E_TYPE_SHELL_VIEW) + +static void +task_shell_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CONFIRM_PURGE: + e_task_shell_view_set_confirm_purge ( + E_TASK_SHELL_VIEW (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +task_shell_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CONFIRM_PURGE: + g_value_set_boolean ( + value, e_task_shell_view_get_confirm_purge ( + E_TASK_SHELL_VIEW (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +task_shell_view_dispose (GObject *object) +{ + e_task_shell_view_private_dispose (E_TASK_SHELL_VIEW (object)); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_task_shell_view_parent_class)->dispose (object); +} + +static void +task_shell_view_finalize (GObject *object) +{ + e_task_shell_view_private_finalize (E_TASK_SHELL_VIEW (object)); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_task_shell_view_parent_class)->finalize (object); +} + +static void +task_shell_view_constructed (GObject *object) +{ + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_task_shell_view_parent_class)->constructed (object); + + e_task_shell_view_private_constructed (E_TASK_SHELL_VIEW (object)); +} + +static void +task_shell_view_execute_search (EShellView *shell_view) +{ + ETaskShellContent *task_shell_content; + EShellWindow *shell_window; + EShellContent *shell_content; + EShellSearchbar *searchbar; + EActionComboBox *combo_box; + GtkRadioAction *action; + ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; + ETaskTable *task_table; + EWebView *web_view; + ECalModel *model; + icaltimezone *timezone; + struct icaltimetype current_time; + time_t start_range; + time_t end_range; + time_t now_time; + gchar *start, *end; + gchar *query; + gchar *temp; + gint value; + + shell_window = e_shell_view_get_shell_window (shell_view); + shell_content = e_shell_view_get_shell_content (shell_view); + + task_shell_content = E_TASK_SHELL_CONTENT (shell_content); + searchbar = e_task_shell_content_get_searchbar (task_shell_content); + + task_shell_content = E_TASK_SHELL_CONTENT (shell_content); + task_table = e_task_shell_content_get_task_table (task_shell_content); + model = e_task_table_get_model (task_table); + timezone = e_cal_model_get_timezone (model); + current_time = icaltime_current_time_with_zone (timezone); + now_time = time_day_begin (icaltime_as_timet (current_time)); + + action = GTK_RADIO_ACTION (ACTION (TASK_SEARCH_ANY_FIELD_CONTAINS)); + value = gtk_radio_action_get_current_value (action); + + if (value == TASK_SEARCH_ADVANCED) { + query = e_shell_view_get_search_query (shell_view); + + if (!query) + query = g_strdup (""); + } else { + const gchar *format; + const gchar *text; + GString *string; + + text = e_shell_searchbar_get_search_text (searchbar); + + if (text == NULL || *text == '\0') { + text = ""; + value = TASK_SEARCH_SUMMARY_CONTAINS; + } + + switch (value) { + default: + text = ""; + /* fall through */ + + case TASK_SEARCH_SUMMARY_CONTAINS: + format = "(contains? \"summary\" %s)"; + break; + + case TASK_SEARCH_DESCRIPTION_CONTAINS: + format = "(contains? \"description\" %s)"; + break; + + case TASK_SEARCH_ANY_FIELD_CONTAINS: + format = "(contains? \"any\" %s)"; + break; + } + + /* Build the query. */ + string = g_string_new (""); + e_sexp_encode_string (string, text); + query = g_strdup_printf (format, string->str); + g_string_free (string, TRUE); + } + + /* Apply selected filter. */ + combo_box = e_shell_searchbar_get_filter_combo_box (searchbar); + value = e_action_combo_box_get_current_value (combo_box); + switch (value) { + case TASK_FILTER_ANY_CATEGORY: + break; + + case TASK_FILTER_UNMATCHED: + temp = g_strdup_printf ( + "(and (has-categories? #f) %s)", query); + g_free (query); + query = temp; + break; + + case TASK_FILTER_NEXT_7_DAYS_TASKS: + start_range = now_time; + end_range = time_day_end (time_add_day (start_range, 7)); + start = isodate_from_time_t (start_range); + end = isodate_from_time_t (end_range); + + temp = g_strdup_printf ( + "(and %s (due-in-time-range? " + "(make-time \"%s\") (make-time \"%s\")))", + query, start, end); + g_free (query); + query = temp; + break; + + case TASK_FILTER_ACTIVE_TASKS: + start_range = now_time; + end_range = time_day_end (time_add_day (start_range, 365)); + start = isodate_from_time_t (start_range); + end = isodate_from_time_t (end_range); + + temp = g_strdup_printf ( + "(and %s (due-in-time-range? " + "(make-time \"%s\") (make-time \"%s\")) " + "(not (is-completed?)))", + query, start, end); + g_free (query); + query = temp; + break; + + case TASK_FILTER_OVERDUE_TASKS: + start_range = 0; + end_range = time_day_end (now_time); + start = isodate_from_time_t (start_range); + end = isodate_from_time_t (end_range); + + temp = g_strdup_printf ( + "(and %s (due-in-time-range? " + "(make-time \"%s\") (make-time \"%s\")) " + "(not (is-completed?)))", + query, start, end); + g_free (query); + query = temp; + break; + + case TASK_FILTER_COMPLETED_TASKS: + temp = g_strdup_printf ( + "(and (is-completed?) %s)", query); + g_free (query); + query = temp; + break; + + case TASK_FILTER_TASKS_WITH_ATTACHMENTS: + temp = g_strdup_printf ( + "(and (has-attachments?) %s)", query); + g_free (query); + query = temp; + break; + + default: + { + GList *categories; + const gchar *category_name; + + categories = e_util_get_searchable_categories (); + category_name = g_list_nth_data (categories, value); + g_list_free (categories); + + temp = g_strdup_printf ( + "(and (has-categories? \"%s\") %s)", + category_name, query); + g_free (query); + query = temp; + break; + } + } + + /* Honor the user's preference to hide completed tasks. */ + temp = calendar_config_get_hide_completed_tasks_sexp (FALSE); + if (temp != NULL) { + gchar *temp2; + + temp2 = g_strdup_printf ("(and %s %s)", temp, query); + g_free (query); + g_free (temp); + query = temp2; + } + + /* Submit the query. */ + e_cal_model_set_search_query (model, query); + g_free (query); + + preview_pane = + e_task_shell_content_get_preview_pane (task_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); + e_cal_component_preview_clear (task_preview); +} + +static void +task_shell_view_update_actions (EShellView *shell_view) +{ + EShellContent *shell_content; + EShellSidebar *shell_sidebar; + EShellWindow *shell_window; + GtkAction *action; + const gchar *label; + gboolean sensitive; + guint32 state; + + /* Be descriptive. */ + gboolean any_tasks_selected; + gboolean has_primary_source; + gboolean multiple_tasks_selected; + gboolean primary_source_is_writable; + gboolean primary_source_is_removable; + gboolean primary_source_is_remote_deletable; + gboolean primary_source_in_collection; + gboolean selection_has_url; + gboolean selection_is_assignable; + gboolean single_task_selected; + gboolean some_tasks_complete; + gboolean some_tasks_incomplete; + gboolean sources_are_editable; + gboolean refresh_supported; + + /* Chain up to parent's update_actions() method. */ + E_SHELL_VIEW_CLASS (e_task_shell_view_parent_class)-> + update_actions (shell_view); + + shell_window = e_shell_view_get_shell_window (shell_view); + + shell_content = e_shell_view_get_shell_content (shell_view); + state = e_shell_content_check_state (shell_content); + + single_task_selected = + (state & E_TASK_SHELL_CONTENT_SELECTION_SINGLE); + multiple_tasks_selected = + (state & E_TASK_SHELL_CONTENT_SELECTION_MULTIPLE); + selection_is_assignable = + (state & E_TASK_SHELL_CONTENT_SELECTION_CAN_ASSIGN); + sources_are_editable = + (state & E_TASK_SHELL_CONTENT_SELECTION_CAN_EDIT); + some_tasks_complete = + (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_COMPLETE); + some_tasks_incomplete = + (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE); + selection_has_url = + (state & E_TASK_SHELL_CONTENT_SELECTION_HAS_URL); + + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + state = e_shell_sidebar_check_state (shell_sidebar); + + has_primary_source = + (state & E_TASK_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE); + primary_source_is_writable = + (state & E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE); + primary_source_is_removable = + (state & E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE); + primary_source_is_remote_deletable = + (state & E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE); + primary_source_in_collection = + (state & E_TASK_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION); + refresh_supported = + (state & E_TASK_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH); + + any_tasks_selected = + (single_task_selected || multiple_tasks_selected); + + action = ACTION (TASK_ASSIGN); + sensitive = + single_task_selected && sources_are_editable && + selection_is_assignable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_DELETE); + sensitive = any_tasks_selected && sources_are_editable; + gtk_action_set_sensitive (action, sensitive); + if (multiple_tasks_selected) + label = _("Delete Tasks"); + else + label = _("Delete Task"); + gtk_action_set_label (action, label); + + action = ACTION (TASK_FIND); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_FORWARD); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_LIST_COPY); + sensitive = has_primary_source; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_LIST_DELETE); + sensitive = + primary_source_is_removable || + primary_source_is_remote_deletable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_LIST_PROPERTIES); + sensitive = primary_source_is_writable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_LIST_REFRESH); + sensitive = refresh_supported; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_LIST_RENAME); + sensitive = + primary_source_is_writable && + !primary_source_in_collection; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_MARK_COMPLETE); + sensitive = + any_tasks_selected && + sources_are_editable && + some_tasks_incomplete; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_MARK_INCOMPLETE); + sensitive = + any_tasks_selected && + sources_are_editable && + some_tasks_complete; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_OPEN); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_OPEN_URL); + sensitive = single_task_selected && selection_has_url; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_PRINT); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_PURGE); + sensitive = sources_are_editable; + gtk_action_set_sensitive (action, sensitive); + + action = ACTION (TASK_SAVE_AS); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); +} + +static void +e_task_shell_view_class_init (ETaskShellViewClass *class) +{ + GObjectClass *object_class; + EShellViewClass *shell_view_class; + + g_type_class_add_private (class, sizeof (ETaskShellViewPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = task_shell_view_set_property; + object_class->get_property = task_shell_view_get_property; + object_class->dispose = task_shell_view_dispose; + object_class->finalize = task_shell_view_finalize; + object_class->constructed = task_shell_view_constructed; + + shell_view_class = E_SHELL_VIEW_CLASS (class); + shell_view_class->label = _("Tasks"); + shell_view_class->icon_name = "evolution-tasks"; + shell_view_class->ui_definition = "evolution-tasks.ui"; + shell_view_class->ui_manager_id = "org.gnome.evolution.tasks"; + shell_view_class->search_options = "/task-search-options"; + shell_view_class->search_rules = "tasktypes.xml"; + shell_view_class->new_shell_content = e_task_shell_content_new; + shell_view_class->new_shell_sidebar = e_task_shell_sidebar_new; + shell_view_class->execute_search = task_shell_view_execute_search; + shell_view_class->update_actions = task_shell_view_update_actions; + + g_object_class_install_property ( + object_class, + PROP_CONFIRM_PURGE, + g_param_spec_boolean ( + "confirm-purge", + "Confirm Purge", + NULL, + TRUE, + G_PARAM_READWRITE)); + + /* Ensure the GalView types we need are registered. */ + g_type_ensure (GAL_TYPE_VIEW_ETABLE); +} + +static void +e_task_shell_view_class_finalize (ETaskShellViewClass *class) +{ +} + +static void +e_task_shell_view_init (ETaskShellView *task_shell_view) +{ + task_shell_view->priv = + E_TASK_SHELL_VIEW_GET_PRIVATE (task_shell_view); + + e_task_shell_view_private_init (task_shell_view); +} + +void +e_task_shell_view_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_task_shell_view_register_type (type_module); +} + +gboolean +e_task_shell_view_get_confirm_purge (ETaskShellView *task_shell_view) +{ + g_return_val_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view), FALSE); + + return task_shell_view->priv->confirm_purge; +} + +void +e_task_shell_view_set_confirm_purge (ETaskShellView *task_shell_view, + gboolean confirm_purge) +{ + g_return_if_fail (E_IS_TASK_SHELL_VIEW (task_shell_view)); + + if (task_shell_view->priv->confirm_purge == confirm_purge) + return; + + task_shell_view->priv->confirm_purge = confirm_purge; + + g_object_notify (G_OBJECT (task_shell_view), "confirm-purge"); +} diff --git a/modules/calendar/e-task-shell-view.h b/modules/calendar/e-task-shell-view.h new file mode 100644 index 0000000000..97f07edff7 --- /dev/null +++ b/modules/calendar/e-task-shell-view.h @@ -0,0 +1,71 @@ +/* + * e-task-shell-view.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_VIEW_H +#define E_TASK_SHELL_VIEW_H + +#include <shell/e-shell-view.h> + +/* Standard GObject macros */ +#define E_TYPE_TASK_SHELL_VIEW \ + (e_task_shell_view_get_type ()) +#define E_TASK_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellView)) +#define E_TASK_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewClass)) +#define E_IS_TASK_SHELL_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_TASK_SHELL_VIEW)) +#define E_IS_TASK_SHELL_VIEW_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_TASK_SHELL_VIEW)) +#define E_TASK_SHELL_VIEW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_TASK_SHELL_VIEW, ETaskShellViewClass)) + +G_BEGIN_DECLS + +typedef struct _ETaskShellView ETaskShellView; +typedef struct _ETaskShellViewClass ETaskShellViewClass; +typedef struct _ETaskShellViewPrivate ETaskShellViewPrivate; + +struct _ETaskShellView { + EShellView parent; + ETaskShellViewPrivate *priv; +}; + +struct _ETaskShellViewClass { + EShellViewClass parent_class; +}; + +GType e_task_shell_view_get_type (void); +void e_task_shell_view_type_register (GTypeModule *type_module); +gboolean e_task_shell_view_get_confirm_purge + (ETaskShellView *task_shell_view); +void e_task_shell_view_set_confirm_purge + (ETaskShellView *task_shell_view, + gboolean confirm_purge); + +G_END_DECLS + +#endif /* E_TASK_SHELL_VIEW_H */ diff --git a/modules/calendar/evolution-module-calendar.c b/modules/calendar/evolution-module-calendar.c new file mode 100644 index 0000000000..fabe410271 --- /dev/null +++ b/modules/calendar/evolution-module-calendar.c @@ -0,0 +1,83 @@ +/* + * evolution-module-calendar.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-cal-attachment-handler.h" + +#include "e-cal-config-hook.h" +#include "e-cal-event-hook.h" + +#include "e-cal-shell-backend.h" +#include "e-cal-shell-content.h" +#include "e-cal-shell-sidebar.h" +#include "e-cal-shell-view.h" + +#include "e-memo-shell-backend.h" +#include "e-memo-shell-content.h" +#include "e-memo-shell-sidebar.h" +#include "e-memo-shell-view.h" + +#include "e-task-shell-backend.h" +#include "e-task-shell-content.h" +#include "e-task-shell-sidebar.h" +#include "e-task-shell-view.h" + +#include "e-calendar-preferences.h" + +/* Module Entry Points */ +void e_module_load (GTypeModule *type_module); +void e_module_unload (GTypeModule *type_module); + +G_MODULE_EXPORT void +e_module_load (GTypeModule *type_module) +{ + /* Register dynamically loaded types. */ + + e_cal_attachment_handler_register_type (type_module); + + e_cal_config_hook_register_type (type_module); + e_cal_event_hook_register_type (type_module); + + e_cal_shell_view_type_register (type_module); + e_cal_shell_backend_type_register (type_module); + e_cal_shell_content_type_register (type_module); + e_cal_shell_sidebar_type_register (type_module); + + e_memo_shell_view_type_register (type_module); + e_memo_shell_backend_type_register (type_module); + e_memo_shell_content_type_register (type_module); + e_memo_shell_sidebar_type_register (type_module); + + e_task_shell_view_type_register (type_module); + e_task_shell_backend_type_register (type_module); + e_task_shell_content_type_register (type_module); + e_task_shell_sidebar_type_register (type_module); + + e_calendar_preferences_type_register (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} |