/* * e-attachment-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 * * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include "e-attachment-view.h" #include #include #include "e-attachment-dialog.h" #include "e-attachment-handler-image.h" #include "e-attachment-handler-sendto.h" #include "e-misc-utils.h" #include "e-selection.h" #include "e-ui-manager.h" enum { UPDATE_ACTIONS, LAST_SIGNAL }; /* Note: Do not use the info field. */ static GtkTargetEntry target_table[] = { { (gchar *) "_NETSCAPE_URL", 0, 0 } }; static const gchar *ui = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static gulong signals[LAST_SIGNAL]; G_DEFINE_INTERFACE ( EAttachmentView, e_attachment_view, GTK_TYPE_WIDGET) static void action_add_cb (GtkAction *action, EAttachmentView *view) { EAttachmentStore *store; gpointer parent; parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; store = e_attachment_view_get_store (view); e_attachment_store_run_load_dialog (store, parent); } static void action_cancel_cb (GtkAction *action, EAttachmentView *view) { EAttachment *attachment; GList *list; list = e_attachment_view_get_selected_attachments (view); g_return_if_fail (g_list_length (list) == 1); attachment = list->data; e_attachment_cancel (attachment); g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_hide_cb (GtkAction *action, EAttachmentView *view) { EAttachment *attachment; GList *list; list = e_attachment_view_get_selected_attachments (view); g_return_if_fail (g_list_length (list) == 1); attachment = list->data; e_attachment_set_shown (attachment, FALSE); g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_hide_all_cb (GtkAction *action, EAttachmentView *view) { EAttachmentStore *store; GList *list, *iter; store = e_attachment_view_get_store (view); list = e_attachment_store_get_attachments (store); for (iter = list; iter != NULL; iter = iter->next) { EAttachment *attachment; attachment = E_ATTACHMENT (iter->data); e_attachment_set_shown (attachment, FALSE); } g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_open_with_cb (GtkAction *action, EAttachmentView *view) { EAttachment *attachment; EAttachmentStore *store; GtkWidget *dialog; GtkTreePath *path; GtkTreeIter iter; GAppInfo *app_info = NULL; GFileInfo *file_info; GList *list; gpointer parent; const gchar *content_type; parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; list = e_attachment_view_get_selected_paths (view); g_return_if_fail (g_list_length (list) == 1); path = list->data; store = e_attachment_view_get_store (view); gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); gtk_tree_model_get ( GTK_TREE_MODEL (store), &iter, E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, &attachment, -1); g_return_if_fail (E_IS_ATTACHMENT (attachment)); file_info = e_attachment_ref_file_info (attachment); g_return_if_fail (file_info != NULL); content_type = g_file_info_get_content_type (file_info); dialog = gtk_app_chooser_dialog_new_for_content_type ( parent, 0, content_type); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { GtkAppChooser *app_chooser = GTK_APP_CHOOSER (dialog); app_info = gtk_app_chooser_get_app_info (app_chooser); } gtk_widget_destroy (dialog); if (app_info != NULL) { e_attachment_view_open_path (view, path, app_info); g_object_unref (app_info); } g_object_unref (file_info); g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL); g_list_free (list); } static void action_open_with_app_info_cb (GtkAction *action, EAttachmentView *view) { GAppInfo *app_info; GtkTreePath *path; GList *list; list = e_attachment_view_get_selected_paths (view); g_return_if_fail (g_list_length (list) == 1); path = list->data; app_info = g_object_get_data (G_OBJECT (action), "app-info"); g_return_if_fail (G_IS_APP_INFO (app_info)); e_attachment_view_open_path (view, path, app_info); g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL); g_list_free (list); } static void action_properties_cb (GtkAction *action, EAttachmentView *view) { EAttachment *attachment; GtkWidget *dialog; GList *list; gpointer parent; list = e_attachment_view_get_selected_attachments (view); g_return_if_fail (g_list_length (list) == 1); attachment = list->data; parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; dialog = e_attachment_dialog_new (parent, attachment); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_remove_cb (GtkAction *action, EAttachmentView *view) { e_attachment_view_remove_selected (view, FALSE); } static void action_save_all_cb (GtkAction *action, EAttachmentView *view) { EAttachmentStore *store; GList *list, *iter; GFile *destination; gpointer parent; store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; /* XXX We lose the previous selection. */ e_attachment_view_select_all (view); list = e_attachment_view_get_selected_attachments (view); e_attachment_view_unselect_all (view); destination = e_attachment_store_run_save_dialog ( store, list, parent); if (destination == NULL) goto exit; for (iter = list; iter != NULL; iter = iter->next) { EAttachment *attachment = iter->data; e_attachment_save_async ( attachment, destination, (GAsyncReadyCallback) e_attachment_save_handle_error, parent); } g_object_unref (destination); exit: g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_save_as_cb (GtkAction *action, EAttachmentView *view) { EAttachmentStore *store; GList *list, *iter; GFile *destination; gpointer parent; store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; list = e_attachment_view_get_selected_attachments (view); destination = e_attachment_store_run_save_dialog ( store, list, parent); if (destination == NULL) goto exit; for (iter = list; iter != NULL; iter = iter->next) { EAttachment *attachment = iter->data; e_attachment_save_async ( attachment, destination, (GAsyncReadyCallback) e_attachment_save_handle_error, parent); } g_object_unref (destination); exit: g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_show_cb (GtkAction *action, EAttachmentView *view) { EAttachment *attachment; GList *list; list = e_attachment_view_get_selected_attachments (view); g_return_if_fail (g_list_length (list) == 1); attachment = list->data; e_attachment_set_shown (attachment, TRUE); g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void action_show_all_cb (GtkAction *action, EAttachmentView *view) { EAttachmentStore *store; GList *list, *iter; store = e_attachment_view_get_store (view); list = e_attachment_store_get_attachments (store); for (iter = list; iter != NULL; iter = iter->next) { EAttachment *attachment; attachment = E_ATTACHMENT (iter->data); e_attachment_set_shown (attachment, TRUE); } g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static GtkActionEntry standard_entries[] = { { "cancel", GTK_STOCK_CANCEL, NULL, NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_cancel_cb) }, { "open-with", NULL, N_("Open With Other Application..."), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_open_with_cb) }, { "save-all", GTK_STOCK_SAVE_AS, N_("S_ave All"), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_save_all_cb) }, { "save-as", GTK_STOCK_SAVE_AS, NULL, NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_save_as_cb) }, /* Alternate "save-all" label, for when * the attachment store has one row. */ { "save-one", GTK_STOCK_SAVE_AS, NULL, NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_save_all_cb) }, }; static GtkActionEntry editable_entries[] = { { "add", GTK_STOCK_ADD, N_("A_dd Attachment..."), NULL, N_("Attach a file"), G_CALLBACK (action_add_cb) }, { "properties", GTK_STOCK_PROPERTIES, NULL, NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_properties_cb) }, { "remove", GTK_STOCK_REMOVE, NULL, NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_remove_cb) } }; static GtkActionEntry inline_entries[] = { { "hide", NULL, N_("_Hide"), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_hide_cb) }, { "hide-all", NULL, N_("Hid_e All"), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_hide_all_cb) }, { "show", NULL, N_("_View Inline"), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_show_cb) }, { "show-all", NULL, N_("Vie_w All Inline"), NULL, NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_show_all_cb) } }; static void attachment_view_netscape_url (EAttachmentView *view, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { static GdkAtom atom = GDK_NONE; EAttachmentStore *store; EAttachment *attachment; const gchar *data; gpointer parent; gchar *copied_data; gchar **strv; gint length; if (G_UNLIKELY (atom == GDK_NONE)) atom = gdk_atom_intern_static_string ("_NETSCAPE_URL"); if (gtk_selection_data_get_target (selection_data) != atom) return; g_signal_stop_emission_by_name (view, "drag-data-received"); /* _NETSCAPE_URL is represented as "URI\nTITLE" */ data = (const gchar *) gtk_selection_data_get_data (selection_data); length = gtk_selection_data_get_length (selection_data); copied_data = g_strndup (data, length); strv = g_strsplit (copied_data, "\n", 2); g_free (copied_data); store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; attachment = e_attachment_new_for_uri (strv[0]); e_attachment_store_add_attachment (store, attachment); e_attachment_load_async ( attachment, (GAsyncReadyCallback) e_attachment_load_handle_error, parent); g_object_unref (attachment); g_strfreev (strv); gtk_drag_finish (drag_context, TRUE, FALSE, time); } static void attachment_view_text_calendar (EAttachmentView *view, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { EAttachmentStore *store; EAttachment *attachment; CamelMimePart *mime_part; GdkAtom data_type; GdkAtom target; const gchar *data; gpointer parent; gchar *content_type; gint length; target = gtk_selection_data_get_target (selection_data); if (!e_targets_include_calendar (&target, 1)) return; g_signal_stop_emission_by_name (view, "drag-data-received"); data = (const gchar *) gtk_selection_data_get_data (selection_data); length = gtk_selection_data_get_length (selection_data); data_type = gtk_selection_data_get_data_type (selection_data); mime_part = camel_mime_part_new (); content_type = gdk_atom_name (data_type); camel_mime_part_set_content (mime_part, data, length, content_type); camel_mime_part_set_disposition (mime_part, "inline"); g_free (content_type); store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; attachment = e_attachment_new (); e_attachment_set_mime_part (attachment, mime_part); e_attachment_store_add_attachment (store, attachment); e_attachment_load_async ( attachment, (GAsyncReadyCallback) e_attachment_load_handle_error, parent); g_object_unref (attachment); g_object_unref (mime_part); gtk_drag_finish (drag_context, TRUE, FALSE, time); } static void attachment_view_text_x_vcard (EAttachmentView *view, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { EAttachmentStore *store; EAttachment *attachment; CamelMimePart *mime_part; GdkAtom data_type; GdkAtom target; const gchar *data; gpointer parent; gchar *content_type; gint length; target = gtk_selection_data_get_target (selection_data); if (!e_targets_include_directory (&target, 1)) return; g_signal_stop_emission_by_name (view, "drag-data-received"); data = (const gchar *) gtk_selection_data_get_data (selection_data); length = gtk_selection_data_get_length (selection_data); data_type = gtk_selection_data_get_data_type (selection_data); mime_part = camel_mime_part_new (); content_type = gdk_atom_name (data_type); camel_mime_part_set_content (mime_part, data, length, content_type); camel_mime_part_set_disposition (mime_part, "inline"); g_free (content_type); store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; attachment = e_attachment_new (); e_attachment_set_mime_part (attachment, mime_part); e_attachment_store_add_attachment (store, attachment); e_attachment_load_async ( attachment, (GAsyncReadyCallback) e_attachment_load_handle_error, parent); g_object_unref (attachment); g_object_unref (mime_part); gtk_drag_finish (drag_context, TRUE, FALSE, time); } static void attachment_view_uris (EAttachmentView *view, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { EAttachmentStore *store; gpointer parent; gchar **uris; gint ii; uris = gtk_selection_data_get_uris (selection_data); if (uris == NULL) return; g_signal_stop_emission_by_name (view, "drag-data-received"); store = e_attachment_view_get_store (view); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; for (ii = 0; uris[ii] != NULL; ii++) { EAttachment *attachment; attachment = e_attachment_new_for_uri (uris[ii]); e_attachment_store_add_attachment (store, attachment); e_attachment_load_async ( attachment, (GAsyncReadyCallback) e_attachment_load_handle_error, parent); g_object_unref (attachment); } g_strfreev (uris); gtk_drag_finish (drag_context, TRUE, FALSE, time); } static void attachment_view_update_actions (EAttachmentView *view) { EAttachmentViewPrivate *priv; EAttachment *attachment; EAttachmentStore *store; GtkActionGroup *action_group; GtkAction *action; GList *list, *iter; guint n_shown = 0; guint n_hidden = 0; guint n_selected; gboolean busy = FALSE; gboolean can_show = FALSE; gboolean shown = FALSE; gboolean visible; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); priv = e_attachment_view_get_private (view); store = e_attachment_view_get_store (view); list = e_attachment_store_get_attachments (store); for (iter = list; iter != NULL; iter = iter->next) { attachment = iter->data; if (!e_attachment_get_can_show (attachment)) continue; if (e_attachment_get_shown (attachment)) n_shown++; else n_hidden++; } g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); list = e_attachment_view_get_selected_attachments (view); n_selected = g_list_length (list); if (n_selected == 1) { attachment = g_object_ref (list->data); busy |= e_attachment_get_loading (attachment); busy |= e_attachment_get_saving (attachment); can_show = e_attachment_get_can_show (attachment); shown = e_attachment_get_shown (attachment); } else attachment = NULL; g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); action = e_attachment_view_get_action (view, "cancel"); gtk_action_set_visible (action, busy); action = e_attachment_view_get_action (view, "hide"); gtk_action_set_visible (action, can_show && shown); /* Show this action if there are multiple viewable * attachments, and at least one of them is shown. */ visible = (n_shown + n_hidden > 1) && (n_shown > 0); action = e_attachment_view_get_action (view, "hide-all"); gtk_action_set_visible (action, visible); action = e_attachment_view_get_action (view, "open-with"); gtk_action_set_visible (action, !busy && n_selected == 1); action = e_attachment_view_get_action (view, "properties"); gtk_action_set_visible (action, !busy && n_selected == 1); action = e_attachment_view_get_action (view, "remove"); gtk_action_set_visible (action, !busy && n_selected > 0); action = e_attachment_view_get_action (view, "save-as"); gtk_action_set_visible (action, !busy && n_selected > 0); action = e_attachment_view_get_action (view, "show"); gtk_action_set_visible (action, can_show && !shown); /* Show this action if there are multiple viewable * attachments, and at least one of them is hidden. */ visible = (n_shown + n_hidden > 1) && (n_hidden > 0); action = e_attachment_view_get_action (view, "show-all"); gtk_action_set_visible (action, visible); /* Clear out the "openwith" action group. */ gtk_ui_manager_remove_ui (priv->ui_manager, priv->merge_id); action_group = e_attachment_view_get_action_group (view, "openwith"); e_action_group_remove_all_actions (action_group); gtk_ui_manager_ensure_update (priv->ui_manager); if (attachment == NULL || busy) return; list = e_attachment_list_apps (attachment); for (iter = list; iter != NULL; iter = iter->next) { GAppInfo *app_info = iter->data; GtkAction *action; GIcon *app_icon; const gchar *app_executable; const gchar *app_name; gchar *action_tooltip; gchar *action_label; gchar *action_name; app_executable = g_app_info_get_executable (app_info); app_icon = g_app_info_get_icon (app_info); app_name = g_app_info_get_name (app_info); action_name = g_strdup_printf ("open-with-%s", app_executable); action_label = g_strdup_printf (_("Open With \"%s\""), app_name); action_tooltip = g_strdup_printf ( _("Open this attachment in %s"), app_name); action = gtk_action_new ( action_name, action_label, action_tooltip, NULL); gtk_action_set_gicon (action, app_icon); g_object_set_data_full ( G_OBJECT (action), "app-info", g_object_ref (app_info), (GDestroyNotify) g_object_unref); g_object_set_data_full ( G_OBJECT (action), "attachment", g_object_ref (attachment), (GDestroyNotify) g_object_unref); g_signal_connect ( action, "activate", G_CALLBACK (action_open_with_app_info_cb), view); gtk_action_group_add_action (action_group, action); gtk_ui_manager_add_ui ( priv->ui_manager, priv->merge_id, "/context/open-actions", action_name, action_name, GTK_UI_MANAGER_AUTO, FALSE); g_free (action_name); g_free (action_label); g_free (action_tooltip); } g_object_unref (attachment); g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } static void attachment_view_init_drag_dest (EAttachmentView *view) { EAttachmentViewPrivate *priv; GtkTargetList *target_list; priv = e_attachment_view_get_private (view); target_list = gtk_target_list_new ( target_table, G_N_ELEMENTS (target_table)); gtk_target_list_add_uri_targets (target_list, 0); e_target_list_add_calendar_targets (target_list, 0); e_target_list_add_directory_targets (target_list, 0); priv->target_list = target_list; priv->drag_actions = GDK_ACTION_COPY; } static void e_attachment_view_default_init (EAttachmentViewInterface *interface) { interface->update_actions = attachment_view_update_actions; g_object_interface_install_property ( interface, g_param_spec_boolean ( "dragging", "Dragging", NULL, FALSE, G_PARAM_READWRITE)); g_object_interface_install_property ( interface, g_param_spec_boolean ( "editable", "Editable", NULL, TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); signals[UPDATE_ACTIONS] = g_signal_new ( "update-actions", G_TYPE_FROM_INTERFACE (interface), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (EAttachmentViewInterface, update_actions), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /* Register known handler types. */ g_type_ensure (E_TYPE_ATTACHMENT_HANDLER_IMAGE); g_type_ensure (E_TYPE_ATTACHMENT_HANDLER_SENDTO); } void e_attachment_view_init (EAttachmentView *view) { EAttachmentViewPrivate *priv; GtkUIManager *ui_manager; GtkActionGroup *action_group; GError *error = NULL; priv = e_attachment_view_get_private (view); ui_manager = e_ui_manager_new (); priv->merge_id = gtk_ui_manager_new_merge_id (ui_manager); priv->ui_manager = ui_manager; action_group = e_attachment_view_add_action_group (view, "standard"); gtk_action_group_add_actions ( action_group, standard_entries, G_N_ELEMENTS (standard_entries), view); action_group = e_attachment_view_add_action_group (view, "editable"); g_object_bind_property ( view, "editable", action_group, "visible", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); gtk_action_group_add_actions ( action_group, editable_entries, G_N_ELEMENTS (editable_entries), view); action_group = e_attachment_view_add_action_group (view, "inline"); gtk_action_group_add_actions ( action_group, inline_entries, G_N_ELEMENTS (inline_entries), view); gtk_action_group_set_visible (action_group, FALSE); e_attachment_view_add_action_group (view, "openwith"); /* Because we are loading from a hard-coded string, there is * no chance of I/O errors. Failure here implies a malformed * UI definition. Full stop. */ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); if (error != NULL) g_error ("%s", error->message); attachment_view_init_drag_dest (view); e_attachment_view_drag_source_set (view); /* Connect built-in drag and drop handlers. */ g_signal_connect ( view, "drag-data-received", G_CALLBACK (attachment_view_netscape_url), NULL); g_signal_connect ( view, "drag-data-received", G_CALLBACK (attachment_view_text_calendar), NULL); g_signal_connect ( view, "drag-data-received", G_CALLBACK (attachment_view_text_x_vcard), NULL); g_signal_connect ( view, "drag-data-received", G_CALLBACK (attachment_view_uris), NULL); } void e_attachment_view_dispose (EAttachmentView *view) { EAttachmentViewPrivate *priv; priv = e_attachment_view_get_private (view); if (priv->target_list != NULL) { gtk_target_list_unref (priv->target_list); priv->target_list = NULL; } if (priv->ui_manager != NULL) { g_object_unref (priv->ui_manager); priv->ui_manager = NULL; } } void e_attachment_view_finalize (EAttachmentView *view) { EAttachmentViewPrivate *priv; priv = e_attachment_view_get_private (view); g_list_foreach (priv->event_list, (GFunc) gdk_event_free, NULL); g_list_free (priv->event_list); g_list_foreach (priv->selected, (GFunc) g_object_unref, NULL); g_list_free (priv->selected); } EAttachmentViewPrivate * e_attachment_view_get_private (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_val_if_fail (interface->get_private != NULL, NULL); return interface->get_private (view); } EAttachmentStore * e_attachment_view_get_store (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_val_if_fail (interface->get_store != NULL, NULL); return interface->get_store (view); } gboolean e_attachment_view_get_editable (EAttachmentView *view) { EAttachmentViewPrivate *priv; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); priv = e_attachment_view_get_private (view); return priv->editable; } void e_attachment_view_set_editable (EAttachmentView *view, gboolean editable) { EAttachmentViewPrivate *priv; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); priv = e_attachment_view_get_private (view); priv->editable = editable; if (editable) e_attachment_view_drag_dest_set (view); else e_attachment_view_drag_dest_unset (view); g_object_notify (G_OBJECT (view), "editable"); } gboolean e_attachment_view_get_dragging (EAttachmentView *view) { EAttachmentViewPrivate *priv; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); priv = e_attachment_view_get_private (view); return priv->dragging; } void e_attachment_view_set_dragging (EAttachmentView *view, gboolean dragging) { EAttachmentViewPrivate *priv; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); priv = e_attachment_view_get_private (view); priv->dragging = dragging; g_object_notify (G_OBJECT (view), "dragging"); } GtkTargetList * e_attachment_view_get_target_list (EAttachmentView *view) { EAttachmentViewPrivate *priv; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); priv = e_attachment_view_get_private (view); return priv->target_list; } GdkDragAction e_attachment_view_get_drag_actions (EAttachmentView *view) { EAttachmentViewPrivate *priv; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), 0); priv = e_attachment_view_get_private (view); return priv->drag_actions; } void e_attachment_view_add_drag_actions (EAttachmentView *view, GdkDragAction drag_actions) { EAttachmentViewPrivate *priv; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); priv = e_attachment_view_get_private (view); priv->drag_actions |= drag_actions; } GList * e_attachment_view_get_selected_attachments (EAttachmentView *view) { EAttachmentStore *store; GtkTreeModel *model; GList *list, *item; gint column_id; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; list = e_attachment_view_get_selected_paths (view); store = e_attachment_view_get_store (view); model = GTK_TREE_MODEL (store); /* Convert the GtkTreePaths to EAttachments. */ for (item = list; item != NULL; item = item->next) { EAttachment *attachment; GtkTreePath *path; GtkTreeIter iter; path = item->data; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, column_id, &attachment, -1); gtk_tree_path_free (path); item->data = attachment; } return list; } void e_attachment_view_open_path (EAttachmentView *view, GtkTreePath *path, GAppInfo *app_info) { EAttachmentStore *store; EAttachment *attachment; GtkTreeModel *model; GtkTreeIter iter; gpointer parent; gint column_id; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (path != NULL); column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; store = e_attachment_view_get_store (view); model = GTK_TREE_MODEL (store); g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path)); gtk_tree_model_get (model, &iter, column_id, &attachment, -1); parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; e_attachment_open_async ( attachment, app_info, (GAsyncReadyCallback) e_attachment_open_handle_error, parent); g_object_unref (attachment); } void e_attachment_view_remove_selected (EAttachmentView *view, gboolean select_next) { EAttachmentStore *store; GtkTreeModel *model; GList *list, *item; gint column_id; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; list = e_attachment_view_get_selected_paths (view); store = e_attachment_view_get_store (view); model = GTK_TREE_MODEL (store); /* Remove attachments in reverse order to avoid invalidating * tree paths as we iterate over the list. Note, the list is * probably already sorted but we sort again just to be safe. */ list = g_list_reverse (g_list_sort ( list, (GCompareFunc) gtk_tree_path_compare)); for (item = list; item != NULL; item = item->next) { EAttachment *attachment; GtkTreePath *path = item->data; GtkTreeIter iter; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, column_id, &attachment, -1); e_attachment_store_remove_attachment (store, attachment); g_object_unref (attachment); } /* If we only removed one attachment, try to select another. */ if (select_next && g_list_length (list) == 1) { GtkTreePath *path = list->data; e_attachment_view_select_path (view, path); if (!e_attachment_view_path_is_selected (view, path)) if (gtk_tree_path_prev (path)) e_attachment_view_select_path (view, path); } g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL); g_list_free (list); } gboolean e_attachment_view_button_press_event (EAttachmentView *view, GdkEventButton *event) { EAttachmentViewPrivate *priv; GtkTreePath *path; gboolean editable; gboolean handled = FALSE; gboolean path_is_selected = FALSE; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (event != NULL, FALSE); priv = e_attachment_view_get_private (view); if (g_list_find (priv->event_list, event) != NULL) return FALSE; if (priv->event_list != NULL) { /* Save the event to be propagated in order. */ priv->event_list = g_list_append ( priv->event_list, gdk_event_copy ((GdkEvent *) event)); return TRUE; } editable = e_attachment_view_get_editable (view); path = e_attachment_view_get_path_at_pos (view, event->x, event->y); path_is_selected = e_attachment_view_path_is_selected (view, path); if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { GList *list, *iter; gboolean busy = FALSE; list = e_attachment_view_get_selected_attachments (view); for (iter = list; iter != NULL; iter = iter->next) { EAttachment *attachment = iter->data; busy |= e_attachment_get_loading (attachment); busy |= e_attachment_get_saving (attachment); } /* Prepare for dragging if the clicked item is selected * and none of the selected items are loading or saving. */ if (path_is_selected && !busy) { priv->start_x = event->x; priv->start_y = event->y; priv->event_list = g_list_append ( priv->event_list, gdk_event_copy ((GdkEvent *) event)); handled = TRUE; } g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { /* If the user clicked on a selected item, retain the * current selection. If the user clicked on an unselected * item, select the clicked item only. If the user did not * click on an item, clear the current selection. */ if (path == NULL) e_attachment_view_unselect_all (view); else if (!path_is_selected) { e_attachment_view_unselect_all (view); e_attachment_view_select_path (view, path); } /* Non-editable attachment views should only show a * popup menu when right-clicking on an attachment, * but editable views can show the menu any time. */ if (path != NULL || editable) { e_attachment_view_show_popup_menu ( view, event, NULL, NULL); handled = TRUE; } } if (path != NULL) gtk_tree_path_free (path); return handled; } gboolean e_attachment_view_button_release_event (EAttachmentView *view, GdkEventButton *event) { EAttachmentViewPrivate *priv; GtkWidget *widget = GTK_WIDGET (view); GList *iter; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (event != NULL, FALSE); priv = e_attachment_view_get_private (view); for (iter = priv->event_list; iter != NULL; iter = iter->next) { GdkEvent *event = iter->data; gtk_propagate_event (widget, event); gdk_event_free (event); } g_list_free (priv->event_list); priv->event_list = NULL; return FALSE; } gboolean e_attachment_view_motion_notify_event (EAttachmentView *view, GdkEventMotion *event) { EAttachmentViewPrivate *priv; GtkWidget *widget = GTK_WIDGET (view); GtkTargetList *targets; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (event != NULL, FALSE); priv = e_attachment_view_get_private (view); if (priv->event_list == NULL) return FALSE; if (!gtk_drag_check_threshold ( widget, priv->start_x, priv->start_y, event->x, event->y)) return TRUE; g_list_foreach (priv->event_list, (GFunc) gdk_event_free, NULL); g_list_free (priv->event_list); priv->event_list = NULL; targets = gtk_drag_source_get_target_list (widget); gtk_drag_begin ( widget, targets, GDK_ACTION_COPY, 1, (GdkEvent *) event); return TRUE; } gboolean e_attachment_view_key_press_event (EAttachmentView *view, GdkEventKey *event) { gboolean editable; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (event != NULL, FALSE); editable = e_attachment_view_get_editable (view); if (event->keyval == GDK_KEY_Delete && editable) { e_attachment_view_remove_selected (view, TRUE); return TRUE; } return FALSE; } GtkTreePath * e_attachment_view_get_path_at_pos (EAttachmentView *view, gint x, gint y) { EAttachmentViewInterface *interface; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_val_if_fail (interface->get_path_at_pos != NULL, NULL); return interface->get_path_at_pos (view, x, y); } GList * e_attachment_view_get_selected_paths (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_val_if_fail (interface->get_selected_paths != NULL, NULL); return interface->get_selected_paths (view); } gboolean e_attachment_view_path_is_selected (EAttachmentView *view, GtkTreePath *path) { EAttachmentViewInterface *interface; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); /* Handle NULL paths gracefully. */ if (path == NULL) return FALSE; interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_val_if_fail (interface->path_is_selected != NULL, FALSE); return interface->path_is_selected (view, path); } void e_attachment_view_select_path (EAttachmentView *view, GtkTreePath *path) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (path != NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_if_fail (interface->select_path != NULL); interface->select_path (view, path); } void e_attachment_view_unselect_path (EAttachmentView *view, GtkTreePath *path) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (path != NULL); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_if_fail (interface->unselect_path != NULL); interface->unselect_path (view, path); } void e_attachment_view_select_all (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_if_fail (interface->select_all != NULL); interface->select_all (view); } void e_attachment_view_unselect_all (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); g_return_if_fail (interface->unselect_all != NULL); interface->unselect_all (view); } void e_attachment_view_sync_selection (EAttachmentView *view, EAttachmentView *target) { GList *list, *iter; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (E_IS_ATTACHMENT_VIEW (target)); list = e_attachment_view_get_selected_paths (view); e_attachment_view_unselect_all (target); for (iter = list; iter != NULL; iter = iter->next) e_attachment_view_select_path (target, iter->data); g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL); g_list_free (list); } void e_attachment_view_drag_source_set (EAttachmentView *view) { EAttachmentViewInterface *interface; GtkTargetEntry *targets; GtkTargetList *list; gint n_targets; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); if (interface->drag_source_set == NULL) return; list = gtk_target_list_new (NULL, 0); gtk_target_list_add_uri_targets (list, 0); targets = gtk_target_table_new_from_list (list, &n_targets); interface->drag_source_set ( view, GDK_BUTTON1_MASK, targets, n_targets, GDK_ACTION_COPY); gtk_target_table_free (targets, n_targets); gtk_target_list_unref (list); } void e_attachment_view_drag_source_unset (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); if (interface->drag_source_unset == NULL) return; interface->drag_source_unset (view); } void e_attachment_view_drag_begin (EAttachmentView *view, GdkDragContext *context) { EAttachmentViewPrivate *priv; guint n_selected; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); priv = e_attachment_view_get_private (view); e_attachment_view_set_dragging (view, TRUE); g_warn_if_fail (priv->selected == NULL); priv->selected = e_attachment_view_get_selected_attachments (view); n_selected = g_list_length (priv->selected); if (n_selected > 1) gtk_drag_set_icon_stock ( context, GTK_STOCK_DND_MULTIPLE, 0, 0); else if (n_selected == 1) { EAttachment *attachment; GtkIconTheme *icon_theme; GtkIconInfo *icon_info; GIcon *icon; gint width, height; attachment = E_ATTACHMENT (priv->selected->data); icon = e_attachment_ref_icon (attachment); g_return_if_fail (icon != NULL); icon_theme = gtk_icon_theme_get_default (); gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &width, &height); icon_info = gtk_icon_theme_lookup_by_gicon ( icon_theme, icon, MIN (width, height), GTK_ICON_LOOKUP_USE_BUILTIN); if (icon_info != NULL) { GdkPixbuf *pixbuf; GError *error = NULL; pixbuf = gtk_icon_info_load_icon (icon_info, &error); if (pixbuf != NULL) { gtk_drag_set_icon_pixbuf ( context, pixbuf, 0, 0); g_object_unref (pixbuf); } else if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } gtk_icon_info_free (icon_info); } g_object_unref (icon); } } void e_attachment_view_drag_end (EAttachmentView *view, GdkDragContext *context) { EAttachmentViewPrivate *priv; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); priv = e_attachment_view_get_private (view); e_attachment_view_set_dragging (view, FALSE); g_list_foreach (priv->selected, (GFunc) g_object_unref, NULL); g_list_free (priv->selected); priv->selected = NULL; } static void attachment_view_got_uris_cb (EAttachmentStore *store, GAsyncResult *result, gpointer user_data) { struct { gchar **uris; gboolean done; } *status = user_data; /* XXX Since this is a best-effort function, * should we care about errors? */ status->uris = e_attachment_store_get_uris_finish ( store, result, NULL); status->done = TRUE; } void e_attachment_view_drag_data_get (EAttachmentView *view, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time) { EAttachmentViewPrivate *priv; EAttachmentStore *store; struct { gchar **uris; gboolean done; } status; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); g_return_if_fail (selection != NULL); status.uris = NULL; status.done = FALSE; priv = e_attachment_view_get_private (view); store = e_attachment_view_get_store (view); if (priv->selected == NULL) return; e_attachment_store_get_uris_async ( store, priv->selected, (GAsyncReadyCallback) attachment_view_got_uris_cb, &status); /* We can't return until we have results, so crank * the main loop until the callback gets triggered. */ while (!status.done) if (gtk_main_iteration ()) break; if (status.uris != NULL) gtk_selection_data_set_uris (selection, status.uris); g_strfreev (status.uris); } void e_attachment_view_drag_dest_set (EAttachmentView *view) { EAttachmentViewPrivate *priv; EAttachmentViewInterface *interface; GtkTargetEntry *targets; gint n_targets; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); if (interface->drag_dest_set == NULL) return; priv = e_attachment_view_get_private (view); targets = gtk_target_table_new_from_list ( priv->target_list, &n_targets); interface->drag_dest_set ( view, targets, n_targets, priv->drag_actions); gtk_target_table_free (targets, n_targets); } void e_attachment_view_drag_dest_unset (EAttachmentView *view) { EAttachmentViewInterface *interface; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view); if (interface->drag_dest_unset == NULL) return; interface->drag_dest_unset (view); } gboolean e_attachment_view_drag_motion (EAttachmentView *view, GdkDragContext *context, gint x, gint y, guint time) { EAttachmentViewPrivate *priv; GdkDragAction actions; GdkDragAction chosen_action; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE); priv = e_attachment_view_get_private (view); /* Disallow drops if we're not editable. */ if (!e_attachment_view_get_editable (view)) return FALSE; /* Disallow drops if we initiated the drag. * This helps prevent duplicate attachments. */ if (e_attachment_view_get_dragging (view)) return FALSE; actions = gdk_drag_context_get_actions (context); actions &= priv->drag_actions; chosen_action = gdk_drag_context_get_suggested_action (context); if (chosen_action == GDK_ACTION_ASK) { GdkDragAction mask; mask = GDK_ACTION_COPY | GDK_ACTION_MOVE; if ((actions & mask) != mask) chosen_action = GDK_ACTION_COPY; } gdk_drag_status (context, chosen_action, time); return (chosen_action != 0); } gboolean e_attachment_view_drag_drop (EAttachmentView *view, GdkDragContext *context, gint x, gint y, guint time) { g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE); g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE); /* Disallow drops if we initiated the drag. * This helps prevent duplicate attachments. */ return !e_attachment_view_get_dragging (view); } void e_attachment_view_drag_data_received (EAttachmentView *view, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { GdkAtom atom; gchar *name; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_return_if_fail (GDK_IS_DRAG_CONTEXT (drag_context)); /* Drop handlers are supposed to stop further emission of the * "drag-data-received" signal if they can handle the data. If * we get this far it means none of the handlers were successful, * so report the drop as failed. */ atom = gtk_selection_data_get_target (selection_data); name = gdk_atom_name (atom); g_warning ("Unknown selection target: %s", name); g_free (name); gtk_drag_finish (drag_context, FALSE, FALSE, time); } GtkAction * e_attachment_view_get_action (EAttachmentView *view, const gchar *action_name) { GtkUIManager *ui_manager; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); g_return_val_if_fail (action_name != NULL, NULL); ui_manager = e_attachment_view_get_ui_manager (view); return e_lookup_action (ui_manager, action_name); } GtkActionGroup * e_attachment_view_add_action_group (EAttachmentView *view, const gchar *group_name) { GtkActionGroup *action_group; GtkUIManager *ui_manager; const gchar *domain; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); g_return_val_if_fail (group_name != NULL, NULL); ui_manager = e_attachment_view_get_ui_manager (view); domain = GETTEXT_PACKAGE; action_group = gtk_action_group_new (group_name); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); return action_group; } GtkActionGroup * e_attachment_view_get_action_group (EAttachmentView *view, const gchar *group_name) { GtkUIManager *ui_manager; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); g_return_val_if_fail (group_name != NULL, NULL); ui_manager = e_attachment_view_get_ui_manager (view); return e_lookup_action_group (ui_manager, group_name); } GtkWidget * e_attachment_view_get_popup_menu (EAttachmentView *view) { GtkUIManager *ui_manager; GtkWidget *menu; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); ui_manager = e_attachment_view_get_ui_manager (view); menu = gtk_ui_manager_get_widget (ui_manager, "/context"); g_return_val_if_fail (GTK_IS_MENU (menu), NULL); return menu; } GtkUIManager * e_attachment_view_get_ui_manager (EAttachmentView *view) { EAttachmentViewPrivate *priv; g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); priv = e_attachment_view_get_private (view); return priv->ui_manager; } void e_attachment_view_show_popup_menu (EAttachmentView *view, GdkEventButton *event, GtkMenuPositionFunc func, gpointer user_data) { GtkWidget *menu; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); e_attachment_view_update_actions (view); menu = e_attachment_view_get_popup_menu (view); if (event != NULL) gtk_menu_popup ( GTK_MENU (menu), NULL, NULL, func, user_data, event->button, event->time); else gtk_menu_popup ( GTK_MENU (menu), NULL, NULL, func, user_data, 0, gtk_get_current_event_time ()); } void e_attachment_view_update_actions (EAttachmentView *view) { g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); g_signal_emit (view, signals[UPDATE_ACTIONS], 0); }