aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/misc
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2009-04-28 02:53:18 +0800
committerMatthew Barnes <mbarnes@redhat.com>2009-04-28 02:53:18 +0800
commite377ea5e61171e57f9e892652d0fd1f77953eda8 (patch)
treed0387074398b82952680766ade0d3df12ae60810 /widgets/misc
parent59f095bfb5b92f8bd0205238f91fc3a2277de76c (diff)
downloadgsoc2013-evolution-e377ea5e61171e57f9e892652d0fd1f77953eda8.tar.gz
gsoc2013-evolution-e377ea5e61171e57f9e892652d0fd1f77953eda8.tar.zst
gsoc2013-evolution-e377ea5e61171e57f9e892652d0fd1f77953eda8.zip
Bug 516933 – Rewrite attachment UI
Rewrite the attachment UI to better utilize GIO and also to migrate from GnomeIconList to GtkIconView. This also introduces a "List View" option similar to Nautilus, as well as the EAttachmentHandler framework for extending attachment handling (may eventually replace EMFormatHook). This commit also fixes a number of secondary attachment bugs: Bug 311609 – new attachment bar should use regular gtk+ expander Bug 314923 – Drag and Drop in attachment window is inconsistent and requires additional click Bug 338179 – attachment saving ... Bug 350364 – Action to get info about attachments Bug 383047 – Viewing mail attachments Bug 427235 – Can't copy attachment mime type string Bug 454091 – Cannot save multiple attachments who have the same name Bug 494629 – Rethink composer's attachment UI Bug 553970 – Evolution ignores umask when saving attachments Bug 577375 – mailto: and attach doesn't URL un-escape
Diffstat (limited to 'widgets/misc')
-rw-r--r--widgets/misc/e-attachment-button.c725
-rw-r--r--widgets/misc/e-attachment-button.h83
-rw-r--r--widgets/misc/e-attachment-dialog.c446
-rw-r--r--widgets/misc/e-attachment-dialog.h73
-rw-r--r--widgets/misc/e-attachment-handler-image.c280
-rw-r--r--widgets/misc/e-attachment-handler-image.h65
-rw-r--r--widgets/misc/e-attachment-handler.c191
-rw-r--r--widgets/misc/e-attachment-handler.h78
-rw-r--r--widgets/misc/e-attachment-icon-view.c537
-rw-r--r--widgets/misc/e-attachment-icon-view.h66
-rw-r--r--widgets/misc/e-attachment-paned.c781
-rw-r--r--widgets/misc/e-attachment-paned.h84
-rw-r--r--widgets/misc/e-attachment-store.c874
-rw-r--r--widgets/misc/e-attachment-store.h122
-rw-r--r--widgets/misc/e-attachment-tree-view.c605
-rw-r--r--widgets/misc/e-attachment-tree-view.h66
-rw-r--r--widgets/misc/e-attachment-view.c1637
-rw-r--r--widgets/misc/e-attachment-view.h227
18 files changed, 6940 insertions, 0 deletions
diff --git a/widgets/misc/e-attachment-button.c b/widgets/misc/e-attachment-button.c
new file mode 100644
index 0000000000..a9c230bac7
--- /dev/null
+++ b/widgets/misc/e-attachment-button.c
@@ -0,0 +1,725 @@
+/*
+ * e-attachment-button.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)
+ *
+ */
+
+/* Much of the popup menu logic here was ripped from GtkMenuToolButton. */
+
+#include "e-attachment-button.h"
+
+#include "e-util/e-binding.h"
+
+#define E_ATTACHMENT_BUTTON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonPrivate))
+
+struct _EAttachmentButtonPrivate {
+
+ EAttachmentView *view;
+ EAttachment *attachment;
+ gulong reference_handler_id;
+
+ EMutualBinding *can_show_binding;
+ EMutualBinding *shown_binding;
+
+ GtkWidget *expand_button;
+ GtkWidget *toggle_button;
+ GtkWidget *cell_view;
+
+ guint expandable : 1;
+ guint expanded : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ATTACHMENT,
+ PROP_EXPANDABLE,
+ PROP_EXPANDED,
+ PROP_VIEW
+};
+
+static gpointer parent_class;
+
+static void
+attachment_button_menu_deactivate_cb (EAttachmentButton *button)
+{
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkToggleButton *toggle_button;
+
+ view = e_attachment_button_get_view (button);
+ action_group = e_attachment_view_get_action_group (view, "inline");
+ toggle_button = GTK_TOGGLE_BUTTON (button->priv->toggle_button);
+
+ gtk_toggle_button_set_active (toggle_button, FALSE);
+
+ gtk_action_group_set_visible (action_group, FALSE);
+}
+
+static void
+attachment_button_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ EAttachmentButton *button)
+{
+ GtkRequisition menu_requisition;
+ GtkTextDirection direction;
+ GdkRectangle monitor;
+ GdkScreen *screen;
+ GdkWindow *window;
+ GtkWidget *widget;
+ GtkWidget *toggle_button;
+ gint monitor_num;
+
+ widget = GTK_WIDGET (button);
+ toggle_button = button->priv->toggle_button;
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_requisition);
+
+ window = gtk_widget_get_parent_window (widget);
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, window);
+ if (monitor_num < 0)
+ monitor_num = 0;
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gdk_window_get_origin (window, x, y);
+ *x += widget->allocation.x;
+ *y += widget->allocation.y;
+
+ direction = gtk_widget_get_direction (widget);
+ if (direction == GTK_TEXT_DIR_LTR)
+ *x += MAX (widget->allocation.width - menu_requisition.width, 0);
+ else if (menu_requisition.width > widget->allocation.width)
+ *x -= menu_requisition.width - widget->allocation.width;
+
+ if ((*y + toggle_button->allocation.height + menu_requisition.height) <= monitor.y + monitor.height)
+ *y += toggle_button->allocation.height;
+ else if ((*y - menu_requisition.height) >= monitor.y)
+ *y -= menu_requisition.height;
+ else if (monitor.y + monitor.height - (*y + toggle_button->allocation.height) > *y)
+ *y += toggle_button->allocation.height;
+ else
+ *y -= menu_requisition.height;
+
+ *push_in = FALSE;
+}
+
+static void
+attachment_button_select_path (EAttachmentButton *button)
+{
+ EAttachmentView *view;
+ EAttachment *attachment;
+ GtkTreeRowReference *reference;
+ GtkTreePath *path;
+
+ attachment = e_attachment_button_get_attachment (button);
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+ reference = e_attachment_get_reference (attachment);
+ g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+ view = e_attachment_button_get_view (button);
+ path = gtk_tree_row_reference_get_path (reference);
+
+ e_attachment_view_unselect_all (view);
+ e_attachment_view_select_path (view, path);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+attachment_button_show_popup_menu (EAttachmentButton *button,
+ GdkEventButton *event)
+{
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkToggleButton *toggle_button;
+
+ view = e_attachment_button_get_view (button);
+ action_group = e_attachment_view_get_action_group (view, "inline");
+ toggle_button = GTK_TOGGLE_BUTTON (button->priv->toggle_button);
+
+ attachment_button_select_path (button);
+ gtk_toggle_button_set_active (toggle_button, TRUE);
+
+ e_attachment_view_show_popup_menu (
+ view, event, (GtkMenuPositionFunc)
+ attachment_button_menu_position, button);
+
+ gtk_action_group_set_visible (action_group, TRUE);
+}
+
+static void
+attachment_button_update_cell_view (EAttachmentButton *button)
+{
+ GtkCellView *cell_view;
+ EAttachment *attachment;
+ GtkTreeRowReference *reference;
+ GtkTreeModel *model = NULL;
+ GtkTreePath *path = NULL;
+
+ cell_view = GTK_CELL_VIEW (button->priv->cell_view);
+
+ attachment = e_attachment_button_get_attachment (button);
+ if (attachment == NULL)
+ goto exit;
+
+ reference = e_attachment_get_reference (attachment);
+ if (reference == NULL)
+ goto exit;
+
+ model = gtk_tree_row_reference_get_model (reference);
+ path = gtk_tree_row_reference_get_path (reference);
+
+exit:
+ gtk_cell_view_set_model (cell_view, model);
+ gtk_cell_view_set_displayed_row (cell_view, path);
+
+ if (path != NULL)
+ gtk_tree_path_free (path);
+}
+
+static void
+attachment_button_update_pixbufs (EAttachmentButton *button)
+{
+ GtkCellView *cell_view;
+ GtkCellRenderer *renderer;
+ GtkIconTheme *icon_theme;
+ GdkPixbuf *pixbuf_expander_open;
+ GdkPixbuf *pixbuf_expander_closed;
+ GList *list;
+
+ icon_theme = gtk_icon_theme_get_default ();
+
+ /* Grab the first cell renderer. */
+ cell_view = GTK_CELL_VIEW (button->priv->cell_view);
+ list = gtk_cell_view_get_cell_renderers (cell_view);
+ renderer = GTK_CELL_RENDERER (list->data);
+ g_list_free (list);
+
+ pixbuf_expander_open = gtk_widget_render_icon (
+ GTK_WIDGET (button), GTK_STOCK_GO_DOWN,
+ GTK_ICON_SIZE_BUTTON, NULL);
+
+ pixbuf_expander_closed = gtk_widget_render_icon (
+ GTK_WIDGET (button), GTK_STOCK_GO_FORWARD,
+ GTK_ICON_SIZE_BUTTON, NULL);
+
+ g_object_set (
+ renderer,
+ "pixbuf-expander-open", pixbuf_expander_open,
+ "pixbuf-expander-closed", pixbuf_expander_closed,
+ NULL);
+
+ g_object_unref (pixbuf_expander_open);
+ g_object_unref (pixbuf_expander_closed);
+}
+
+static void
+attachment_button_expand_clicked_cb (EAttachmentButton *button)
+{
+ gboolean expanded;
+
+ expanded = e_attachment_button_get_expanded (button);
+ e_attachment_button_set_expanded (button, !expanded);
+}
+
+static void
+attachment_button_expand_drag_data_get_cb (EAttachmentButton *button,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view;
+
+ attachment_button_select_path (button);
+
+ view = e_attachment_button_get_view (button);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static gboolean
+attachment_button_toggle_button_press_event_cb (EAttachmentButton *button,
+ GdkEventButton *event)
+{
+ if (event->button == 1) {
+ attachment_button_show_popup_menu (button, event);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+attachment_button_set_view (EAttachmentButton *button,
+ EAttachmentView *view)
+{
+ GtkWidget *menu;
+
+ g_return_if_fail (button->priv->view == NULL);
+
+ button->priv->view = g_object_ref (view);
+
+ menu = e_attachment_view_get_popup_menu (view);
+
+ g_signal_connect_swapped (
+ menu, "deactivate",
+ G_CALLBACK (attachment_button_menu_deactivate_cb), button);
+}
+
+static void
+attachment_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ e_attachment_button_set_attachment (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_EXPANDABLE:
+ e_attachment_button_set_expandable (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EXPANDED:
+ e_attachment_button_set_expanded (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_VIEW:
+ attachment_button_set_view (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ g_value_set_object (
+ value,
+ e_attachment_button_get_attachment (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_EXPANDABLE:
+ g_value_set_boolean (
+ value,
+ e_attachment_button_get_expandable (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_EXPANDED:
+ g_value_set_boolean (
+ value,
+ e_attachment_button_get_expanded (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_VIEW:
+ g_value_set_object (
+ value,
+ e_attachment_button_get_view (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_button_dispose (GObject *object)
+{
+ EAttachmentButtonPrivate *priv;
+
+ priv = E_ATTACHMENT_BUTTON_GET_PRIVATE (object);
+
+ if (priv->view != NULL) {
+ g_object_unref (priv->view);
+ priv->view = NULL;
+ }
+
+ if (priv->attachment != NULL) {
+ g_signal_handler_disconnect (
+ priv->attachment,
+ priv->reference_handler_id);
+ g_object_unref (priv->attachment);
+ priv->attachment = NULL;
+ }
+
+ if (priv->expand_button != NULL) {
+ g_object_unref (priv->expand_button);
+ priv->expand_button = NULL;
+ }
+
+ if (priv->toggle_button != NULL) {
+ g_object_unref (priv->toggle_button);
+ priv->toggle_button = NULL;
+ }
+
+ if (priv->cell_view != NULL) {
+ g_object_unref (priv->cell_view);
+ priv->cell_view = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_button_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ EAttachmentButton *button;
+
+ /* Chain up to parent's style_set() method. */
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
+
+ button = E_ATTACHMENT_BUTTON (widget);
+ attachment_button_update_pixbufs (button);
+}
+
+static void
+attachment_button_class_init (EAttachmentButtonClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentButtonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_button_set_property;
+ object_class->get_property = attachment_button_get_property;
+ object_class->dispose = attachment_button_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->style_set = attachment_button_style_set;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ATTACHMENT,
+ g_param_spec_object (
+ "attachment",
+ "Attachment",
+ NULL,
+ E_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDABLE,
+ g_param_spec_boolean (
+ "expandable",
+ "Expandable",
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean (
+ "expanded",
+ "Expanded",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VIEW,
+ g_param_spec_object (
+ "view",
+ "View",
+ NULL,
+ E_TYPE_ATTACHMENT_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+attachment_button_init (EAttachmentButton *button)
+{
+ GtkCellRenderer *renderer;
+ GtkCellLayout *cell_layout;
+ GtkTargetEntry *targets;
+ GtkTargetList *list;
+ GtkWidget *container;
+ GtkWidget *widget;
+ gint n_targets;
+
+ button->priv = E_ATTACHMENT_BUTTON_GET_PRIVATE (button);
+
+ /* Configure Widgets */
+
+ container = GTK_WIDGET (button);
+
+ widget = gtk_button_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ button->priv->expand_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_mutual_binding_new (
+ G_OBJECT (button), "expandable",
+ G_OBJECT (widget), "sensitive");
+
+ widget = gtk_toggle_button_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ button->priv->toggle_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = button->priv->expand_button;
+
+ widget = gtk_cell_view_new ();
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ button->priv->cell_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = button->priv->toggle_button;
+
+ widget = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ /* Configure Renderers */
+
+ cell_layout = GTK_CELL_LAYOUT (button->priv->cell_view);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "is-expander", TRUE, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ e_mutual_binding_new (
+ G_OBJECT (button), "expanded",
+ G_OBJECT (renderer), "is-expanded");
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ /* Configure Drag and Drop */
+
+ 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);
+
+ gtk_drag_source_set (
+ button->priv->expand_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_drag_source_set (
+ button->priv->toggle_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_target_table_free (targets, n_targets);
+ gtk_target_list_unref (list);
+
+ /* Configure Signal Handlers */
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "clicked",
+ G_CALLBACK (attachment_button_expand_clicked_cb), button);
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "drag-data-get",
+ G_CALLBACK (attachment_button_expand_drag_data_get_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "button-press-event",
+ G_CALLBACK (attachment_button_toggle_button_press_event_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "drag-data-get",
+ G_CALLBACK (attachment_button_expand_drag_data_get_cb),
+ button);
+
+}
+
+GType
+e_attachment_button_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentButtonClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_button_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentButton),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_button_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_HBOX, "EAttachmentButton", &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_attachment_button_new (EAttachmentView *view)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ return g_object_new (
+ E_TYPE_ATTACHMENT_BUTTON,
+ "view", view, NULL);
+}
+
+EAttachmentView *
+e_attachment_button_get_view (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), NULL);
+
+ return button->priv->view;
+}
+
+EAttachment *
+e_attachment_button_get_attachment (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), NULL);
+
+ return button->priv->attachment;
+}
+
+void
+e_attachment_button_set_attachment (EAttachmentButton *button,
+ EAttachment *attachment)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ if (attachment != NULL) {
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+ g_object_ref (attachment);
+ }
+
+ if (button->priv->attachment != NULL) {
+ e_mutual_binding_unbind (
+ button->priv->can_show_binding);
+ button->priv->can_show_binding = NULL;
+ e_mutual_binding_unbind (
+ button->priv->shown_binding);
+ button->priv->shown_binding = NULL;
+ g_signal_handler_disconnect (
+ button->priv->attachment,
+ button->priv->reference_handler_id);
+ g_object_unref (button->priv->attachment);
+ }
+
+ button->priv->attachment = attachment;
+
+ if (attachment != NULL) {
+ EMutualBinding *binding;
+ gulong handler_id;
+
+ binding = e_mutual_binding_new (
+ G_OBJECT (attachment), "can-show",
+ G_OBJECT (button), "expandable");
+ button->priv->can_show_binding = binding;
+
+ binding = e_mutual_binding_new (
+ G_OBJECT (attachment), "shown",
+ G_OBJECT (button), "expanded");
+ button->priv->shown_binding = binding;
+
+ handler_id = g_signal_connect_swapped (
+ attachment, "notify::reference",
+ G_CALLBACK (attachment_button_update_cell_view),
+ button);
+ button->priv->reference_handler_id = handler_id;
+
+ attachment_button_update_cell_view (button);
+ attachment_button_update_pixbufs (button);
+ }
+
+ g_object_notify (G_OBJECT (button), "attachment");
+}
+
+gboolean
+e_attachment_button_get_expandable (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), FALSE);
+
+ return button->priv->expandable;
+}
+
+void
+e_attachment_button_set_expandable (EAttachmentButton *button,
+ gboolean expandable)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ button->priv->expandable = expandable;
+
+ if (!expandable)
+ e_attachment_button_set_expanded (button, FALSE);
+
+ g_object_notify (G_OBJECT (button), "expandable");
+}
+
+gboolean
+e_attachment_button_get_expanded (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), FALSE);
+
+ return button->priv->expanded;
+}
+
+void
+e_attachment_button_set_expanded (EAttachmentButton *button,
+ gboolean expanded)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ button->priv->expanded = expanded;
+
+ g_object_notify (G_OBJECT (button), "expanded");
+}
diff --git a/widgets/misc/e-attachment-button.h b/widgets/misc/e-attachment-button.h
new file mode 100644
index 0000000000..6e2f5672ef
--- /dev/null
+++ b/widgets/misc/e-attachment-button.h
@@ -0,0 +1,83 @@
+/*
+ * e-attachment-button.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_ATTACHMENT_BUTTON_H
+#define E_ATTACHMENT_BUTTON_H
+
+#include <gtk/gtk.h>
+#include <widgets/misc/e-attachment.h>
+#include <widgets/misc/e-attachment-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_BUTTON \
+ (e_attachment_button_get_type ())
+#define E_ATTACHMENT_BUTTON(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButton))
+#define E_ATTACHMENT_BUTTON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonClass))
+#define E_IS_ATTACHMENT_BUTTON(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_BUTTON))
+#define E_IS_ATTACHMENT_BUTTON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_BUTTON))
+#define E_ATTACHMENT_BUTTON_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentButton EAttachmentButton;
+typedef struct _EAttachmentButtonClass EAttachmentButtonClass;
+typedef struct _EAttachmentButtonPrivate EAttachmentButtonPrivate;
+
+struct _EAttachmentButton {
+ GtkHBox parent;
+ EAttachmentButtonPrivate *priv;
+};
+
+struct _EAttachmentButtonClass {
+ GtkHBoxClass parent_class;
+};
+
+GType e_attachment_button_get_type (void);
+GtkWidget * e_attachment_button_new (EAttachmentView *view);
+EAttachmentView *
+ e_attachment_button_get_view (EAttachmentButton *button);
+EAttachment * e_attachment_button_get_attachment
+ (EAttachmentButton *button);
+void e_attachment_button_set_attachment
+ (EAttachmentButton *button,
+ EAttachment *attachment);
+gboolean e_attachment_button_get_expandable
+ (EAttachmentButton *button);
+void e_attachment_button_set_expandable
+ (EAttachmentButton *button,
+ gboolean expandable);
+gboolean e_attachment_button_get_expanded(EAttachmentButton *button);
+void e_attachment_button_set_expanded(EAttachmentButton *button,
+ gboolean expanded);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_BUTTON_H */
diff --git a/widgets/misc/e-attachment-dialog.c b/widgets/misc/e-attachment-dialog.c
new file mode 100644
index 0000000000..e8c599aeea
--- /dev/null
+++ b/widgets/misc/e-attachment-dialog.c
@@ -0,0 +1,446 @@
+/*
+ * e-attachment-dialog.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)
+ *
+ */
+
+#include "e-attachment-dialog.h"
+
+#include <glib/gi18n.h>
+
+#define E_ATTACHMENT_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogPrivate))
+
+struct _EAttachmentDialogPrivate {
+ EAttachment *attachment;
+ GtkWidget *display_name_entry;
+ GtkWidget *description_entry;
+ GtkWidget *content_type_label;
+ GtkWidget *disposition_checkbox;
+};
+
+enum {
+ PROP_0,
+ PROP_ATTACHMENT
+};
+
+static gpointer parent_class;
+
+static void
+attachment_dialog_update (EAttachmentDialog *dialog)
+{
+ EAttachment *attachment;
+ GFileInfo *file_info;
+ GtkWidget *widget;
+ const gchar *content_type;
+ const gchar *display_name;
+ const gchar *description;
+ const gchar *disposition;
+ gchar *type_description = NULL;
+ gboolean sensitive;
+ gboolean active;
+
+ attachment = e_attachment_dialog_get_attachment (dialog);
+
+ if (attachment != NULL) {
+ file_info = e_attachment_get_file_info (attachment);
+ description = e_attachment_get_description (attachment);
+ disposition = e_attachment_get_disposition (attachment);
+ } else {
+ file_info = NULL;
+ description = NULL;
+ disposition = NULL;
+ }
+
+ if (file_info != NULL) {
+ content_type = g_file_info_get_content_type (file_info);
+ display_name = g_file_info_get_display_name (file_info);
+ } else {
+ content_type = NULL;
+ display_name = NULL;
+ }
+
+ if (content_type != NULL) {
+ gchar *comment;
+ gchar *mime_type;
+
+ comment = g_content_type_get_description (content_type);
+ mime_type = g_content_type_get_mime_type (content_type);
+
+ type_description =
+ g_strdup_printf ("%s (%s)", comment, mime_type);
+
+ g_free (comment);
+ g_free (mime_type);
+ }
+
+ sensitive = G_IS_FILE_INFO (file_info);
+
+ gtk_dialog_set_response_sensitive (
+ GTK_DIALOG (dialog), GTK_RESPONSE_OK, sensitive);
+
+ widget = dialog->priv->display_name_entry;
+ gtk_widget_set_sensitive (widget, sensitive);
+ if (display_name != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), display_name);
+
+ widget = dialog->priv->description_entry;
+ gtk_widget_set_sensitive (widget, sensitive);
+ if (description != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), description);
+
+ widget = dialog->priv->content_type_label;
+ gtk_label_set_text (GTK_LABEL (widget), type_description);
+
+ active = (g_strcmp0 (disposition, "inline") == 0);
+ widget = dialog->priv->disposition_checkbox;
+ gtk_widget_set_sensitive (widget, sensitive);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), active);
+
+ g_free (type_description);
+}
+
+static void
+attachment_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ e_attachment_dialog_set_attachment (
+ E_ATTACHMENT_DIALOG (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ g_value_set_object (
+ value, e_attachment_dialog_get_attachment (
+ E_ATTACHMENT_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_dialog_dispose (GObject *object)
+{
+ EAttachmentDialogPrivate *priv;
+
+ priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (object);
+
+ if (priv->attachment != NULL) {
+ g_object_unref (priv->attachment);
+ priv->attachment = NULL;
+ }
+
+ if (priv->display_name_entry != NULL) {
+ g_object_unref (priv->display_name_entry);
+ priv->display_name_entry = NULL;
+ }
+
+ if (priv->description_entry != NULL) {
+ g_object_unref (priv->description_entry);
+ priv->description_entry = NULL;
+ }
+
+ if (priv->content_type_label != NULL) {
+ g_object_unref (priv->content_type_label);
+ priv->content_type_label = NULL;
+ }
+
+ if (priv->disposition_checkbox != NULL) {
+ g_object_unref (priv->disposition_checkbox);
+ priv->disposition_checkbox = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_dialog_map (GtkWidget *widget)
+{
+ GtkWidget *action_area;
+ GtkWidget *content_area;
+
+ /* Chain up to parent's map() method. */
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ /* XXX Override GtkDialog's broken style property defaults. */
+ action_area = gtk_dialog_get_action_area (GTK_DIALOG (widget));
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (widget));
+
+ gtk_box_set_spacing (GTK_BOX (content_area), 12);
+ gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
+ gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
+}
+
+static void
+attachment_dialog_response (GtkDialog *dialog,
+ gint response_id)
+{
+ EAttachmentDialogPrivate *priv;
+ EAttachment *attachment;
+ GtkToggleButton *button;
+ GFileInfo *file_info;
+ CamelMimePart *mime_part;
+ const gchar *attribute;
+ const gchar *text;
+ gboolean active;
+
+ if (response_id != GTK_RESPONSE_OK)
+ return;
+
+ priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (dialog);
+ g_return_if_fail (E_IS_ATTACHMENT (priv->attachment));
+ attachment = priv->attachment;
+
+ file_info = e_attachment_get_file_info (attachment);
+ g_return_if_fail (G_IS_FILE_INFO (file_info));
+
+ mime_part = e_attachment_get_mime_part (attachment);
+
+ attribute = G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
+ text = gtk_entry_get_text (GTK_ENTRY (priv->display_name_entry));
+ g_file_info_set_attribute_string (file_info, attribute, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_filename (mime_part, text);
+
+ attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+ text = gtk_entry_get_text (GTK_ENTRY (priv->description_entry));
+ g_file_info_set_attribute_string (file_info, attribute, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_description (mime_part, text);
+
+ button = GTK_TOGGLE_BUTTON (priv->disposition_checkbox);
+ active = gtk_toggle_button_get_active (button);
+ text = active ? "inline" : "attachment";
+ e_attachment_set_disposition (attachment, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_disposition (mime_part, text);
+
+ g_object_notify (G_OBJECT (attachment), "file-info");
+}
+
+static void
+attachment_dialog_class_init (EAttachmentDialogClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkDialogClass *dialog_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_dialog_set_property;
+ object_class->get_property = attachment_dialog_get_property;
+ object_class->dispose = attachment_dialog_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->map = attachment_dialog_map;
+
+ dialog_class = GTK_DIALOG_CLASS (class);
+ dialog_class->response = attachment_dialog_response;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ATTACHMENT,
+ g_param_spec_object (
+ "attachment",
+ "Attachment",
+ NULL,
+ E_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+attachment_dialog_init (EAttachmentDialog *dialog)
+{
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ dialog->priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (dialog);
+
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
+ gtk_window_set_icon_name (
+ GTK_WINDOW (dialog), "mail-attachment");
+ gtk_window_set_title (
+ GTK_WINDOW (dialog), _("Attachment Properties"));
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ widget = gtk_table_new (4, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->display_name_entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new_with_mnemonic (_("_Filename:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->display_name_entry);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->description_entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new_with_mnemonic (_("_Description:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->description_entry);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 2, 3, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->content_type_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (_("MIME Type:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("_Suggest automatic display of attachment"));
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 2, 3, 4, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->disposition_checkbox = g_object_ref (widget);
+ gtk_widget_show (widget);
+}
+
+GType
+e_attachment_dialog_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentDialogClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_dialog_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_init */
+ sizeof (EAttachmentDialog),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_dialog_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_DIALOG, "EAttachmentDialog", &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_attachment_dialog_new (GtkWindow *parent,
+ EAttachment *attachment)
+{
+ if (parent != NULL)
+ g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
+ if (attachment != NULL)
+ g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+ return g_object_new (
+ E_TYPE_ATTACHMENT_DIALOG,
+ "transient-for", parent, "attachment", attachment, NULL);
+}
+
+EAttachment *
+e_attachment_dialog_get_attachment (EAttachmentDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_DIALOG (dialog), NULL);
+
+ return dialog->priv->attachment;
+}
+
+void
+e_attachment_dialog_set_attachment (EAttachmentDialog *dialog,
+ EAttachment *attachment)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_DIALOG (dialog));
+
+ if (attachment != NULL) {
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+ g_object_ref (attachment);
+ }
+
+ if (dialog->priv->attachment != NULL)
+ g_object_unref (dialog->priv->attachment);
+
+ dialog->priv->attachment = attachment;
+
+ attachment_dialog_update (dialog);
+
+ g_object_notify (G_OBJECT (dialog), "attachment");
+}
diff --git a/widgets/misc/e-attachment-dialog.h b/widgets/misc/e-attachment-dialog.h
new file mode 100644
index 0000000000..8e24e1840c
--- /dev/null
+++ b/widgets/misc/e-attachment-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * e-attachment-dialog.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_ATTACHMENT_DIALOG_H
+#define E_ATTACHMENT_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <widgets/misc/e-attachment.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_DIALOG \
+ (e_attachment_dialog_get_type ())
+#define E_ATTACHMENT_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialog))
+#define E_ATTACHMENT_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogClass))
+#define E_IS_ATTACHMENT_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_DIALOG))
+#define E_IS_ATTACHMENT_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_DIALOG))
+#define E_ATTACHMENT_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentDialog EAttachmentDialog;
+typedef struct _EAttachmentDialogClass EAttachmentDialogClass;
+typedef struct _EAttachmentDialogPrivate EAttachmentDialogPrivate;
+
+struct _EAttachmentDialog {
+ GtkDialog parent;
+ EAttachmentDialogPrivate *priv;
+};
+
+struct _EAttachmentDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType e_attachment_dialog_get_type (void);
+GtkWidget * e_attachment_dialog_new (GtkWindow *parent,
+ EAttachment *attachment);
+EAttachment * e_attachment_dialog_get_attachment
+ (EAttachmentDialog *dialog);
+void e_attachment_dialog_set_attachment
+ (EAttachmentDialog *dialog,
+ EAttachment *attachment);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_DIALOG_H */
diff --git a/widgets/misc/e-attachment-handler-image.c b/widgets/misc/e-attachment-handler-image.c
new file mode 100644
index 0000000000..ba5f8d759c
--- /dev/null
+++ b/widgets/misc/e-attachment-handler-image.c
@@ -0,0 +1,280 @@
+/*
+ * e-attachment-handler-image.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)
+ *
+ */
+
+#include "e-attachment-handler-image.h"
+
+#include <glib/gi18n.h>
+#include <gconf/gconf-client.h>
+
+#define E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImagePrivate))
+
+struct _EAttachmentHandlerImagePrivate {
+ gint placeholder;
+};
+
+static gpointer parent_class;
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <placeholder name='custom-actions'>"
+" <menuitem action='image-set-as-background'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+static void
+action_image_set_as_background_saved_cb (EAttachment *attachment,
+ GAsyncResult *result,
+ EAttachmentHandler *handler)
+{
+ EAttachmentView *view;
+ GConfClient *client;
+ GtkWidget *dialog;
+ GFile *file;
+ const gchar *key;
+ gpointer parent;
+ gchar *value;
+ GError *error = NULL;
+
+ client = gconf_client_get_default ();
+ view = e_attachment_handler_get_view (handler);
+
+ file = e_attachment_save_finish (attachment, result, &error);
+
+ if (error != NULL)
+ goto error;
+
+ value = g_file_get_path (file);
+ g_object_unref (file);
+
+ key = "/desktop/gnome/background/picture_filename";
+ gconf_client_set_string (client, key, value, &error);
+ g_free (value);
+
+ if (error != NULL)
+ goto error;
+
+ /* Ignore errors for this part. */
+ key = "/desktop/gnome/background/picture_options";
+ value = gconf_client_get_string (client, key, NULL);
+ if (g_strcmp0 (value, "none") == 0)
+ gconf_client_set_string (client, key, "wallpaper", NULL);
+ g_free (value);
+
+ goto exit;
+
+error:
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ dialog = gtk_message_dialog_new_with_markup (
+ parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "<big><b>%s</b></big>",
+ _("Could not set as background"));
+
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+ g_error_free (error);
+
+exit:
+ g_object_unref (client);
+ g_object_unref (handler);
+}
+
+static void
+action_image_set_as_background_cb (GtkAction *action,
+ EAttachmentHandler *handler)
+{
+ EAttachmentView *view;
+ EAttachment *attachment;
+ GFile *destination;
+ GList *selected;
+ gchar *path;
+
+ view = e_attachment_handler_get_view (handler);
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = E_ATTACHMENT (selected->data);
+
+ /* Save the image under ~/.gnome2/wallpapers/. */
+ path = g_build_filename (
+ g_get_home_dir (), ".gnome2", "wallpapers", NULL);
+ destination = g_file_new_for_path (path);
+ g_mkdir_with_parents (path, 0755);
+ g_free (path);
+
+ e_attachment_save_async (
+ attachment, destination, (GAsyncReadyCallback)
+ action_image_set_as_background_saved_cb,
+ g_object_ref (handler));
+
+ g_object_unref (destination);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+ { "image-set-as-background",
+ NULL,
+ N_("Set as _Background"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_image_set_as_background_cb) }
+};
+
+static void
+attachment_handler_image_update_actions_cb (EAttachmentView *view,
+ EAttachmentHandler *handler)
+{
+ EAttachmentHandlerImagePrivate *priv;
+ EAttachment *attachment;
+ GFileInfo *file_info;
+ GtkActionGroup *action_group;
+ const gchar *content_type;
+ gchar *mime_type;
+ GList *selected;
+ gboolean visible = FALSE;
+
+ priv = E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE (handler);
+
+ selected = e_attachment_view_get_selected_attachments (view);
+
+ if (g_list_length (selected) != 1)
+ goto exit;
+
+ attachment = E_ATTACHMENT (selected->data);
+ file_info = e_attachment_get_file_info (attachment);
+
+ if (file_info == NULL)
+ goto exit;
+
+ if (e_attachment_get_loading (attachment))
+ goto exit;
+
+ if (e_attachment_get_saving (attachment))
+ goto exit;
+
+ content_type = g_file_info_get_content_type (file_info);
+
+ mime_type = g_content_type_get_mime_type (content_type);
+ visible = (g_ascii_strncasecmp (mime_type, "image/", 6) == 0);
+ g_free (mime_type);
+
+exit:
+ action_group = e_attachment_view_get_action_group (view, "image");
+ gtk_action_group_set_visible (action_group, visible);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+attachment_handler_image_constructed (GObject *object)
+{
+ EAttachmentHandlerImagePrivate *priv;
+ EAttachmentHandler *handler;
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkUIManager *ui_manager;
+ GError *error = NULL;
+
+ handler = E_ATTACHMENT_HANDLER (object);
+ priv = E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE (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, "image");
+ gtk_action_group_add_actions (
+ action_group, standard_entries,
+ G_N_ELEMENTS (standard_entries), object);
+
+ 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 (attachment_handler_image_update_actions_cb),
+ object);
+}
+
+static void
+attachment_handler_image_class_init (EAttachmentHandlerImageClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentHandlerImagePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = attachment_handler_image_constructed;
+}
+
+static void
+attachment_handler_image_init (EAttachmentHandlerImage *handler)
+{
+ handler->priv = E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE (handler);
+}
+
+GType
+e_attachment_handler_image_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentHandlerImageClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_handler_image_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentHandlerImage),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_handler_image_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ E_TYPE_ATTACHMENT_HANDLER,
+ "EAttachmentHandlerImage",
+ &type_info, 0);
+ }
+
+ return type;
+}
diff --git a/widgets/misc/e-attachment-handler-image.h b/widgets/misc/e-attachment-handler-image.h
new file mode 100644
index 0000000000..53b076c61f
--- /dev/null
+++ b/widgets/misc/e-attachment-handler-image.h
@@ -0,0 +1,65 @@
+/*
+ * e-attachment-handler-image.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_ATTACHMENT_HANDLER_IMAGE_H
+#define E_ATTACHMENT_HANDLER_IMAGE_H
+
+#include <widgets/misc/e-attachment-handler.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_HANDLER_IMAGE \
+ (e_attachment_handler_image_get_type ())
+#define E_ATTACHMENT_HANDLER_IMAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImage))
+#define E_ATTACHMENT_HANDLER_IMAGE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImageClass))
+#define E_IS_ATTACHMENT_HANDLER_IMAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE))
+#define E_IS_ATTACHMENT_HANDLER_IMAGE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_HANDLER_IMAGE))
+#define E_ATTACHMENT_HANDLER_IMAGE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImageClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentHandlerImage EAttachmentHandlerImage;
+typedef struct _EAttachmentHandlerImageClass EAttachmentHandlerImageClass;
+typedef struct _EAttachmentHandlerImagePrivate EAttachmentHandlerImagePrivate;
+
+struct _EAttachmentHandlerImage {
+ EAttachmentHandler parent;
+ EAttachmentHandlerImagePrivate *priv;
+};
+
+struct _EAttachmentHandlerImageClass {
+ EAttachmentHandlerClass parent_class;
+};
+
+GType e_attachment_handler_image_get_type (void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_HANDLER_IMAGE_H */
diff --git a/widgets/misc/e-attachment-handler.c b/widgets/misc/e-attachment-handler.c
new file mode 100644
index 0000000000..03af1eec9b
--- /dev/null
+++ b/widgets/misc/e-attachment-handler.c
@@ -0,0 +1,191 @@
+/*
+ * e-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)
+ *
+ */
+
+#include "e-attachment-handler.h"
+
+#define E_ATTACHMENT_HANDLER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerPrivate))
+
+struct _EAttachmentHandlerPrivate {
+ gpointer view; /* weak pointer */
+};
+
+enum {
+ PROP_0,
+ PROP_VIEW
+};
+
+static gpointer parent_class;
+
+static void
+attachment_handler_set_view (EAttachmentHandler *handler,
+ EAttachmentView *view)
+{
+ g_return_if_fail (handler->priv->view == NULL);
+
+ handler->priv->view = view;
+
+ g_object_add_weak_pointer (
+ G_OBJECT (view), &handler->priv->view);
+}
+
+static void
+attachment_handler_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_VIEW:
+ attachment_handler_set_view (
+ E_ATTACHMENT_HANDLER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_handler_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_VIEW:
+ g_value_set_object (
+ value, e_attachment_handler_get_view (
+ E_ATTACHMENT_HANDLER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_handler_constructed (GObject *object)
+{
+ /* This allows subclasses to chain up safely since GObject
+ * does not implement this method, and we might want to do
+ * something here in the future. */
+}
+
+static void
+attachment_handler_class_init (EAttachmentHandlerClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentHandlerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_handler_set_property;
+ object_class->get_property = attachment_handler_get_property;
+ object_class->constructed = attachment_handler_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VIEW,
+ g_param_spec_object (
+ "view",
+ "View",
+ NULL,
+ E_TYPE_ATTACHMENT_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+attachment_handler_init (EAttachmentHandler *handler)
+{
+ handler->priv = E_ATTACHMENT_HANDLER_GET_PRIVATE (handler);
+}
+
+GType
+e_attachment_handler_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentHandlerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_handler_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentHandler),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_handler_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ G_TYPE_OBJECT, "EAttachmentHandler",
+ &type_info, G_TYPE_FLAG_ABSTRACT);
+ }
+
+ return type;
+}
+
+EAttachmentView *
+e_attachment_handler_get_view (EAttachmentHandler *handler)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), NULL);
+
+ return E_ATTACHMENT_VIEW (handler->priv->view);
+}
+
+GdkDragAction
+e_attachment_handler_get_drag_actions (EAttachmentHandler *handler)
+{
+ EAttachmentHandlerClass *class;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), 0);
+
+ class = E_ATTACHMENT_HANDLER_GET_CLASS (handler);
+
+ if (class->get_drag_actions != NULL)
+ return class->get_drag_actions (handler);
+
+ return 0;
+}
+
+const GtkTargetEntry *
+e_attachment_handler_get_target_table (EAttachmentHandler *handler,
+ guint *n_targets)
+{
+ EAttachmentHandlerClass *class;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), NULL);
+
+ class = E_ATTACHMENT_HANDLER_GET_CLASS (handler);
+
+ if (class->get_target_table != NULL)
+ return class->get_target_table (handler, n_targets);
+
+ if (n_targets != NULL)
+ *n_targets = 0;
+
+ return NULL;
+}
diff --git a/widgets/misc/e-attachment-handler.h b/widgets/misc/e-attachment-handler.h
new file mode 100644
index 0000000000..05df3e8bbe
--- /dev/null
+++ b/widgets/misc/e-attachment-handler.h
@@ -0,0 +1,78 @@
+/*
+ * e-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_ATTACHMENT_HANDLER_H
+#define E_ATTACHMENT_HANDLER_H
+
+#include <widgets/misc/e-attachment-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_HANDLER \
+ (e_attachment_handler_get_type ())
+#define E_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandler))
+#define E_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerClass))
+#define E_IS_ATTACHMENT_HANDLER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER))
+#define E_IS_ATTACHMENT_HANDLER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_HANDLER))
+#define E_ATTACHMENT_HANDLER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentHandler EAttachmentHandler;
+typedef struct _EAttachmentHandlerClass EAttachmentHandlerClass;
+typedef struct _EAttachmentHandlerPrivate EAttachmentHandlerPrivate;
+
+struct _EAttachmentHandler {
+ GObject parent;
+ EAttachmentHandlerPrivate *priv;
+};
+
+struct _EAttachmentHandlerClass {
+ GObjectClass parent_class;
+
+ GdkDragAction (*get_drag_actions) (EAttachmentHandler *handler);
+ const GtkTargetEntry *
+ (*get_target_table) (EAttachmentHandler *handler,
+ guint *n_targets);
+};
+
+GType e_attachment_handler_get_type (void);
+EAttachmentView *
+ e_attachment_handler_get_view (EAttachmentHandler *handler);
+GdkDragAction e_attachment_handler_get_drag_actions
+ (EAttachmentHandler *handler);
+const GtkTargetEntry *
+ e_attachment_handler_get_target_table
+ (EAttachmentHandler *handler,
+ guint *n_targets);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_HANDLER_H */
diff --git a/widgets/misc/e-attachment-icon-view.c b/widgets/misc/e-attachment-icon-view.c
new file mode 100644
index 0000000000..0845f82a76
--- /dev/null
+++ b/widgets/misc/e-attachment-icon-view.c
@@ -0,0 +1,537 @@
+/*
+ * e-attachment-icon-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)
+ *
+ */
+
+#include "e-attachment-icon-view.h"
+
+#include <glib/gi18n.h>
+
+#include "e-attachment.h"
+#include "e-attachment-store.h"
+#include "e-attachment-view.h"
+
+#define E_ATTACHMENT_ICON_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconViewPrivate))
+
+struct _EAttachmentIconViewPrivate {
+ EAttachmentViewPrivate view_priv;
+};
+
+enum {
+ PROP_0,
+ PROP_EDITABLE
+};
+
+static gpointer parent_class;
+
+static void
+attachment_icon_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_icon_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_icon_view_dispose (GObject *object)
+{
+ e_attachment_view_dispose (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_icon_view_finalize (GObject *object)
+{
+ e_attachment_view_finalize (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+attachment_icon_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_press_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ button_press_event (widget, event);
+}
+
+static gboolean
+attachment_icon_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_release_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_release_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ button_release_event (widget, event);
+}
+
+static gboolean
+attachment_icon_view_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_key_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's key_press_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ key_press_event (widget, event);
+}
+
+static void
+attachment_icon_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_begin() method. */
+ GTK_WIDGET_CLASS (parent_class)->drag_begin (widget, context);
+
+ e_attachment_view_drag_begin (view, context);
+}
+
+static void
+attachment_icon_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_end() method. */
+ GTK_WIDGET_CLASS (parent_class)->drag_end (widget, context);
+
+ e_attachment_view_drag_end (view, context);
+}
+
+static void
+attachment_icon_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static gboolean
+attachment_icon_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ return e_attachment_view_drag_motion (view, context, x, y, time);
+}
+
+static gboolean
+attachment_icon_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (!e_attachment_view_drag_drop (view, context, x, y, time))
+ return FALSE;
+
+ /* Chain up to parent's drag_drop() method. */
+ return GTK_WIDGET_CLASS (parent_class)->drag_drop (
+ widget, context, x, y, time);
+}
+
+static void
+attachment_icon_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_received (
+ view, context, x, y, selection, info, time);
+}
+
+static gboolean
+attachment_icon_view_popup_menu (GtkWidget *widget)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_show_popup_menu (view, NULL, NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+attachment_icon_view_item_activated (GtkIconView *icon_view,
+ GtkTreePath *path)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (icon_view);
+
+ e_attachment_view_open_path (view, path, NULL);
+}
+
+static EAttachmentViewPrivate *
+attachment_icon_view_get_private (EAttachmentView *view)
+{
+ EAttachmentIconViewPrivate *priv;
+
+ priv = E_ATTACHMENT_ICON_VIEW_GET_PRIVATE (view);
+
+ return &priv->view_priv;
+}
+
+static EAttachmentStore *
+attachment_icon_view_get_store (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+ GtkTreeModel *model;
+
+ icon_view = GTK_ICON_VIEW (view);
+ model = gtk_icon_view_get_model (icon_view);
+
+ return E_ATTACHMENT_STORE (model);
+}
+
+static GtkTreePath *
+attachment_icon_view_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_get_path_at_pos (icon_view, x, y);
+}
+
+static GList *
+attachment_icon_view_get_selected_paths (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_get_selected_items (icon_view);
+}
+
+static gboolean
+attachment_icon_view_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_path_is_selected (icon_view, path);
+}
+
+static void
+attachment_icon_view_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_select_path (icon_view, path);
+}
+
+static void
+attachment_icon_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unselect_path (icon_view, path);
+}
+
+static void
+attachment_icon_view_select_all (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_select_all (icon_view);
+}
+
+static void
+attachment_icon_view_unselect_all (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unselect_all (icon_view);
+}
+
+static void
+attachment_icon_view_drag_source_set (EAttachmentView *view,
+ GdkModifierType start_button_mask,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_enable_model_drag_source (
+ icon_view, start_button_mask, targets, n_targets, actions);
+}
+
+static void
+attachment_icon_view_drag_dest_set (EAttachmentView *view,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_enable_model_drag_dest (
+ icon_view, targets, n_targets, actions);
+}
+
+static void
+attachment_icon_view_drag_source_unset (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unset_model_drag_source (icon_view);
+}
+
+static void
+attachment_icon_view_drag_dest_unset (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unset_model_drag_dest (icon_view);
+}
+
+static void
+attachment_icon_view_class_init (EAttachmentIconViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkIconViewClass *icon_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_icon_view_set_property;
+ object_class->get_property = attachment_icon_view_get_property;
+ object_class->dispose = attachment_icon_view_dispose;
+ object_class->finalize = attachment_icon_view_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = attachment_icon_view_button_press_event;
+ widget_class->button_release_event = attachment_icon_view_button_release_event;
+ widget_class->key_press_event = attachment_icon_view_key_press_event;
+ widget_class->drag_begin = attachment_icon_view_drag_begin;
+ widget_class->drag_end = attachment_icon_view_drag_end;
+ widget_class->drag_data_get = attachment_icon_view_drag_data_get;
+ widget_class->drag_motion = attachment_icon_view_drag_motion;
+ widget_class->drag_drop = attachment_icon_view_drag_drop;
+ widget_class->drag_data_received = attachment_icon_view_drag_data_received;
+ widget_class->popup_menu = attachment_icon_view_popup_menu;
+
+ icon_view_class = GTK_ICON_VIEW_CLASS (class);
+ icon_view_class->item_activated = attachment_icon_view_item_activated;
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+attachment_icon_view_iface_init (EAttachmentViewIface *iface)
+{
+ iface->get_private = attachment_icon_view_get_private;
+ iface->get_store = attachment_icon_view_get_store;
+
+ iface->get_path_at_pos = attachment_icon_view_get_path_at_pos;
+ iface->get_selected_paths = attachment_icon_view_get_selected_paths;
+ iface->path_is_selected = attachment_icon_view_path_is_selected;
+ iface->select_path = attachment_icon_view_select_path;
+ iface->unselect_path = attachment_icon_view_unselect_path;
+ iface->select_all = attachment_icon_view_select_all;
+ iface->unselect_all = attachment_icon_view_unselect_all;
+
+ iface->drag_source_set = attachment_icon_view_drag_source_set;
+ iface->drag_dest_set = attachment_icon_view_drag_dest_set;
+ iface->drag_source_unset = attachment_icon_view_drag_source_unset;
+ iface->drag_dest_unset = attachment_icon_view_drag_dest_unset;
+}
+
+static void
+attachment_icon_view_init (EAttachmentIconView *icon_view)
+{
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *renderer;
+ cell_layout = GTK_CELL_LAYOUT (icon_view);
+ icon_view->priv = E_ATTACHMENT_ICON_VIEW_GET_PRIVATE (icon_view);
+
+ e_attachment_view_init (E_ATTACHMENT_VIEW (icon_view));
+
+ gtk_icon_view_set_selection_mode (
+ GTK_ICON_VIEW (icon_view), GTK_SELECTION_MULTIPLE);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DIALOG, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (
+ renderer, "alignment", PANGO_ALIGN_CENTER,
+ "wrap-mode", PANGO_WRAP_WORD, "wrap-width", 150,
+ "yalign", 0.0, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "text",
+ E_ATTACHMENT_STORE_COLUMN_CAPTION);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Loading"), NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Saving"), NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_SAVING);
+}
+
+GType
+e_attachment_icon_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentIconViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_icon_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentIconView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_icon_view_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo iface_info = {
+ (GInterfaceInitFunc) attachment_icon_view_iface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_ICON_VIEW, "EAttachmentIconView",
+ &type_info, 0);
+
+ g_type_add_interface_static (
+ type, E_TYPE_ATTACHMENT_VIEW, &iface_info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_attachment_icon_view_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_ICON_VIEW, NULL);
+}
diff --git a/widgets/misc/e-attachment-icon-view.h b/widgets/misc/e-attachment-icon-view.h
new file mode 100644
index 0000000000..ab9360e42e
--- /dev/null
+++ b/widgets/misc/e-attachment-icon-view.h
@@ -0,0 +1,66 @@
+/*
+ * e-attachment-icon-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_ATTACHMENT_ICON_VIEW_H
+#define E_ATTACHMENT_ICON_VIEW_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_ICON_VIEW \
+ (e_attachment_icon_view_get_type ())
+#define E_ATTACHMENT_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconView))
+#define E_ATTACHMENT_ICON_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconView))
+#define E_IS_ATTACHMENT_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_ICON_VIEW))
+#define E_IS_ATTACHMENT_ICON_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_ICON_VIEW))
+#define E_ATTACHMENT_ICON_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_ICON_VIEW))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentIconView EAttachmentIconView;
+typedef struct _EAttachmentIconViewClass EAttachmentIconViewClass;
+typedef struct _EAttachmentIconViewPrivate EAttachmentIconViewPrivate;
+
+struct _EAttachmentIconView {
+ GtkIconView parent;
+ EAttachmentIconViewPrivate *priv;
+};
+
+struct _EAttachmentIconViewClass {
+ GtkIconViewClass parent_class;
+};
+
+GType e_attachment_icon_view_get_type (void);
+GtkWidget * e_attachment_icon_view_new (void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_ICON_VIEW_H */
diff --git a/widgets/misc/e-attachment-paned.c b/widgets/misc/e-attachment-paned.c
new file mode 100644
index 0000000000..853ff48cb2
--- /dev/null
+++ b/widgets/misc/e-attachment-paned.c
@@ -0,0 +1,781 @@
+/*
+ * e-attachment-paned.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)
+ *
+ */
+
+#include "e-attachment-paned.h"
+
+#include <glib/gi18n.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/gconf-bridge.h"
+
+#include "e-attachment-view.h"
+#include "e-attachment-store.h"
+#include "e-attachment-icon-view.h"
+#include "e-attachment-tree-view.h"
+
+#define E_ATTACHMENT_PANED_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedPrivate))
+
+#define NUM_VIEWS 2
+
+/* Initial height of the lower pane. */
+#define INITIAL_HEIGHT 150
+
+struct _EAttachmentPanedPrivate {
+ GtkTreeModel *model;
+ GtkWidget *expander;
+ GtkWidget *notebook;
+ GtkWidget *combo_box;
+ GtkWidget *icon_view;
+ GtkWidget *tree_view;
+ GtkWidget *show_hide_label;
+ GtkWidget *status_icon;
+ GtkWidget *status_label;
+ GtkWidget *content_area;
+
+ gint active_view;
+ guint expanded : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_VIEW,
+ PROP_EDITABLE,
+ PROP_EXPANDED
+};
+
+static gpointer parent_class;
+
+static void
+attachment_paned_notify_cb (EAttachmentPaned *paned,
+ GParamSpec *pspec,
+ GtkExpander *expander)
+{
+ GtkLabel *label;
+ const gchar *text;
+
+ label = GTK_LABEL (paned->priv->show_hide_label);
+
+ /* Update the expander label. */
+ if (gtk_expander_get_expanded (expander))
+ text = _("Hide _Attachment Bar");
+ else
+ text = _("Show _Attachment Bar");
+
+ gtk_label_set_text_with_mnemonic (label, text);
+}
+
+static void
+attachment_paned_sync_icon_view (EAttachmentPaned *paned)
+{
+ EAttachmentView *source;
+ EAttachmentView *target;
+
+ source = E_ATTACHMENT_VIEW (paned->priv->tree_view);
+ target = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+
+ /* Only sync if the tree view is active. This prevents the
+ * two views from endlessly trying to sync with each other. */
+ if (e_attachment_paned_get_active_view (paned) == 1)
+ e_attachment_view_sync_selection (source, target);
+}
+
+static void
+attachment_paned_sync_tree_view (EAttachmentPaned *paned)
+{
+ EAttachmentView *source;
+ EAttachmentView *target;
+
+ source = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+ target = E_ATTACHMENT_VIEW (paned->priv->tree_view);
+
+ /* Only sync if the icon view is active. This prevents the
+ * two views from endlessly trying to sync with each other. */
+ if (e_attachment_paned_get_active_view (paned) == 0)
+ e_attachment_view_sync_selection (source, target);
+}
+
+static void
+attachment_paned_update_status (EAttachmentPaned *paned)
+{
+ EAttachmentView *view;
+ EAttachmentStore *store;
+ GtkExpander *expander;
+ GtkLabel *label;
+ guint num_attachments;
+ guint64 total_size;
+ gchar *display_size;
+ gchar *markup;
+
+ view = E_ATTACHMENT_VIEW (paned);
+ store = e_attachment_view_get_store (view);
+ expander = GTK_EXPANDER (paned->priv->expander);
+ label = GTK_LABEL (paned->priv->status_label);
+
+ num_attachments = e_attachment_store_get_num_attachments (store);
+ total_size = e_attachment_store_get_total_size (store);
+ display_size = g_format_size_for_display (total_size);
+
+ markup = g_strdup_printf (
+ "<b>%d</b> %s (%s)", num_attachments, ngettext (
+ "Attachment", "Attachments", num_attachments),
+ display_size);
+ gtk_label_set_markup (label, markup);
+ g_free (markup);
+
+ g_free (display_size);
+
+ if (num_attachments > 0) {
+ gtk_widget_show (paned->priv->status_icon);
+ gtk_widget_show (paned->priv->status_label);
+ gtk_expander_set_expanded (expander, TRUE);
+ } else {
+ gtk_widget_hide (paned->priv->status_icon);
+ gtk_widget_hide (paned->priv->status_label);
+ gtk_expander_set_expanded (expander, FALSE);
+ }
+}
+
+static void
+attachment_paned_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ e_attachment_paned_set_active_view (
+ E_ATTACHMENT_PANED (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EXPANDED:
+ e_attachment_paned_set_expanded (
+ E_ATTACHMENT_PANED (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_paned_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ g_value_set_int (
+ value, e_attachment_paned_get_active_view (
+ E_ATTACHMENT_PANED (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EXPANDED:
+ g_value_set_boolean (
+ value, e_attachment_paned_get_expanded (
+ E_ATTACHMENT_PANED (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_paned_dispose (GObject *object)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (object);
+
+ if (priv->model != NULL) {
+ g_object_unref (priv->model);
+ priv->model = NULL;
+ }
+
+ if (priv->expander != NULL) {
+ g_object_unref (priv->expander);
+ priv->expander = NULL;
+ }
+
+ if (priv->notebook != NULL) {
+ g_object_unref (priv->notebook);
+ priv->notebook = NULL;
+ }
+
+ if (priv->combo_box != NULL) {
+ g_object_unref (priv->combo_box);
+ priv->combo_box = NULL;
+ }
+
+ if (priv->icon_view != NULL) {
+ g_object_unref (priv->icon_view);
+ priv->icon_view = NULL;
+ }
+
+ if (priv->tree_view != NULL) {
+ g_object_unref (priv->tree_view);
+ priv->tree_view = NULL;
+ }
+
+ if (priv->show_hide_label != NULL) {
+ g_object_unref (priv->show_hide_label);
+ priv->show_hide_label = NULL;
+ }
+
+ if (priv->status_icon != NULL) {
+ g_object_unref (priv->status_icon);
+ priv->status_icon = NULL;
+ }
+
+ if (priv->status_label != NULL) {
+ g_object_unref (priv->status_label);
+ priv->status_label = NULL;
+ }
+
+ if (priv->content_area != NULL) {
+ g_object_unref (priv->content_area);
+ priv->content_area = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_paned_constructed (GObject *object)
+{
+ EAttachmentPanedPrivate *priv;
+ GConfBridge *bridge;
+ const gchar *key;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (object);
+
+ bridge = gconf_bridge_get ();
+
+ /* Set up property-to-property bindings. */
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "active-view",
+ G_OBJECT (priv->combo_box), "active");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "active-view",
+ G_OBJECT (priv->notebook), "page");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "editable",
+ G_OBJECT (priv->icon_view), "editable");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "editable",
+ G_OBJECT (priv->tree_view), "editable");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "expanded",
+ G_OBJECT (priv->expander), "expanded");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "expanded",
+ G_OBJECT (priv->combo_box), "sensitive");
+
+ e_mutual_binding_new (
+ G_OBJECT (object), "expanded",
+ G_OBJECT (priv->notebook), "visible");
+
+ /* Set up property-to-GConf bindings. */
+
+ key = "/apps/evolution/shell/attachment_view";
+ gconf_bridge_bind_property (bridge, key, object, "active-view");
+}
+
+static EAttachmentViewPrivate *
+attachment_paned_get_private (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_private (view);
+}
+
+static EAttachmentStore *
+attachment_paned_get_store (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_store (view);
+}
+
+static GtkTreePath *
+attachment_paned_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_path_at_pos (view, x, y);
+}
+
+static GList *
+attachment_paned_get_selected_paths (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_selected_paths (view);
+}
+
+static gboolean
+attachment_paned_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_path_is_selected (view, path);
+}
+
+static void
+attachment_paned_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_select_path (view, path);
+}
+
+static void
+attachment_paned_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_unselect_path (view, path);
+}
+
+static void
+attachment_paned_select_all (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_select_all (view);
+}
+
+static void
+attachment_paned_unselect_all (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_unselect_all (view);
+}
+
+static void
+attachment_paned_update_actions (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_update_actions (view);
+}
+
+static void
+attachment_paned_class_init (EAttachmentPanedClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentPanedPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_paned_set_property;
+ object_class->get_property = attachment_paned_get_property;
+ object_class->dispose = attachment_paned_dispose;
+ object_class->constructed = attachment_paned_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_VIEW,
+ g_param_spec_int (
+ "active-view",
+ "Active View",
+ NULL,
+ 0,
+ NUM_VIEWS,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean (
+ "expanded",
+ "Expanded",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+attachment_paned_iface_init (EAttachmentViewIface *iface)
+{
+ iface->get_private = attachment_paned_get_private;
+ iface->get_store = attachment_paned_get_store;
+ iface->get_path_at_pos = attachment_paned_get_path_at_pos;
+ iface->get_selected_paths = attachment_paned_get_selected_paths;
+ iface->path_is_selected = attachment_paned_path_is_selected;
+ iface->select_path = attachment_paned_select_path;
+ iface->unselect_path = attachment_paned_unselect_path;
+ iface->select_all = attachment_paned_select_all;
+ iface->unselect_all = attachment_paned_unselect_all;
+ iface->update_actions = attachment_paned_update_actions;
+}
+
+static void
+attachment_paned_init (EAttachmentPaned *paned)
+{
+ EAttachmentView *view;
+ GtkTreeSelection *selection;
+ GtkSizeGroup *size_group;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkAction *action;
+
+ paned->priv = E_ATTACHMENT_PANED_GET_PRIVATE (paned);
+ paned->priv->model = e_attachment_store_new ();
+
+ /* Keep the expander label and combo box the same height. */
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ /* Construct the Attachment Views */
+
+ container = GTK_WIDGET (paned);
+
+ widget = gtk_notebook_new ();
+ gtk_widget_set_size_request (widget, -1, INITIAL_HEIGHT);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ paned->priv->notebook = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = paned->priv->notebook;
+
+ 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_notebook_append_page (GTK_NOTEBOOK (container), widget, NULL);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_attachment_icon_view_new ();
+ GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
+ gtk_icon_view_set_model (GTK_ICON_VIEW (widget), paned->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ paned->priv->icon_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = paned->priv->notebook;
+
+ 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_notebook_append_page (GTK_NOTEBOOK (container), widget, NULL);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_attachment_tree_view_new ();
+ GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (widget), paned->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ paned->priv->tree_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Construct the Controls */
+
+ container = GTK_WIDGET (paned);
+
+ widget = gtk_vbox_new (FALSE, 6);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ paned->priv->content_area = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_expander_new (NULL);
+ gtk_expander_set_spacing (GTK_EXPANDER (widget), 0);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ paned->priv->expander = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* The "Add Attachment" button proxies the "add" action from
+ * one of the two attachment views. Doesn't matter which. */
+ widget = gtk_button_new ();
+ view = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+ action = e_attachment_view_get_action (view, "add");
+ gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new ());
+#if GTK_CHECK_VERSION(2,16,0)
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget), action);
+#else
+ gtk_action_connect_proxy (action, widget); /* XXX Deprecated */
+#endif
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_combo_box_new_text ();
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (widget), _("Icon View"));
+ gtk_combo_box_append_text (GTK_COMBO_BOX (widget), _("List View"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->combo_box = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = paned->priv->expander;
+
+ /* Request the width to be as large as possible, and let the
+ * GtkExpander allocate what space there is. This effectively
+ * packs the widget to expand. */
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_widget_set_size_request (widget, G_MAXINT, -1);
+ gtk_expander_set_label_widget (GTK_EXPANDER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("Show _Attachment Bar"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->show_hide_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_alignment_new (0.5, 0.5, 0.0, 1.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "mail-attachment", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->status_icon = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->status_label = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (paned->priv->tree_view));
+
+ g_signal_connect_swapped (
+ selection, "changed",
+ G_CALLBACK (attachment_paned_sync_icon_view), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->icon_view, "selection-changed",
+ G_CALLBACK (attachment_paned_sync_tree_view), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->expander, "notify::expanded",
+ G_CALLBACK (attachment_paned_notify_cb), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->model, "notify::num-attachments",
+ G_CALLBACK (attachment_paned_update_status), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->model, "notify::total-size",
+ G_CALLBACK (attachment_paned_update_status), paned);
+
+ g_object_unref (size_group);
+}
+
+GType
+e_attachment_paned_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentPanedClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_paned_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentPaned),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_paned_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo iface_info = {
+ (GInterfaceInitFunc) attachment_paned_iface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_VPANED, "EAttachmentPaned",
+ &type_info, 0);
+
+ g_type_add_interface_static (
+ type, E_TYPE_ATTACHMENT_VIEW, &iface_info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_attachment_paned_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_PANED, NULL);
+}
+
+GtkWidget *
+e_attachment_paned_get_content_area (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), NULL);
+
+ return paned->priv->content_area;
+}
+
+gint
+e_attachment_paned_get_active_view (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), 0);
+
+ return paned->priv->active_view;
+}
+
+void
+e_attachment_paned_set_active_view (EAttachmentPaned *paned,
+ gint active_view)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+ g_return_if_fail (active_view >= 0 && active_view < NUM_VIEWS);
+
+ paned->priv->active_view = active_view;
+
+ g_object_notify (G_OBJECT (paned), "active-view");
+}
+
+gboolean
+e_attachment_paned_get_expanded (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), FALSE);
+
+ return paned->priv->expanded;
+}
+
+void
+e_attachment_paned_set_expanded (EAttachmentPaned *paned,
+ gboolean expanded)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+
+ paned->priv->expanded = expanded;
+
+ g_object_notify (G_OBJECT (paned), "expanded");
+}
+
+void
+e_attachment_paned_drag_data_received (EAttachmentPaned *paned,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+
+ /* XXX Dirty hack for forwarding drop events. */
+ g_signal_emit_by_name (
+ paned->priv->icon_view, "drag-data-received",
+ context, x, y, selection, info, time);
+}
diff --git a/widgets/misc/e-attachment-paned.h b/widgets/misc/e-attachment-paned.h
new file mode 100644
index 0000000000..076ab6033e
--- /dev/null
+++ b/widgets/misc/e-attachment-paned.h
@@ -0,0 +1,84 @@
+/*
+ * e-attachment-paned.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_ATTACHMENT_PANED_H
+#define E_ATTACHMENT_PANED_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_PANED \
+ (e_attachment_paned_get_type ())
+#define E_ATTACHMENT_PANED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPaned))
+#define E_ATTACHMENT_PANED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedClass))
+#define E_IS_ATTACHMENT_PANED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_PANED))
+#define E_IS_ATTACHMENT_PANED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_PANED))
+#define E_ATTACHMENT_PANED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentPaned EAttachmentPaned;
+typedef struct _EAttachmentPanedClass EAttachmentPanedClass;
+typedef struct _EAttachmentPanedPrivate EAttachmentPanedPrivate;
+
+struct _EAttachmentPaned {
+ GtkVPaned parent;
+ EAttachmentPanedPrivate *priv;
+};
+
+struct _EAttachmentPanedClass {
+ GtkVPanedClass parent_class;
+};
+
+GType e_attachment_paned_get_type (void);
+GtkWidget * e_attachment_paned_new (void);
+GtkWidget * e_attachment_paned_get_content_area
+ (EAttachmentPaned *paned);
+gint e_attachment_paned_get_active_view
+ (EAttachmentPaned *paned);
+void e_attachment_paned_set_active_view
+ (EAttachmentPaned *paned,
+ gint active_view);
+gboolean e_attachment_paned_get_expanded (EAttachmentPaned *paned);
+void e_attachment_paned_set_expanded (EAttachmentPaned *paned,
+ gboolean expanded);
+void e_attachment_paned_drag_data_received
+ (EAttachmentPaned *paned,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_PANED_H */
diff --git a/widgets/misc/e-attachment-store.c b/widgets/misc/e-attachment-store.c
new file mode 100644
index 0000000000..0b6e5a2baa
--- /dev/null
+++ b/widgets/misc/e-attachment-store.c
@@ -0,0 +1,874 @@
+/*
+ * e-attachment-store.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)
+ *
+ */
+
+#include "e-attachment-store.h"
+
+#include <errno.h>
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "e-util/e-util.h"
+#include "e-util/e-mktemp.h"
+#include "e-util/gconf-bridge.h"
+
+#define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
+
+struct _EAttachmentStorePrivate {
+ GHashTable *attachment_index;
+ gchar *current_folder;
+
+ guint ignore_row_changed : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_CURRENT_FOLDER,
+ PROP_NUM_ATTACHMENTS,
+ PROP_NUM_LOADING,
+ PROP_TOTAL_SIZE
+};
+
+static gpointer parent_class;
+
+static void
+attachment_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FOLDER:
+ e_attachment_store_set_current_folder (
+ E_ATTACHMENT_STORE (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FOLDER:
+ g_value_set_string (
+ value,
+ e_attachment_store_get_current_folder (
+ E_ATTACHMENT_STORE (object)));
+ return;
+
+ case PROP_NUM_ATTACHMENTS:
+ g_value_set_uint (
+ value,
+ e_attachment_store_get_num_attachments (
+ E_ATTACHMENT_STORE (object)));
+ return;
+
+ case PROP_NUM_LOADING:
+ g_value_set_uint (
+ value,
+ e_attachment_store_get_num_loading (
+ E_ATTACHMENT_STORE (object)));
+ return;
+
+ case PROP_TOTAL_SIZE:
+ g_value_set_uint64 (
+ value,
+ e_attachment_store_get_total_size (
+ E_ATTACHMENT_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_store_dispose (GObject *object)
+{
+ EAttachmentStorePrivate *priv;
+
+ priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
+
+ g_hash_table_remove_all (priv->attachment_index);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_store_finalize (GObject *object)
+{
+ EAttachmentStorePrivate *priv;
+
+ priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->attachment_index);
+
+ g_free (priv->current_folder);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+attachment_store_constructed (GObject *object)
+{
+ GConfBridge *bridge;
+ const gchar *key;
+
+ bridge = gconf_bridge_get ();
+
+ key = "/apps/evolution/shell/current_folder";
+ gconf_bridge_bind_property (bridge, key, object, "current-folder");
+}
+
+static void
+attachment_store_class_init (EAttachmentStoreClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_store_set_property;
+ object_class->get_property = attachment_store_get_property;
+ object_class->dispose = attachment_store_dispose;
+ object_class->finalize = attachment_store_finalize;
+ object_class->constructed = attachment_store_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CURRENT_FOLDER,
+ g_param_spec_string (
+ "current-folder",
+ "Current Folder",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NUM_ATTACHMENTS,
+ g_param_spec_uint (
+ "num-attachments",
+ "Num Attachments",
+ NULL,
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NUM_LOADING,
+ g_param_spec_uint (
+ "num-loading",
+ "Num Loading",
+ NULL,
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TOTAL_SIZE,
+ g_param_spec_uint64 (
+ "total-size",
+ "Total Size",
+ NULL,
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READABLE));
+}
+
+static void
+attachment_store_init (EAttachmentStore *store)
+{
+ GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
+ GHashTable *attachment_index;
+ gint column = 0;
+
+ attachment_index = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) gtk_tree_row_reference_free);
+
+ store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
+ store->priv->attachment_index = attachment_index;
+
+ types[column++] = E_TYPE_ATTACHMENT; /* COLUMN_ATTACHMENT */
+ types[column++] = G_TYPE_STRING; /* COLUMN_CAPTION */
+ types[column++] = G_TYPE_STRING; /* COLUMN_CONTENT_TYPE */
+ types[column++] = G_TYPE_STRING; /* COLUMN_DESCRIPTION */
+ types[column++] = G_TYPE_ICON; /* COLUMN_ICON */
+ types[column++] = G_TYPE_BOOLEAN; /* COLUMN_LOADING */
+ types[column++] = G_TYPE_INT; /* COLUMN_PERCENT */
+ types[column++] = G_TYPE_BOOLEAN; /* COLUMN_SAVING */
+ types[column++] = G_TYPE_UINT64; /* COLUMN_SIZE */
+
+ g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS);
+
+ gtk_list_store_set_column_types (
+ GTK_LIST_STORE (store), G_N_ELEMENTS (types), types);
+}
+
+GType
+e_attachment_store_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentStoreClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_store_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentStore),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_store_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_LIST_STORE, "EAttachmentStore",
+ &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkTreeModel *
+e_attachment_store_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL);
+}
+
+void
+e_attachment_store_add_attachment (EAttachmentStore *store,
+ EAttachment *attachment)
+{
+ GtkTreeRowReference *reference;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GFile *file;
+
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+ gtk_list_store_append (GTK_LIST_STORE (store), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (store), &iter,
+ E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1);
+
+ model = GTK_TREE_MODEL (store);
+ path = gtk_tree_model_get_path (model, &iter);
+ reference = gtk_tree_row_reference_new (model, path);
+ gtk_tree_path_free (path);
+
+ g_hash_table_insert (
+ store->priv->attachment_index,
+ g_object_ref (attachment), reference);
+
+ file = e_attachment_get_file (attachment);
+
+ /* This lets the attachment tell us when to update. */
+ e_attachment_set_reference (attachment, reference);
+
+ g_object_freeze_notify (G_OBJECT (store));
+ g_object_notify (G_OBJECT (store), "num-attachments");
+ g_object_notify (G_OBJECT (store), "total-size");
+ g_object_thaw_notify (G_OBJECT (store));
+}
+
+gboolean
+e_attachment_store_remove_attachment (EAttachmentStore *store,
+ EAttachment *attachment)
+{
+ GtkTreeRowReference *reference;
+ GHashTable *hash_table;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
+ g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+ hash_table = store->priv->attachment_index;
+ reference = g_hash_table_lookup (hash_table, attachment);
+
+ if (reference == NULL)
+ return FALSE;
+
+ if (!gtk_tree_row_reference_valid (reference)) {
+ g_hash_table_remove (hash_table, attachment);
+ return FALSE;
+ }
+
+ e_attachment_cancel (attachment);
+ e_attachment_set_reference (attachment, NULL);
+
+ model = gtk_tree_row_reference_get_model (reference);
+ path = gtk_tree_row_reference_get_path (reference);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
+ g_hash_table_remove (hash_table, attachment);
+
+ g_object_freeze_notify (G_OBJECT (store));
+ g_object_notify (G_OBJECT (store), "num-attachments");
+ g_object_notify (G_OBJECT (store), "total-size");
+ g_object_thaw_notify (G_OBJECT (store));
+
+ return TRUE;
+}
+
+void
+e_attachment_store_add_to_multipart (EAttachmentStore *store,
+ CamelMultipart *multipart,
+ const gchar *default_charset)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+ g_return_if_fail (CAMEL_MULTIPART (multipart));
+
+ model = GTK_TREE_MODEL (store);
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid) {
+ EAttachment *attachment;
+ gint column_id;
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+ gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+
+ /* Skip the attachment if it's still loading. */
+ if (!e_attachment_get_loading (attachment))
+ e_attachment_add_to_multipart (
+ attachment, multipart, default_charset);
+
+ g_object_unref (attachment);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+}
+
+const gchar *
+e_attachment_store_get_current_folder (EAttachmentStore *store)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+
+ return store->priv->current_folder;
+}
+
+void
+e_attachment_store_set_current_folder (EAttachmentStore *store,
+ const gchar *current_folder)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+
+ if (current_folder == NULL)
+ current_folder = g_get_home_dir ();
+
+ g_free (store->priv->current_folder);
+ store->priv->current_folder = g_strdup (current_folder);
+
+ g_object_notify (G_OBJECT (store), "current-folder");
+}
+
+guint
+e_attachment_store_get_num_attachments (EAttachmentStore *store)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+ return g_hash_table_size (store->priv->attachment_index);
+}
+
+guint
+e_attachment_store_get_num_loading (EAttachmentStore *store)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ guint num_loading = 0;
+ gboolean valid;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+ model = GTK_TREE_MODEL (store);
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid) {
+ EAttachment *attachment;
+ gint column_id;
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+ gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+ if (e_attachment_get_loading (attachment))
+ num_loading++;
+ g_object_unref (attachment);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ return num_loading;
+}
+
+goffset
+e_attachment_store_get_total_size (EAttachmentStore *store)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ goffset total_size = 0;
+ gboolean valid;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+ model = GTK_TREE_MODEL (store);
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid) {
+ EAttachment *attachment;
+ GFileInfo *file_info;
+ gint column_id;
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+ gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+ file_info = e_attachment_get_file_info (attachment);
+ if (file_info != NULL)
+ total_size += g_file_info_get_size (file_info);
+ g_object_unref (attachment);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ return total_size;
+}
+
+gint
+e_attachment_store_run_file_chooser_dialog (EAttachmentStore *store,
+ GtkWidget *dialog)
+{
+ GtkFileChooser *file_chooser;
+ gint response = GTK_RESPONSE_NONE;
+ const gchar *current_folder;
+ gboolean update_folder;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), response);
+ g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), response);
+
+ file_chooser = GTK_FILE_CHOOSER (dialog);
+ current_folder = e_attachment_store_get_current_folder (store);
+ gtk_file_chooser_set_current_folder (file_chooser, current_folder);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ update_folder =
+ (response == GTK_RESPONSE_ACCEPT) ||
+ (response == GTK_RESPONSE_OK) ||
+ (response == GTK_RESPONSE_YES) ||
+ (response == GTK_RESPONSE_APPLY);
+
+ if (update_folder) {
+ gchar *folder;
+
+ folder = gtk_file_chooser_get_current_folder (file_chooser);
+ e_attachment_store_set_current_folder (store, folder);
+ g_free (folder);
+ }
+
+ return response;
+}
+
+void
+e_attachment_store_run_load_dialog (EAttachmentStore *store,
+ GtkWindow *parent)
+{
+ GtkFileChooser *file_chooser;
+ GtkWidget *dialog;
+ GtkWidget *option;
+ GSList *files, *iter;
+ const gchar *disposition;
+ gboolean active;
+ gint response;
+
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+ g_return_if_fail (GTK_IS_WINDOW (parent));
+
+ dialog = gtk_file_chooser_dialog_new (
+ _("Add Attachment"), parent,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ _("A_ttach"), GTK_RESPONSE_OK, NULL);
+
+ file_chooser = GTK_FILE_CHOOSER (dialog);
+ gtk_file_chooser_set_local_only (file_chooser, FALSE);
+ gtk_file_chooser_set_select_multiple (file_chooser, TRUE);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
+
+ option = gtk_check_button_new_with_mnemonic (
+ _("_Suggest automatic display of attachment"));
+ gtk_file_chooser_set_extra_widget (file_chooser, option);
+ gtk_widget_show (option);
+
+ response = e_attachment_store_run_file_chooser_dialog (store, dialog);
+
+ if (response != GTK_RESPONSE_OK)
+ goto exit;
+
+ files = gtk_file_chooser_get_files (file_chooser);
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option));
+ disposition = active ? "inline" : "attachment";
+
+ for (iter = files; iter != NULL; iter = g_slist_next (iter)) {
+ EAttachment *attachment;
+ GFile *file = iter->data;
+
+ attachment = e_attachment_new ();
+ e_attachment_set_file (attachment, file);
+ e_attachment_store_add_attachment (store, attachment);
+ e_attachment_load_async (
+ attachment, (GAsyncReadyCallback)
+ e_attachment_load_handle_error, parent);
+ g_object_unref (attachment);
+ }
+
+ g_slist_foreach (files, (GFunc) g_object_unref, NULL);
+ g_slist_free (files);
+
+exit:
+ gtk_widget_destroy (dialog);
+}
+
+GFile *
+e_attachment_store_run_save_dialog (EAttachmentStore *store,
+ GList *attachment_list,
+ GtkWindow *parent)
+{
+ GtkFileChooser *file_chooser;
+ GtkFileChooserAction action;
+ GtkWidget *dialog;
+ GFile *destination;
+ const gchar *title;
+ gint response;
+ guint length;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+
+ length = g_list_length (attachment_list);
+
+ if (length == 0)
+ return NULL;
+
+ title = ngettext ("Save Attachment", "Save Attachments", length);
+
+ if (length == 1)
+ action = GTK_FILE_CHOOSER_ACTION_SAVE;
+ else
+ action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+
+ dialog = gtk_file_chooser_dialog_new (
+ title, parent, action,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL);
+
+ file_chooser = GTK_FILE_CHOOSER (dialog);
+ gtk_file_chooser_set_local_only (file_chooser, FALSE);
+ gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
+
+ if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
+ EAttachment *attachment;
+ GFileInfo *file_info;
+ const gchar *name = NULL;
+
+ attachment = attachment_list->data;
+ file_info = e_attachment_get_file_info (attachment);
+ if (file_info != NULL)
+ name = g_file_info_get_display_name (file_info);
+ if (name == NULL)
+ /* Translators: Default attachment filename. */
+ name = _("attachment.dat");
+ gtk_file_chooser_set_current_name (file_chooser, name);
+ }
+
+ response = e_attachment_store_run_file_chooser_dialog (store, dialog);
+
+ if (response == GTK_RESPONSE_OK)
+ destination = gtk_file_chooser_get_file (file_chooser);
+ else
+ destination = NULL;
+
+ gtk_widget_destroy (dialog);
+
+ return destination;
+}
+
+/******************** e_attachment_store_get_uris_async() ********************/
+
+typedef struct _UriContext UriContext;
+
+struct _UriContext {
+ GSimpleAsyncResult *simple;
+ GList *attachment_list;
+ GError *error;
+ gchar **uris;
+ gint index;
+};
+
+static UriContext *
+attachment_store_uri_context_new (EAttachmentStore *store,
+ GList *attachment_list,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ UriContext *uri_context;
+ GSimpleAsyncResult *simple;
+ guint length;
+ gchar **uris;
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback, user_data,
+ e_attachment_store_get_uris_async);
+
+ /* Add one for NULL terminator. */
+ length = g_list_length (attachment_list) + 1;
+ uris = g_malloc0 (sizeof (gchar *) * length);
+
+ uri_context = g_slice_new0 (UriContext);
+ uri_context->simple = simple;
+ uri_context->attachment_list = g_list_copy (attachment_list);
+ uri_context->uris = uris;
+
+ g_list_foreach (
+ uri_context->attachment_list,
+ (GFunc) g_object_ref, NULL);
+
+ return uri_context;
+}
+
+static void
+attachment_store_uri_context_free (UriContext *uri_context)
+{
+ /* Do not free the GSimpleAsyncResult. */
+
+ /* The attachment list should be empty now. */
+ g_warn_if_fail (uri_context->attachment_list == NULL);
+
+ /* So should the error. */
+ g_warn_if_fail (uri_context->error == NULL);
+
+ g_strfreev (uri_context->uris);
+
+ g_slice_free (UriContext, uri_context);
+}
+
+static void
+attachment_store_get_uris_save_cb (EAttachment *attachment,
+ GAsyncResult *result,
+ UriContext *uri_context)
+{
+ GSimpleAsyncResult *simple;
+ GFile *file;
+ gchar **uris;
+ gchar *uri;
+ GError *error = NULL;
+
+ file = e_attachment_save_finish (attachment, result, &error);
+
+ /* Remove the attachment from the list. */
+ uri_context->attachment_list = g_list_remove (
+ uri_context->attachment_list, attachment);
+ g_object_unref (attachment);
+
+ if (file != NULL) {
+ uri = g_file_get_uri (file);
+ uri_context->uris[uri_context->index++] = uri;
+ g_object_unref (file);
+
+ } else if (error != NULL) {
+ /* If this is the first error, cancel the other jobs. */
+ if (uri_context->error == NULL) {
+ g_propagate_error (&uri_context->error, error);
+ g_list_foreach (
+ uri_context->attachment_list,
+ (GFunc) e_attachment_cancel, NULL);
+ error = NULL;
+
+ /* Otherwise, we can only report back one error. So if
+ * this is something other than cancellation, dump it to
+ * the terminal. */
+ } else if (!g_error_matches (
+ error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (error != NULL)
+ g_error_free (error);
+
+ /* If there's still jobs running, let them finish. */
+ if (uri_context->attachment_list != NULL)
+ return;
+
+ /* Steal the result. */
+ simple = uri_context->simple;
+ uri_context->simple = NULL;
+
+ /* And the URI list. */
+ uris = uri_context->uris;
+ uri_context->uris = NULL;
+
+ /* And the error. */
+ error = uri_context->error;
+ uri_context->error = NULL;
+
+ if (error == NULL)
+ g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+ else {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete (simple);
+
+ attachment_store_uri_context_free (uri_context);
+}
+
+void
+e_attachment_store_get_uris_async (EAttachmentStore *store,
+ GList *attachment_list,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GFile *temp_directory;
+ UriContext *uri_context;
+ GList *iter, *trash = NULL;
+ gchar *template;
+ gchar *path;
+
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+ g_return_if_fail (callback != NULL);
+
+ uri_context = attachment_store_uri_context_new (
+ store, attachment_list, callback, user_data);
+
+ /* Grab the copied attachment list. */
+ attachment_list = uri_context->attachment_list;
+
+ /* First scan the list for attachments with a GFile. */
+ for (iter = attachment_list; iter != NULL; iter = iter->next) {
+ EAttachment *attachment = iter->data;
+ GFile *file;
+ gchar *uri;
+
+ file = e_attachment_get_file (attachment);
+ if (file == NULL)
+ continue;
+
+ uri = g_file_get_uri (file);
+ uri_context->uris[uri_context->index++] = uri;
+
+ /* Mark the list node for deletion. */
+ trash = g_list_prepend (trash, iter);
+ g_object_unref (attachment);
+ }
+
+ /* Expunge the list. */
+ for (iter = trash; iter != NULL; iter = iter->next) {
+ GList *link = iter->data;
+ attachment_list = g_list_delete_link (attachment_list, link);
+ }
+ g_list_free (trash);
+
+ uri_context->attachment_list = attachment_list;
+
+ /* Any remaining attachments in the list should have MIME parts
+ * only, so we need to save them all to a temporary directory.
+ * We use a directory so the files can retain their basenames. */
+ template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+ path = e_mkdtemp (template);
+ g_free (template);
+
+ if (path == NULL) {
+ GSimpleAsyncResult *simple;
+
+ /* Steal the result. */
+ simple = uri_context->simple;
+ uri_context->simple = NULL;
+
+ g_simple_async_result_set_error (
+ simple, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+
+ g_simple_async_result_complete_in_idle (simple);
+ attachment_store_uri_context_free (uri_context);
+ return;
+ }
+
+ temp_directory = g_file_new_for_path (path);
+
+ for (iter = attachment_list; iter != NULL; iter = iter->next)
+ e_attachment_save_async (
+ E_ATTACHMENT (iter->data),
+ temp_directory, (GAsyncReadyCallback)
+ attachment_store_get_uris_save_cb,
+ uri_context);
+
+ g_object_unref (temp_directory);
+}
+
+gchar **
+e_attachment_store_get_uris_finish (EAttachmentStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ gchar **uris;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ uris = g_simple_async_result_get_op_res_gpointer (simple);
+ g_simple_async_result_propagate_error (simple, error);
+ g_object_unref (simple);
+
+ return uris;
+}
diff --git a/widgets/misc/e-attachment-store.h b/widgets/misc/e-attachment-store.h
new file mode 100644
index 0000000000..5d2271cba0
--- /dev/null
+++ b/widgets/misc/e-attachment-store.h
@@ -0,0 +1,122 @@
+/*
+ * e-attachment-store.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_ATTACHMENT_STORE_H
+#define E_ATTACHMENT_STORE_H
+
+#include <gtk/gtk.h>
+#include <widgets/misc/e-attachment.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_STORE \
+ (e_attachment_store_get_type ())
+#define E_ATTACHMENT_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStore))
+#define E_ATTACHMENT_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_STORE, EAttachmentStoreClass))
+#define E_IS_ATTACHMENT_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_STORE))
+#define E_IS_ATTACHMENT_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_STORE))
+#define E_ATTACHMENT_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentStore EAttachmentStore;
+typedef struct _EAttachmentStoreClass EAttachmentStoreClass;
+typedef struct _EAttachmentStorePrivate EAttachmentStorePrivate;
+
+struct _EAttachmentStore {
+ GtkListStore parent;
+ EAttachmentStorePrivate *priv;
+};
+
+struct _EAttachmentStoreClass {
+ GtkListStoreClass parent_class;
+};
+
+enum {
+ E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, /* E_TYPE_ATTACHMENT */
+ E_ATTACHMENT_STORE_COLUMN_CAPTION, /* G_TYPE_STRING */
+ E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, /* G_TYPE_STRING */
+ E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, /* G_TYPE_STRING */
+ E_ATTACHMENT_STORE_COLUMN_ICON, /* G_TYPE_ICON */
+ E_ATTACHMENT_STORE_COLUMN_LOADING, /* G_TYPE_BOOLEAN */
+ E_ATTACHMENT_STORE_COLUMN_PERCENT, /* G_TYPE_INT */
+ E_ATTACHMENT_STORE_COLUMN_SAVING, /* G_TYPE_BOOLEAN */
+ E_ATTACHMENT_STORE_COLUMN_SIZE, /* G_TYPE_UINT64 */
+ E_ATTACHMENT_STORE_NUM_COLUMNS
+};
+
+GType e_attachment_store_get_type (void);
+GtkTreeModel * e_attachment_store_new (void);
+void e_attachment_store_add_attachment
+ (EAttachmentStore *store,
+ EAttachment *attachment);
+gboolean e_attachment_store_remove_attachment
+ (EAttachmentStore *store,
+ EAttachment *attachment);
+void e_attachment_store_add_to_multipart
+ (EAttachmentStore *store,
+ CamelMultipart *multipart,
+ const gchar *default_charset);
+const gchar * e_attachment_store_get_current_folder
+ (EAttachmentStore *store);
+void e_attachment_store_set_current_folder
+ (EAttachmentStore *store,
+ const gchar *current_folder);
+guint e_attachment_store_get_num_attachments
+ (EAttachmentStore *store);
+guint e_attachment_store_get_num_loading
+ (EAttachmentStore *store);
+goffset e_attachment_store_get_total_size
+ (EAttachmentStore *store);
+gint e_attachment_store_run_file_chooser_dialog
+ (EAttachmentStore *store,
+ GtkWidget *dialog);
+void e_attachment_store_run_load_dialog
+ (EAttachmentStore *store,
+ GtkWindow *parent);
+GFile * e_attachment_store_run_save_dialog
+ (EAttachmentStore *store,
+ GList *attachment_list,
+ GtkWindow *parent);
+
+/* Asynchronous Operations */
+void e_attachment_store_get_uris_async
+ (EAttachmentStore *store,
+ GList *attachment_list,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gchar ** e_attachment_store_get_uris_finish
+ (EAttachmentStore *store,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_STORE_H */
diff --git a/widgets/misc/e-attachment-tree-view.c b/widgets/misc/e-attachment-tree-view.c
new file mode 100644
index 0000000000..f2c17cb078
--- /dev/null
+++ b/widgets/misc/e-attachment-tree-view.c
@@ -0,0 +1,605 @@
+/*
+ * e-attachment-tree-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)
+ *
+ */
+
+#include "e-attachment-tree-view.h"
+
+#include <glib/gi18n.h>
+
+#include "e-attachment.h"
+#include "e-attachment-store.h"
+#include "e-attachment-view.h"
+
+#define E_ATTACHMENT_TREE_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewPrivate))
+
+struct _EAttachmentTreeViewPrivate {
+ EAttachmentViewPrivate view_priv;
+};
+
+enum {
+ PROP_0,
+ PROP_EDITABLE
+};
+
+static gpointer parent_class;
+
+static void
+attachment_tree_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_tree_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_tree_view_dispose (GObject *object)
+{
+ e_attachment_view_dispose (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+attachment_tree_view_finalize (GObject *object)
+{
+ e_attachment_view_finalize (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+attachment_tree_view_render_size (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gchar *display_size;
+ gint column_id;
+ guint64 size;
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_SIZE;
+ gtk_tree_model_get (model, iter, column_id, &size, -1);
+
+ display_size = g_format_size_for_display ((goffset) size);
+ g_object_set (renderer, "text", display_size, NULL);
+ g_free (display_size);
+}
+
+static gboolean
+attachment_tree_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_press_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ button_press_event (widget, event);
+}
+
+static gboolean
+attachment_tree_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_release_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_release_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ button_release_event (widget, event);
+}
+
+static gboolean
+attachment_tree_view_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_key_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's key_press_event() method. */
+ return GTK_WIDGET_CLASS (parent_class)->
+ key_press_event (widget, event);
+}
+
+static void
+attachment_tree_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_begin() method. */
+ GTK_WIDGET_CLASS (parent_class)->drag_begin (widget, context);
+
+ e_attachment_view_drag_begin (view, context);
+}
+
+static void
+attachment_tree_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_end() method. */
+ GTK_WIDGET_CLASS (parent_class)->drag_end (widget, context);
+
+ e_attachment_view_drag_end (view, context);
+}
+
+static void
+attachment_tree_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static gboolean
+attachment_tree_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ return e_attachment_view_drag_motion (view, context, x, y, time);
+}
+
+static gboolean
+attachment_tree_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (!e_attachment_view_drag_drop (view, context, x, y, time))
+ return FALSE;
+
+ /* Chain up to parent's drag_drop() method. */
+ return GTK_WIDGET_CLASS (parent_class)->drag_drop (
+ widget, context, x, y, time);
+}
+
+static void
+attachment_tree_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_received (
+ view, context, x, y, selection, info, time);
+}
+
+static gboolean
+attachment_tree_view_popup_menu (GtkWidget *widget)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_show_popup_menu (view, NULL, NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+attachment_tree_view_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (tree_view);
+
+ e_attachment_view_open_path (view, path, NULL);
+}
+
+static EAttachmentViewPrivate *
+attachment_tree_view_get_private (EAttachmentView *view)
+{
+ EAttachmentTreeViewPrivate *priv;
+
+ priv = E_ATTACHMENT_TREE_VIEW_GET_PRIVATE (view);
+
+ return &priv->view_priv;
+}
+
+static EAttachmentStore *
+attachment_tree_view_get_store (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+
+ tree_view = GTK_TREE_VIEW (view);
+ model = gtk_tree_view_get_model (tree_view);
+
+ return E_ATTACHMENT_STORE (model);
+}
+
+static GtkTreePath *
+attachment_tree_view_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ GtkTreeView *tree_view;
+ GtkTreePath *path;
+ gboolean row_exists;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ row_exists = gtk_tree_view_get_path_at_pos (
+ tree_view, x, y, &path, NULL, NULL, NULL);
+
+ return row_exists ? path : NULL;
+}
+
+static GList *
+attachment_tree_view_get_selected_paths (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ return gtk_tree_selection_get_selected_rows (selection, NULL);
+}
+
+static gboolean
+attachment_tree_view_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ return gtk_tree_selection_path_is_selected (selection, path);
+}
+
+static void
+attachment_tree_view_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_select_path (selection, path);
+}
+
+static void
+attachment_tree_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_unselect_path (selection, path);
+}
+
+static void
+attachment_tree_view_select_all (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_select_all (selection);
+}
+
+static void
+attachment_tree_view_unselect_all (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_unselect_all (selection);
+}
+
+static void
+attachment_tree_view_drag_source_set (EAttachmentView *view,
+ GdkModifierType start_button_mask,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_enable_model_drag_source (
+ tree_view, start_button_mask, targets, n_targets, actions);
+}
+
+static void
+attachment_tree_view_drag_dest_set (EAttachmentView *view,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_enable_model_drag_dest (
+ tree_view, targets, n_targets, actions);
+}
+
+static void
+attachment_tree_view_drag_source_unset (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_unset_rows_drag_source (tree_view);
+}
+
+static void
+attachment_tree_view_drag_dest_unset (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_unset_rows_drag_dest (tree_view);
+}
+
+static void
+attachment_tree_view_class_init (EAttachmentTreeViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkTreeViewClass *tree_view_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_tree_view_set_property;
+ object_class->get_property = attachment_tree_view_get_property;
+ object_class->dispose = attachment_tree_view_dispose;
+ object_class->finalize = attachment_tree_view_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = attachment_tree_view_button_press_event;
+ widget_class->button_release_event = attachment_tree_view_button_release_event;
+ widget_class->key_press_event = attachment_tree_view_key_press_event;
+ widget_class->drag_begin = attachment_tree_view_drag_begin;
+ widget_class->drag_end = attachment_tree_view_drag_end;
+ widget_class->drag_data_get = attachment_tree_view_drag_data_get;
+ widget_class->drag_motion = attachment_tree_view_drag_motion;
+ widget_class->drag_drop = attachment_tree_view_drag_drop;
+ widget_class->drag_data_received = attachment_tree_view_drag_data_received;
+ widget_class->popup_menu = attachment_tree_view_popup_menu;
+
+ tree_view_class = GTK_TREE_VIEW_CLASS (class);
+ tree_view_class->row_activated = attachment_tree_view_row_activated;
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+attachment_tree_view_iface_init (EAttachmentViewIface *iface)
+{
+ iface->get_private = attachment_tree_view_get_private;
+ iface->get_store = attachment_tree_view_get_store;
+
+ iface->get_path_at_pos = attachment_tree_view_get_path_at_pos;
+ iface->get_selected_paths = attachment_tree_view_get_selected_paths;
+ iface->path_is_selected = attachment_tree_view_path_is_selected;
+ iface->select_path = attachment_tree_view_select_path;
+ iface->unselect_path = attachment_tree_view_unselect_path;
+ iface->select_all = attachment_tree_view_select_all;
+ iface->unselect_all = attachment_tree_view_unselect_all;
+
+ iface->drag_source_set = attachment_tree_view_drag_source_set;
+ iface->drag_dest_set = attachment_tree_view_drag_dest_set;
+ iface->drag_source_unset = attachment_tree_view_drag_source_unset;
+ iface->drag_dest_unset = attachment_tree_view_drag_dest_unset;
+}
+
+static void
+attachment_tree_view_init (EAttachmentTreeView *tree_view)
+{
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ tree_view->priv = E_ATTACHMENT_TREE_VIEW_GET_PRIVATE (tree_view);
+
+ e_attachment_view_init (E_ATTACHMENT_VIEW (tree_view));
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree_view), TRUE);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+ /* Name Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_spacing (column, 3);
+ gtk_tree_view_column_set_title (column, _("Description"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "text",
+ E_ATTACHMENT_STORE_COLUMN_DESCRIPTION);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Loading"), NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Saving"), NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_SAVING);
+
+ /* Size Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Size"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_set_cell_data_func (
+ column, renderer, (GtkTreeCellDataFunc)
+ attachment_tree_view_render_size, NULL, NULL);
+
+ /* Type Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Type"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "text",
+ E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE);
+}
+
+GType
+e_attachment_tree_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentTreeViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_tree_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EAttachmentTreeView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) attachment_tree_view_init,
+ NULL /* value_table */
+ };
+
+ static const GInterfaceInfo iface_info = {
+ (GInterfaceInitFunc) attachment_tree_view_iface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface_data */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_TREE_VIEW, "EAttachmentTreeView",
+ &type_info, 0);
+
+ g_type_add_interface_static (
+ type, E_TYPE_ATTACHMENT_VIEW, &iface_info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_attachment_tree_view_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_TREE_VIEW, NULL);
+}
diff --git a/widgets/misc/e-attachment-tree-view.h b/widgets/misc/e-attachment-tree-view.h
new file mode 100644
index 0000000000..7f16ba5ab2
--- /dev/null
+++ b/widgets/misc/e-attachment-tree-view.h
@@ -0,0 +1,66 @@
+/*
+ * e-attachment-tree-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_ATTACHMENT_TREE_VIEW_H
+#define E_ATTACHMENT_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_TREE_VIEW \
+ (e_attachment_tree_view_get_type ())
+#define E_ATTACHMENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeView))
+#define E_ATTACHMENT_TREE_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewClass))
+#define E_IS_ATTACHMENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_TREE_VIEW))
+#define E_IS_ATTACHMENT_TREE_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_TREE_VIEW))
+#define E_ATTACHMENT_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentTreeView EAttachmentTreeView;
+typedef struct _EAttachmentTreeViewClass EAttachmentTreeViewClass;
+typedef struct _EAttachmentTreeViewPrivate EAttachmentTreeViewPrivate;
+
+struct _EAttachmentTreeView {
+ GtkTreeView parent;
+ EAttachmentTreeViewPrivate *priv;
+};
+
+struct _EAttachmentTreeViewClass {
+ GtkTreeViewClass parent_class;
+};
+
+GType e_attachment_tree_view_get_type (void);
+GtkWidget * e_attachment_tree_view_new (void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_TREE_VIEW_H */
diff --git a/widgets/misc/e-attachment-view.c b/widgets/misc/e-attachment-view.c
new file mode 100644
index 0000000000..53f719c787
--- /dev/null
+++ b/widgets/misc/e-attachment-view.c
@@ -0,0 +1,1637 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-attachment-view.h"
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <camel/camel-stream-mem.h>
+
+#include "e-util/e-binding.h"
+#include "e-util/e-util.h"
+#include "e-attachment-dialog.h"
+#include "e-attachment-handler-image.h"
+
+enum {
+ UPDATE_ACTIONS,
+ LAST_SIGNAL
+};
+
+/* Note: Do not use the info field. */
+static GtkTargetEntry target_table[] = {
+ { "_NETSCAPE_URL", 0, 0 },
+ { "text/x-vcard", 0, 0 },
+ { "text/calendar", 0, 0 }
+};
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <menuitem action='cancel'/>"
+" <menuitem action='save-as'/>"
+" <menuitem action='remove'/>"
+" <menuitem action='properties'/>"
+" <separator/>"
+" <placeholder name='inline-actions'>"
+" <menuitem action='show'/>"
+" <menuitem action='hide'/>"
+" </placeholder>"
+" <separator/>"
+" <placeholder name='custom-actions'/>"
+" <separator/>"
+" <menuitem action='add'/>"
+" <separator/>"
+" <placeholder name='open-actions'/>"
+" </popup>"
+"</ui>";
+
+static gulong signals[LAST_SIGNAL];
+
+static void
+action_add_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachmentStore *store;
+ gpointer parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_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 *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = selected->data;
+
+ e_attachment_cancel (attachment);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_hide_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = selected->data;
+
+ e_attachment_set_shown (attachment, FALSE);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_open_in_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ GAppInfo *app_info;
+ GtkTreePath *path;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_paths (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ path = selected->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 (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_properties_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ GtkWidget *dialog;
+ GList *selected;
+ gpointer parent;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = selected->data;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ dialog = e_attachment_dialog_new (parent, attachment);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_recent_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ GtkRecentChooser *chooser;
+ EAttachmentStore *store;
+ EAttachment *attachment;
+ gpointer parent;
+ gchar *uri;
+
+ chooser = GTK_RECENT_CHOOSER (action);
+ store = e_attachment_view_get_store (view);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ uri = gtk_recent_chooser_get_current_uri (chooser);
+ attachment = e_attachment_new_for_uri (uri);
+ e_attachment_store_add_attachment (store, attachment);
+ e_attachment_load_async (
+ attachment, (GAsyncReadyCallback)
+ e_attachment_load_handle_error, parent);
+ g_free (uri);
+}
+
+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 *selected, *iter;
+ GFile *destination;
+ gpointer parent;
+
+ store = e_attachment_view_get_store (view);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ /* XXX We lose the previous selection. */
+ e_attachment_view_select_all (view);
+ selected = e_attachment_view_get_selected_attachments (view);
+ e_attachment_view_unselect_all (view);
+
+ destination = e_attachment_store_run_save_dialog (
+ store, selected, parent);
+
+ if (destination == NULL)
+ goto exit;
+
+ for (iter = selected; 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 (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_save_as_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachmentStore *store;
+ GList *selected, *iter;
+ GFile *destination;
+ gpointer parent;
+
+ store = e_attachment_view_get_store (view);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+
+ destination = e_attachment_store_run_save_dialog (
+ store, selected, parent);
+
+ if (destination == NULL)
+ goto exit;
+
+ for (iter = selected; 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 (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static void
+action_show_cb (GtkAction *action,
+ EAttachmentView *view)
+{
+ EAttachment *attachment;
+ GList *selected;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (g_list_length (selected) == 1);
+ attachment = selected->data;
+
+ e_attachment_set_shown (attachment, TRUE);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+ { "cancel",
+ GTK_STOCK_CANCEL,
+ NULL,
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_cancel_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) },
+
+ { "show",
+ NULL,
+ N_("_View Inline"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_show_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_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)
+{
+ static GdkAtom atom = GDK_NONE;
+ EAttachmentStore *store;
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ GdkAtom data_type;
+ const gchar *data;
+ gpointer parent;
+ gchar *content_type;
+ gint length;
+
+ if (G_UNLIKELY (atom = GDK_NONE))
+ atom = gdk_atom_intern_static_string ("text/calendar");
+
+ if (gtk_selection_data_get_target (selection_data) != atom)
+ 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_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);
+
+ camel_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)
+{
+ static GdkAtom atom = GDK_NONE;
+ EAttachmentStore *store;
+ EAttachment *attachment;
+ CamelMimePart *mime_part;
+ GdkAtom data_type;
+ const gchar *data;
+ gpointer parent;
+ gchar *content_type;
+ gint length;
+
+ if (G_UNLIKELY (atom = GDK_NONE))
+ atom = gdk_atom_intern_static_string ("text/x-vcard");
+
+ if (gtk_selection_data_get_target (selection_data) != atom)
+ 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_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);
+
+ camel_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_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;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GList *list, *iter;
+ guint n_selected;
+ gboolean busy = FALSE;
+ gboolean can_show = FALSE;
+ gboolean shown = FALSE;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ priv = e_attachment_view_get_private (view);
+ 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);
+
+ 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);
+
+ /* 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);
+
+ 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;
+
+ if (!g_app_info_should_show (app_info))
+ continue;
+
+ 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-in-%s", app_executable);
+ action_label = g_strdup_printf (_("Open in %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);
+
+#if GTK_CHECK_VERSION(2,16,0)
+ gtk_action_set_gicon (action, app_icon);
+#endif
+
+ 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_in_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_handlers (EAttachmentView *view)
+{
+ EAttachmentViewPrivate *priv;
+ GtkTargetList *target_list;
+ GType *children;
+ guint ii;
+
+ 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);
+
+ priv->handlers = g_ptr_array_new ();
+ priv->target_list = target_list;
+ priv->drag_actions = GDK_ACTION_COPY;
+
+ children = g_type_children (E_TYPE_ATTACHMENT_HANDLER, NULL);
+
+ for (ii = 0; children[ii] != G_TYPE_INVALID; ii++) {
+ EAttachmentHandler *handler;
+ const GtkTargetEntry *targets;
+ guint n_targets;
+
+ handler = g_object_new (children[ii], "view", view, NULL);
+
+ targets = e_attachment_handler_get_target_table (
+ handler, &n_targets);
+ gtk_target_list_add_table (target_list, targets, n_targets);
+ priv->drag_actions |=
+ e_attachment_handler_get_drag_actions (handler);
+
+ g_ptr_array_add (priv->handlers, handler);
+ }
+
+ g_free (children);
+}
+
+static void
+attachment_view_class_init (EAttachmentViewIface *iface)
+{
+ iface->update_actions = attachment_view_update_actions;
+
+ g_object_interface_install_property (
+ iface,
+ 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 (iface),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EAttachmentViewIface, update_actions),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+GType
+e_attachment_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EAttachmentViewIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) attachment_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ 0, /* instance_size */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ G_TYPE_INTERFACE, "EAttachmentView", &type_info, 0);
+
+ g_type_interface_add_prerequisite (type, GTK_TYPE_WIDGET);
+
+ /* Register known handler types. */
+ e_attachment_handler_image_get_type ();
+ }
+
+ return type;
+}
+
+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 = gtk_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");
+
+ e_mutual_binding_new (
+ G_OBJECT (view), "editable",
+ G_OBJECT (action_group), "visible");
+ 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_handlers (view);
+
+ e_attachment_view_drag_source_set (view);
+ e_attachment_view_drag_dest_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);
+
+ g_ptr_array_foreach (priv->handlers, (GFunc) g_object_unref, NULL);
+ g_ptr_array_set_size (priv->handlers, 0);
+
+ 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_ptr_array_free (priv->handlers, TRUE);
+}
+
+EAttachmentViewPrivate *
+e_attachment_view_get_private (EAttachmentView *view)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_val_if_fail (iface->get_private != NULL, NULL);
+
+ return iface->get_private (view);
+}
+
+EAttachmentStore *
+e_attachment_view_get_store (EAttachmentView *view)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_val_if_fail (iface->get_store != NULL, NULL);
+
+ return iface->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;
+
+ g_object_notify (G_OBJECT (view), "editable");
+}
+
+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;
+}
+
+GList *
+e_attachment_view_get_selected_attachments (EAttachmentView *view)
+{
+ EAttachmentStore *store;
+ GtkTreeModel *model;
+ GList *selected, *item;
+ gint column_id;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+ selected = 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 = selected; 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 selected;
+}
+
+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);
+
+ 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_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 *selected, *item;
+ gint column_id;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+ selected = e_attachment_view_get_selected_paths (view);
+ store = e_attachment_view_get_store (view);
+ model = GTK_TREE_MODEL (store);
+
+ for (item = selected; 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 (selected) == 1) {
+ GtkTreePath *path = selected->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 (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+}
+
+gboolean
+e_attachment_view_button_press_event (EAttachmentView *view,
+ GdkEventButton *event)
+{
+ GtkTreePath *path;
+ gboolean editable;
+ gboolean item_clicked;
+
+ 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 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. */
+ path = e_attachment_view_get_path_at_pos (view, event->x, event->y);
+ if (path != NULL) {
+ if (!e_attachment_view_path_is_selected (view, path)) {
+ e_attachment_view_unselect_all (view);
+ e_attachment_view_select_path (view, path);
+ }
+ gtk_tree_path_free (path);
+ item_clicked = TRUE;
+ } else {
+ e_attachment_view_unselect_all (view);
+ item_clicked = FALSE;
+ }
+
+ /* Cancel drag and drop if there are no selected items,
+ * or if any of the selected items are loading or saving. */
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+ GList *selected, *iter;
+ gboolean busy = FALSE;
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ for (iter = selected; iter != NULL; iter = iter->next) {
+ EAttachment *attachment = iter->data;
+ busy |= e_attachment_get_loading (attachment);
+ busy |= e_attachment_get_saving (attachment);
+ }
+ if (selected == NULL || busy)
+ e_attachment_view_drag_source_unset (view);
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+ }
+
+ if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
+ /* 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 (item_clicked || editable) {
+ e_attachment_view_show_popup_menu (
+ view, event, NULL, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+e_attachment_view_button_release_event (EAttachmentView *view,
+ GdkEventButton *event)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ /* Restore the attachment view as a drag source, in case
+ * we had to cancel during a button press event. */
+ if (event->button == 1)
+ e_attachment_view_drag_source_set (view);
+
+ return FALSE;
+}
+
+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_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)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_val_if_fail (iface->get_path_at_pos != NULL, NULL);
+
+ return iface->get_path_at_pos (view, x, y);
+}
+
+GList *
+e_attachment_view_get_selected_paths (EAttachmentView *view)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_val_if_fail (iface->get_selected_paths != NULL, NULL);
+
+ return iface->get_selected_paths (view);
+}
+
+gboolean
+e_attachment_view_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_val_if_fail (iface->path_is_selected != NULL, FALSE);
+
+ return iface->path_is_selected (view, path);
+}
+
+void
+e_attachment_view_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+ g_return_if_fail (path != NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_if_fail (iface->select_path != NULL);
+
+ iface->select_path (view, path);
+}
+
+void
+e_attachment_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+ g_return_if_fail (path != NULL);
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_if_fail (iface->unselect_path != NULL);
+
+ iface->unselect_path (view, path);
+}
+
+void
+e_attachment_view_select_all (EAttachmentView *view)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_if_fail (iface->select_all != NULL);
+
+ iface->select_all (view);
+}
+
+void
+e_attachment_view_unselect_all (EAttachmentView *view)
+{
+ EAttachmentViewIface *iface;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
+ g_return_if_fail (iface->unselect_all != NULL);
+
+ iface->unselect_all (view);
+}
+
+void
+e_attachment_view_sync_selection (EAttachmentView *view,
+ EAttachmentView *target)
+{
+ GList *selected, *iter;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (target));
+
+ selected = e_attachment_view_get_selected_paths (view);
+ e_attachment_view_unselect_all (target);
+
+ for (iter = selected; iter != NULL; iter = iter->next)
+ e_attachment_view_select_path (target, iter->data);
+
+ g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+}
+
+void
+e_attachment_view_drag_source_set (EAttachmentView *view)
+{
+ GtkTargetEntry *targets;
+ GtkTargetList *list;
+ gint n_targets;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ 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);
+
+ gtk_drag_source_set (
+ GTK_WIDGET (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)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ gtk_drag_source_unset (GTK_WIDGET (view));
+}
+
+void
+e_attachment_view_drag_begin (EAttachmentView *view,
+ GdkDragContext *context)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+ g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+}
+
+void
+e_attachment_view_drag_end (EAttachmentView *view,
+ GdkDragContext *context)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+ g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+}
+
+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)
+{
+ EAttachmentStore *store;
+ GList *selected;
+
+ 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;
+
+ store = e_attachment_view_get_store (view);
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ if (selected == NULL)
+ return;
+
+ e_attachment_store_get_uris_async (
+ store, selected, (GAsyncReadyCallback)
+ attachment_view_got_uris_cb, &status);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+
+ /* 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;
+ GtkTargetEntry *targets;
+ gint n_targets;
+
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ priv = e_attachment_view_get_private (view);
+
+ targets = gtk_target_table_new_from_list (
+ priv->target_list, &n_targets);
+
+ gtk_drag_dest_set (
+ GTK_WIDGET (view), GTK_DEST_DEFAULT_ALL,
+ targets, n_targets, priv->drag_actions);
+
+ gtk_target_table_free (targets, n_targets);
+}
+
+void
+e_attachment_view_drag_dest_unset (EAttachmentView *view)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+ gtk_drag_dest_unset (GTK_WIDGET (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;
+
+ actions = priv->drag_actions & context->actions;
+ chosen_action = context->suggested_action;
+
+ 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're not editable. */
+ if (!e_attachment_view_get_editable (view))
+ return FALSE;
+
+ return TRUE;
+}
+
+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;
+}
+
+GtkAction *
+e_attachment_view_recent_action_new (EAttachmentView *view,
+ const gchar *action_name,
+ const gchar *action_label)
+{
+ GtkAction *action;
+ GtkRecentChooser *chooser;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ action = gtk_recent_action_new (
+ action_name, action_label, NULL, NULL);
+ gtk_recent_action_set_show_numbers (GTK_RECENT_ACTION (action), TRUE);
+
+ chooser = GTK_RECENT_CHOOSER (action);
+ gtk_recent_chooser_set_show_icons (chooser, TRUE);
+ gtk_recent_chooser_set_show_not_found (chooser, FALSE);
+ gtk_recent_chooser_set_show_private (chooser, FALSE);
+ gtk_recent_chooser_set_show_tips (chooser, TRUE);
+ gtk_recent_chooser_set_sort_type (chooser, GTK_RECENT_SORT_MRU);
+
+ g_signal_connect (
+ action, "item-activated",
+ G_CALLBACK (action_recent_cb), view);
+
+ return action;
+}
+
+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);
+}
diff --git a/widgets/misc/e-attachment-view.h b/widgets/misc/e-attachment-view.h
new file mode 100644
index 0000000000..0fc5118669
--- /dev/null
+++ b/widgets/misc/e-attachment-view.h
@@ -0,0 +1,227 @@
+/*
+ * e-attachment-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_ATTACHMENT_VIEW_H
+#define E_ATTACHMENT_VIEW_H
+
+#include <gtk/gtk.h>
+#include <widgets/misc/e-attachment-store.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_VIEW \
+ (e_attachment_view_get_type ())
+#define E_ATTACHMENT_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ATTACHMENT_VIEW, EAttachmentView))
+#define E_ATTACHMENT_VIEW_IFACE(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ATTACHMENT_VIEW, EAttachmentViewIface))
+#define E_IS_ATTACHMENT_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ATTACHMENT_VIEW))
+#define E_IS_ATTACHMENT_VIEW_IFACE(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ATTACHMENT_VIEW))
+#define E_ATTACHMENT_VIEW_GET_IFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), E_TYPE_ATTACHMENT_VIEW, EAttachmentViewIface))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentView EAttachmentView;
+typedef struct _EAttachmentViewIface EAttachmentViewIface;
+typedef struct _EAttachmentViewPrivate EAttachmentViewPrivate;
+
+struct _EAttachmentViewIface {
+ GTypeInterface parent_iface;
+
+ /* General Methods */
+ EAttachmentViewPrivate *
+ (*get_private) (EAttachmentView *view);
+ EAttachmentStore *
+ (*get_store) (EAttachmentView *view);
+
+ /* Selection Methods */
+ GtkTreePath * (*get_path_at_pos) (EAttachmentView *view,
+ gint x,
+ gint y);
+ GList * (*get_selected_paths) (EAttachmentView *view);
+ gboolean (*path_is_selected) (EAttachmentView *view,
+ GtkTreePath *path);
+ void (*select_path) (EAttachmentView *view,
+ GtkTreePath *path);
+ void (*unselect_path) (EAttachmentView *view,
+ GtkTreePath *path);
+ void (*select_all) (EAttachmentView *view);
+ void (*unselect_all) (EAttachmentView *view);
+
+ /* Drag and Drop Methods */
+ void (*drag_source_set) (EAttachmentView *view,
+ GdkModifierType start_button_mask,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions);
+ void (*drag_dest_set) (EAttachmentView *view,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions);
+ void (*drag_source_unset) (EAttachmentView *view);
+ void (*drag_dest_unset) (EAttachmentView *view);
+
+ /* Signals */
+ void (*update_actions) (EAttachmentView *view);
+};
+
+struct _EAttachmentViewPrivate {
+
+ /* Attachment Handlers */
+ GPtrArray *handlers;
+
+ /* Drag Destination */
+ GtkTargetList *target_list;
+ GdkDragAction drag_actions;
+
+ /* Popup Menu Management */
+ GtkUIManager *ui_manager;
+ guint merge_id;
+
+ guint editable : 1;
+};
+
+GType e_attachment_view_get_type (void);
+
+void e_attachment_view_init (EAttachmentView *view);
+void e_attachment_view_dispose (EAttachmentView *view);
+void e_attachment_view_finalize (EAttachmentView *view);
+
+EAttachmentViewPrivate *
+ e_attachment_view_get_private (EAttachmentView *view);
+EAttachmentStore *
+ e_attachment_view_get_store (EAttachmentView *view);
+gboolean e_attachment_view_get_editable (EAttachmentView *view);
+void e_attachment_view_set_editable (EAttachmentView *view,
+ gboolean editable);
+GtkTargetList * e_attachment_view_get_target_list
+ (EAttachmentView *view);
+GdkDragAction e_attachment_view_get_drag_actions
+ (EAttachmentView *view);
+GList * e_attachment_view_get_selected_attachments
+ (EAttachmentView *view);
+void e_attachment_view_open_path (EAttachmentView *view,
+ GtkTreePath *path,
+ GAppInfo *app_info);
+void e_attachment_view_remove_selected
+ (EAttachmentView *view,
+ gboolean select_next);
+
+/* Event Support */
+gboolean e_attachment_view_button_press_event
+ (EAttachmentView *view,
+ GdkEventButton *event);
+gboolean e_attachment_view_button_release_event
+ (EAttachmentView *view,
+ GdkEventButton *event);
+gboolean e_attachment_view_key_press_event
+ (EAttachmentView *view,
+ GdkEventKey *event);
+
+/* Selection Management */
+GtkTreePath * e_attachment_view_get_path_at_pos
+ (EAttachmentView *view,
+ gint x,
+ gint y);
+GList * e_attachment_view_get_selected_paths
+ (EAttachmentView *view);
+gboolean e_attachment_view_path_is_selected
+ (EAttachmentView *view,
+ GtkTreePath *path);
+void e_attachment_view_select_path (EAttachmentView *view,
+ GtkTreePath *path);
+void e_attachment_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path);
+void e_attachment_view_select_all (EAttachmentView *view);
+void e_attachment_view_unselect_all (EAttachmentView *view);
+void e_attachment_view_sync_selection(EAttachmentView *view,
+ EAttachmentView *target);
+
+/* Drag Source Support */
+void e_attachment_view_drag_source_set
+ (EAttachmentView *view);
+void e_attachment_view_drag_source_unset
+ (EAttachmentView *view);
+void e_attachment_view_drag_begin (EAttachmentView *view,
+ GdkDragContext *context);
+void e_attachment_view_drag_end (EAttachmentView *view,
+ GdkDragContext *context);
+void e_attachment_view_drag_data_get (EAttachmentView *view,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time);
+
+/* Drag Destination Support */
+void e_attachment_view_drag_dest_set (EAttachmentView *view);
+void e_attachment_view_drag_dest_unset
+ (EAttachmentView *view);
+gboolean e_attachment_view_drag_motion (EAttachmentView *view,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+gboolean e_attachment_view_drag_drop (EAttachmentView *view,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+void e_attachment_view_drag_data_received
+ (EAttachmentView *view,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time);
+
+/* Popup Menu Management */
+GtkAction * e_attachment_view_get_action (EAttachmentView *view,
+ const gchar *action_name);
+GtkActionGroup *e_attachment_view_add_action_group
+ (EAttachmentView *view,
+ const gchar *group_name);
+GtkActionGroup *e_attachment_view_get_action_group
+ (EAttachmentView *view,
+ const gchar *group_name);
+GtkWidget * e_attachment_view_get_popup_menu(EAttachmentView *view);
+GtkUIManager * e_attachment_view_get_ui_manager(EAttachmentView *view);
+GtkAction * e_attachment_view_recent_action_new
+ (EAttachmentView *view,
+ const gchar *action_name,
+ const gchar *action_label);
+void e_attachment_view_show_popup_menu
+ (EAttachmentView *view,
+ GdkEventButton *event,
+ GtkMenuPositionFunc func,
+ gpointer user_data);
+void e_attachment_view_update_actions(EAttachmentView *view);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_VIEW_H */