From e377ea5e61171e57f9e892652d0fd1f77953eda8 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Mon, 27 Apr 2009 14:53:18 -0400 Subject: =?UTF-8?q?Bug=20516933=20=E2=80=93=20Rewrite=20attachment=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- widgets/misc/e-attachment-button.c | 725 +++++++++++++ widgets/misc/e-attachment-button.h | 83 ++ widgets/misc/e-attachment-dialog.c | 446 ++++++++ widgets/misc/e-attachment-dialog.h | 73 ++ widgets/misc/e-attachment-handler-image.c | 280 +++++ widgets/misc/e-attachment-handler-image.h | 65 ++ widgets/misc/e-attachment-handler.c | 191 ++++ widgets/misc/e-attachment-handler.h | 78 ++ widgets/misc/e-attachment-icon-view.c | 537 ++++++++++ widgets/misc/e-attachment-icon-view.h | 66 ++ widgets/misc/e-attachment-paned.c | 781 ++++++++++++++ widgets/misc/e-attachment-paned.h | 84 ++ widgets/misc/e-attachment-store.c | 874 +++++++++++++++ widgets/misc/e-attachment-store.h | 122 +++ widgets/misc/e-attachment-tree-view.c | 605 +++++++++++ widgets/misc/e-attachment-tree-view.h | 66 ++ widgets/misc/e-attachment-view.c | 1637 +++++++++++++++++++++++++++++ widgets/misc/e-attachment-view.h | 227 ++++ 18 files changed, 6940 insertions(+) create mode 100644 widgets/misc/e-attachment-button.c create mode 100644 widgets/misc/e-attachment-button.h create mode 100644 widgets/misc/e-attachment-dialog.c create mode 100644 widgets/misc/e-attachment-dialog.h create mode 100644 widgets/misc/e-attachment-handler-image.c create mode 100644 widgets/misc/e-attachment-handler-image.h create mode 100644 widgets/misc/e-attachment-handler.c create mode 100644 widgets/misc/e-attachment-handler.h create mode 100644 widgets/misc/e-attachment-icon-view.c create mode 100644 widgets/misc/e-attachment-icon-view.h create mode 100644 widgets/misc/e-attachment-paned.c create mode 100644 widgets/misc/e-attachment-paned.h create mode 100644 widgets/misc/e-attachment-store.c create mode 100644 widgets/misc/e-attachment-store.h create mode 100644 widgets/misc/e-attachment-tree-view.c create mode 100644 widgets/misc/e-attachment-tree-view.h create mode 100644 widgets/misc/e-attachment-view.c create mode 100644 widgets/misc/e-attachment-view.h (limited to 'widgets') 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 + * + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_BUTTON_H +#define E_ATTACHMENT_BUTTON_H + +#include +#include +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-dialog.h" + +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_DIALOG_H +#define E_ATTACHMENT_DIALOG_H + +#include +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-handler-image.h" + +#include +#include + +#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 = +"" +" " +" " +" " +" " +" " +""; + +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, + "%s", + _("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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_HANDLER_IMAGE_H +#define E_ATTACHMENT_HANDLER_IMAGE_H + +#include + +/* 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 + * + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_HANDLER_H +#define E_ATTACHMENT_HANDLER_H + +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-icon-view.h" + +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_ICON_VIEW_H +#define E_ATTACHMENT_ICON_VIEW_H + +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-paned.h" + +#include + +#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 ( + "%d %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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_PANED_H +#define E_ATTACHMENT_PANED_H + +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-store.h" + +#include +#include +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_STORE_H +#define E_ATTACHMENT_STORE_H + +#include +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-tree-view.h" + +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_TREE_VIEW_H +#define E_ATTACHMENT_TREE_VIEW_H + +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-attachment-view.h" + +#include +#include +#include +#include + +#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 = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_ATTACHMENT_VIEW_H +#define E_ATTACHMENT_VIEW_H + +#include +#include + +/* 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 */ -- cgit v1.2.3