From 4449a34101406bffe508dd40b8b653f7c7d14c7d Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Mon, 27 Apr 2009 15:36:19 -0400 Subject: Commit the rest of the attachment UI rewrite Oops, last commit only included the -new- files. This also removes EExpander, which is no longer used. --- widgets/misc/Makefile.am | 22 +- widgets/misc/e-attachment-bar.c | 1504 ------------------- widgets/misc/e-attachment-bar.h | 105 -- widgets/misc/e-attachment.c | 3125 ++++++++++++++++++++++++++++++--------- widgets/misc/e-attachment.h | 186 ++- widgets/misc/e-expander.c | 1341 ----------------- widgets/misc/e-expander.h | 82 - 7 files changed, 2590 insertions(+), 3775 deletions(-) delete mode 100644 widgets/misc/e-attachment-bar.c delete mode 100644 widgets/misc/e-attachment-bar.h delete mode 100644 widgets/misc/e-expander.c delete mode 100644 widgets/misc/e-expander.h (limited to 'widgets/misc') diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index 95acd8b929..3befe12e9d 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -37,7 +37,15 @@ widgetsinclude_HEADERS = \ e-account-combo-box.h \ e-activity-handler.h \ e-attachment.h \ - e-attachment-bar.h \ + e-attachment-button.h \ + e-attachment-dialog.h \ + e-attachment-handler.h \ + e-attachment-handler-image.h \ + e-attachment-icon-view.h \ + e-attachment-paned.h \ + e-attachment-store.h \ + e-attachment-tree-view.h \ + e-attachment-view.h \ e-spinner.c \ e-spinner.h \ e-calendar.h \ @@ -51,7 +59,6 @@ widgetsinclude_HEADERS = \ e-combo-button.h \ e-dateedit.h \ e-dropdown-button.h \ - e-expander.h \ e-icon-entry.h \ e-image-chooser.h \ e-info-label.h \ @@ -88,7 +95,15 @@ libemiscwidgets_la_SOURCES = \ e-activity-handler.c \ e-calendar.c \ e-attachment.c \ - e-attachment-bar.c \ + e-attachment-button.c \ + e-attachment-dialog.c \ + e-attachment-handler.c \ + e-attachment-handler-image.c \ + e-attachment-icon-view.c \ + e-attachment-paned.c \ + e-attachment-store.c \ + e-attachment-tree-view.c \ + e-attachment-view.c \ e-calendar-item.c \ e-cell-date-edit.c \ e-cell-percent.c \ @@ -99,7 +114,6 @@ libemiscwidgets_la_SOURCES = \ e-combo-button.c \ e-dateedit.c \ e-dropdown-button.c \ - e-expander.c \ e-icon-entry.c \ e-image-chooser.c \ e-info-label.c \ diff --git a/widgets/misc/e-attachment-bar.c b/widgets/misc/e-attachment-bar.c deleted file mode 100644 index 0210d21b46..0000000000 --- a/widgets/misc/e-attachment-bar.c +++ /dev/null @@ -1,1504 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Ettore Perazzoli - * Jeffrey Stedfast - * Srinivasa Ragavan - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LIBGNOMEUI_GNOME_THUMBNAIL_H -#include -#endif - -#include "e-attachment.h" -#include "e-attachment-bar.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "e-util/e-util.h" -#include "e-util/e-gui-utils.h" -#include "e-util/e-icon-factory.h" -#include "e-util/e-error.h" -#include "e-util/e-mktemp.h" - -#define ICON_WIDTH 64 -#define ICON_SEPARATORS " /-_" -#define ICON_SPACING 2 -#define ICON_ROW_SPACING ICON_SPACING -#define ICON_COL_SPACING ICON_SPACING -#define ICON_BORDER 2 -#define ICON_TEXT_SPACING 2 - - -static GnomeIconListClass *parent_class = NULL; - -struct _EAttachmentBarPrivate { - GtkWidget *attach; /* attachment file dialogue, if active */ - - /* Recent documents. Use this widget directly when bonoboui is obsoleted */ - GtkWidget *recent; - - gboolean batch_unref; - GPtrArray *attachments; - char *path; -}; - - -enum { - CHANGED, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - - -static void update (EAttachmentBar *bar); - -/* Attachment handling functions. */ - -static void -attachment_destroy (EAttachmentBar *bar, EAttachment *attachment) -{ - if (bar->priv->batch_unref) - return; - - if (g_ptr_array_remove (bar->priv->attachments, attachment)) { - update (bar); - g_signal_emit (bar, signals[CHANGED], 0); - } -} - -static void -attachment_changed_cb (EAttachment *attachment, - gpointer data) -{ - update (E_ATTACHMENT_BAR (data)); -} - -static void -add_common (EAttachmentBar *bar, EAttachment *attachment) -{ - g_return_if_fail (attachment != NULL); - - g_ptr_array_add (bar->priv->attachments, attachment); - g_object_weak_ref ((GObject *) attachment, (GWeakNotify) attachment_destroy, bar); - g_signal_connect (attachment, "changed", G_CALLBACK (attachment_changed_cb), bar); - - update (bar); - - g_signal_emit (bar, signals[CHANGED], 0); -} - -static void -add_from_mime_part (EAttachmentBar *bar, CamelMimePart *part) -{ - add_common (bar, e_attachment_new_from_mime_part (part)); -} - -static void -add_from_file (EAttachmentBar *bar, const char *file_name, const char *disposition) -{ - EAttachment *attachment; - CamelException ex; - - camel_exception_init (&ex); - - if ((attachment = e_attachment_new (file_name, disposition, &ex))) { - add_common (bar, attachment); - } else { - /* FIXME: Avoid using error from mailer */ - e_error_run ((GtkWindow *) gtk_widget_get_toplevel ((GtkWidget *) bar), "mail-composer:no-attach", - file_name, camel_exception_get_description (&ex), NULL); - camel_exception_clear (&ex); - } -} - -/* get_system_thumbnail: - * If filled store_uri, then creating thumbnail for it, otherwise, if is_available_local, - * and attachment is not an application, then save to temp and create a thumbnail for the body. - * Otherwise returns NULL (or if something goes wrong/library not available). - */ -static GdkPixbuf * -get_system_thumbnail (EAttachment *attachment, CamelContentType *content_type) -{ - GdkPixbuf *pixbuf = NULL; -#ifdef HAVE_LIBGNOMEUI_GNOME_THUMBNAIL_H - struct stat file_stat; - char *file_uri = NULL; - gboolean is_tmp = FALSE; - - if (!attachment || !attachment->is_available_local) - return NULL; - - if (attachment->store_uri && g_str_has_prefix (attachment->store_uri, "file://")) - file_uri = attachment->store_uri; - else if (attachment->body) { - /* save part to the temp directory */ - char *tmp_file; - - is_tmp = TRUE; - - tmp_file = e_mktemp ("tmp-XXXXXX"); - if (tmp_file) { - CamelStream *stream; - char *mfilename = NULL; - const char * filename; - - filename = camel_mime_part_get_filename (attachment->body); - if (filename == NULL) - filename = "unknown"; - else { - char *utf8_mfilename; - - utf8_mfilename = g_strdup (filename); - e_filename_make_safe (utf8_mfilename); - mfilename = g_filename_from_utf8 ((const char *) utf8_mfilename, -1, NULL, NULL, NULL); - g_free (utf8_mfilename); - - filename = (const char *) mfilename; - } - - file_uri = g_strjoin (NULL, "file://", tmp_file, "-", filename, NULL); - - stream = camel_stream_fs_new_with_name (file_uri + 7, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (stream) { - CamelDataWrapper *content; - - content = camel_medium_get_content_object (CAMEL_MEDIUM (attachment->body)); - - if (camel_data_wrapper_decode_to_stream (content, stream) == -1 - || camel_stream_flush (stream) == -1) { - g_free (file_uri); - file_uri = NULL; - } - - camel_object_unref (stream); - } else { - g_free (file_uri); - file_uri = NULL; - } - - g_free (mfilename); - g_free (tmp_file); - } - } - - if (!file_uri || !g_str_has_prefix (file_uri, "file://")) { - if (is_tmp) - g_free (file_uri); - - return NULL; - } - - if (stat (file_uri + 7, &file_stat) != -1 && S_ISREG (file_stat.st_mode)) { - GnomeThumbnailFactory *th_factory; - char *th_file; - - th_factory = gnome_thumbnail_factory_new (GNOME_THUMBNAIL_SIZE_NORMAL); - th_file = gnome_thumbnail_factory_lookup (th_factory, file_uri, file_stat.st_mtime); - - if (th_file) { - pixbuf = gdk_pixbuf_new_from_file (th_file, NULL); - g_free (th_file); - } else if (content_type) { - char *mime = camel_content_type_simple (content_type); - - if (gnome_thumbnail_factory_can_thumbnail (th_factory, file_uri, mime, file_stat.st_mtime)) { - pixbuf = gnome_thumbnail_factory_generate_thumbnail (th_factory, file_uri, mime); - - if (pixbuf && !is_tmp) - gnome_thumbnail_factory_save_thumbnail (th_factory, pixbuf, file_uri, file_stat.st_mtime); - } - - g_free (mime); - } - - g_object_unref (th_factory); - } - - if (is_tmp) { - /* clear the temp */ - g_remove (file_uri + 7); - g_free (file_uri); - } -#endif - - return pixbuf; -} - -static GdkPixbuf * -scale_pixbuf (GdkPixbuf *pixbuf) -{ - int ratio, width, height; - - if (!pixbuf) - return NULL; - - width = gdk_pixbuf_get_width (pixbuf); - height = gdk_pixbuf_get_height (pixbuf); - if (width >= height) { - if (width > 48) { - ratio = width / 48; - width = 48; - height = height / ratio; - if (height == 0) - height = 1; - } - } else { - if (height > 48) { - ratio = height / 48; - height = 48; - width = width / ratio; - if (width == 0) - width = 1; - } - } - - return e_icon_factory_pixbuf_scale (pixbuf, width, height); -} - -/* Icon list contents handling. */ - -static void -calculate_height_width(EAttachmentBar *bar, int *new_width, int *new_height) -{ - int width, height, icon_width; - PangoFontMetrics *metrics; - PangoContext *context; - - context = gtk_widget_get_pango_context ((GtkWidget *) bar); - metrics = pango_context_get_metrics (context, ((GtkWidget *) bar)->style->font_desc, pango_context_get_language (context)); - width = PANGO_PIXELS (pango_font_metrics_get_approximate_char_width (metrics)) * 15; - /* This should be *2, but the icon list creates too much space above ... */ - height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + pango_font_metrics_get_descent (metrics)) * 3; - pango_font_metrics_unref (metrics); - icon_width = ICON_WIDTH + ICON_SPACING + ICON_BORDER + ICON_TEXT_SPACING; - - if (new_width) - *new_width = MAX (icon_width, width); - - if (new_height) - *new_height = ICON_WIDTH + ICON_SPACING + ICON_BORDER + ICON_TEXT_SPACING + height; - - return; -} - -void -e_attachment_bar_create_attachment_cache (EAttachment *attachment) -{ - - CamelContentType *content_type; - - if (!attachment->body) - return; - - content_type = camel_mime_part_get_content_type (attachment->body); - - if (camel_content_type_is(content_type, "image", "*")) { - CamelDataWrapper *wrapper; - CamelStreamMem *mstream; - GdkPixbufLoader *loader; - gboolean error = TRUE; - GdkPixbuf *pixbuf; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (attachment->body)); - mstream = (CamelStreamMem *) camel_stream_mem_new (); - - camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) mstream); - - /* Stream image into pixbuf loader */ - loader = gdk_pixbuf_loader_new (); - error = !gdk_pixbuf_loader_write (loader, mstream->buffer->data, mstream->buffer->len, NULL); - gdk_pixbuf_loader_close (loader, NULL); - - if (!error) { - pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); - attachment->pixbuf_cache = scale_pixbuf (pixbuf); - pixbuf = attachment->pixbuf_cache; - g_object_ref(pixbuf); - } else { - attachment->pixbuf_cache = NULL; - g_warning ("GdkPixbufLoader Error"); - } - - /* Destroy everything */ - g_object_unref (loader); - camel_object_unref (mstream); - } -} - -static void -update (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - GnomeIconList *icon_list; - int bar_width, bar_height; - int i; - - priv = bar->priv; - icon_list = GNOME_ICON_LIST (bar); - - gnome_icon_list_freeze (icon_list); - - gnome_icon_list_clear (icon_list); - - /* FIXME could be faster, but we don't care. */ - for (i = 0; i < priv->attachments->len; i++) { - EAttachment *attachment; - CamelContentType *content_type; - char *size_string, *label; - GdkPixbuf *pixbuf = NULL; - char *desc; - - attachment = priv->attachments->pdata[i]; - - if (!attachment->is_available_local || !attachment->body) { - if ((pixbuf = e_icon_factory_get_icon("mail-attachment", E_ICON_SIZE_DIALOG))) { - attachment->index = gnome_icon_list_append_pixbuf (icon_list, pixbuf, NULL, ""); - g_object_unref (pixbuf); - } - continue; - } - - content_type = camel_mime_part_get_content_type (attachment->body); - /* Get the image out of the attachment - and create a thumbnail for it */ - if ((pixbuf = attachment->pixbuf_cache)) { - g_object_ref(pixbuf); - } else if (camel_content_type_is(content_type, "image", "*")) { - CamelDataWrapper *wrapper; - CamelStreamMem *mstream; - GdkPixbufLoader *loader; - gboolean error = TRUE; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (attachment->body)); - mstream = (CamelStreamMem *) camel_stream_mem_new (); - - camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) mstream); - - /* Stream image into pixbuf loader */ - loader = gdk_pixbuf_loader_new (); - error = !gdk_pixbuf_loader_write (loader, mstream->buffer->data, mstream->buffer->len, NULL); - gdk_pixbuf_loader_close (loader, NULL); - - if (!error) { - pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); - attachment->pixbuf_cache = scale_pixbuf (pixbuf); - pixbuf = attachment->pixbuf_cache; - g_object_ref (pixbuf); - } else { - pixbuf = NULL; - g_warning ("GdkPixbufLoader Error"); - } - - /* Destroy everything */ - g_object_unref (loader); - camel_object_unref (mstream); - } else if (!bar->expand && (pixbuf = get_system_thumbnail (attachment, content_type))) { - attachment->pixbuf_cache = scale_pixbuf (pixbuf); - pixbuf = attachment->pixbuf_cache; - g_object_ref (pixbuf); - } - - desc = camel_mime_part_get_description (attachment->body); - - if (!desc || *desc == '\0') { - if (attachment->file_name) { - desc = g_filename_to_utf8 (attachment->file_name, -1, NULL, NULL, NULL); - } else { - desc = camel_mime_part_get_filename (attachment->body); - if (desc) - desc = g_strdup (desc); - } - } else { - desc = g_strdup (desc); - } - - if (!desc) - desc = g_strdup (_("attachment")); - - if (attachment->size && (size_string = g_format_size_for_display (attachment->size))) { - label = g_strdup_printf ("%s (%s)", desc, size_string); - g_free (desc); - g_free (size_string); - } else { - label = g_strdup (desc); - g_free (desc); - } - - if (pixbuf == NULL) { - char *mime_type; - - mime_type = camel_content_type_simple (content_type); - pixbuf = e_icon_for_mime_type (mime_type, 48); - if (pixbuf == NULL) { - g_warning("cannot find icon for mime type %s (installation problem?)", mime_type); - pixbuf = e_icon_factory_get_icon("mail-attachment", E_ICON_SIZE_DIALOG); - } - g_free (mime_type); - - /* remember this picture and use it later again */ - if (pixbuf) - attachment->pixbuf_cache = g_object_ref (pixbuf); - } - - if (pixbuf) { - GdkPixbuf *pixbuf_orig = pixbuf; - pixbuf = gdk_pixbuf_add_alpha (pixbuf_orig, TRUE, 255, 255, 255); - - /* gdk_pixbuf_add_alpha returns a newly allocated pixbuf, - free the original one. - */ - g_object_unref (pixbuf_orig); - - /* In case of a attachment bar, in a signed/encrypted part, display the status as a emblem*/ - if (attachment->sign) { - /* Show the signature status at the right-bottom.*/ - GdkPixbuf *sign = NULL; - int x, y; - - if (attachment->sign == CAMEL_CIPHER_VALIDITY_SIGN_BAD) - sign = e_icon_factory_get_icon ("stock_signature-bad", E_ICON_SIZE_MENU); - else if (attachment->sign == CAMEL_CIPHER_VALIDITY_SIGN_GOOD) - sign = e_icon_factory_get_icon ("stock_signature-ok", E_ICON_SIZE_MENU); - else - sign = e_icon_factory_get_icon ("stock_signature", E_ICON_SIZE_MENU); - - x = gdk_pixbuf_get_width (pixbuf) - 17; - y = gdk_pixbuf_get_height (pixbuf) - 17; - - gdk_pixbuf_copy_area (sign, 0, 0, 16, 16, pixbuf, x, y); - g_object_unref (sign); - } - - if (attachment->encrypt) { - /* Show the encryption status at the top left.*/ - GdkPixbuf *encrypt = e_icon_factory_get_icon ("stock_lock-ok", E_ICON_SIZE_MENU); - - gdk_pixbuf_copy_area (encrypt, 0, 0, 16, 16, pixbuf, 1, 1); - g_object_unref (encrypt); - } - - gnome_icon_list_append_pixbuf (icon_list, pixbuf, NULL, label); - g_object_unref (pixbuf); - } - - g_free (label); - } - - gnome_icon_list_thaw (icon_list); - - /* Resize */ - if (bar->expand) { - gtk_widget_get_size_request ((GtkWidget *) bar, &bar_width, &bar_height); - - if (bar->priv->attachments->len) { - int per_col, rows, height, width; - - calculate_height_width(bar, &width, &height); - per_col = bar_width / width; - per_col = (per_col ? per_col : 1); - rows = (bar->priv->attachments->len + per_col -1) / per_col; - gtk_widget_set_size_request ((GtkWidget *) bar, bar_width, rows * height); - } - } -} - -static void -update_remote_file (EAttachment *attachment, EAttachmentBar *bar) -{ - GnomeIconList *icon_list; - GnomeIconTextItem *item; - char *msg, *base; - - if (attachment->percentage == -1) { - update (bar); - return; - } - - base = g_path_get_basename(attachment->file_name); - msg = g_strdup_printf("%s (%d%%)", base, attachment->percentage); - g_free(base); - - icon_list = GNOME_ICON_LIST (bar); - - gnome_icon_list_freeze (icon_list); - - item = gnome_icon_list_get_icon_text_item (icon_list, attachment->index); - if (!item->is_text_allocated) - g_free (item->text); - - gnome_icon_text_item_configure (item, item->x, item->y, item->width, item->fontname, msg, item->is_editable, TRUE); - - gnome_icon_list_thaw (icon_list); -} - -void -e_attachment_bar_remove_selected (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - int id, left, nrem = 0; - GList *items; - GPtrArray *temp_arr; - - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - priv = bar->priv; - - if (!(items = gnome_icon_list_get_selection ((GnomeIconList *) bar))) - return; - - temp_arr = g_ptr_array_new (); - while (items != NULL) { - if ((id = GPOINTER_TO_INT (items->data) - nrem) < priv->attachments->len) { - attachment = E_ATTACHMENT(g_ptr_array_index (priv->attachments, id)); - g_ptr_array_add (temp_arr, (gpointer)attachment); - g_ptr_array_remove_index (priv->attachments, id); - nrem++; - } - - items = items->next; - } - - g_ptr_array_foreach (temp_arr, (GFunc)g_object_unref, NULL); - g_ptr_array_free (temp_arr, TRUE); - - update (bar); - - g_signal_emit (bar, signals[CHANGED], 0); - - id++; - - if ((left = gnome_icon_list_get_num_icons ((GnomeIconList *) bar)) > 0) - gnome_icon_list_focus_icon ((GnomeIconList *) bar, left > id ? id : left - 1); -} - -void -e_attachment_bar_set_width(EAttachmentBar *bar, int bar_width) -{ - int per_col, rows, height, width; - - calculate_height_width(bar, &width, &height); - per_col = bar_width / width; - per_col = (per_col ? per_col : 1); - rows = (bar->priv->attachments->len + per_col - 1) / per_col; - gtk_widget_set_size_request ((GtkWidget *)bar, bar_width, rows * height); -} - -void -e_attachment_bar_edit_selected (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - GList *items; - int id; - - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - priv = bar->priv; - - items = gnome_icon_list_get_selection ((GnomeIconList *) bar); - while (items != NULL) { - if ((id = GPOINTER_TO_INT (items->data)) < priv->attachments->len) { - attachment = priv->attachments->pdata[id]; - e_attachment_edit (attachment, GTK_WIDGET (bar)); - } - - items = items->next; - } -} - -GtkWidget ** -e_attachment_bar_get_selector(EAttachmentBar *bar) -{ - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL); - - return &bar->priv->attach; -} - -/** - * e_attachment_bar_get_selected: - * @bar: an #EAttachmentBar object - * - * Returns a newly allocated #GSList of ref'd #EAttachment objects - * representing the selected items in the #EAttachmentBar Icon List. - **/ -GSList * -e_attachment_bar_get_selected (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - GSList *attachments = NULL; - EAttachment *attachment; - GList *items; - int id; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL); - - priv = bar->priv; - - items = gnome_icon_list_get_selection ((GnomeIconList *) bar); - - while (items != NULL) { - if ((id = GPOINTER_TO_INT (items->data)) < priv->attachments->len) { - attachment = priv->attachments->pdata[id]; - attachments = g_slist_prepend (attachments, attachment); - g_object_ref (attachment); - } - - items = items->next; - } - - attachments = g_slist_reverse (attachments); - - return attachments; -} - -/* FIXME: Cleanup this, since there is a api to get selected attachments */ -/** - * e_attachment_bar_get_attachment: - * @bar: an #EAttachmentBar object - * @id: Index of the desired attachment or -1 to request all selected attachments - * - * Returns a newly allocated #GSList of ref'd #EAttachment objects - * representing the requested item(s) in the #EAttachmentBar Icon - * List. - **/ -GSList * -e_attachment_bar_get_attachment (EAttachmentBar *bar, int id) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - GSList *attachments; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL); - - priv = bar->priv; - - if (id == -1 || id > priv->attachments->len) - return e_attachment_bar_get_selected (bar); - - attachment = priv->attachments->pdata[id]; - attachments = g_slist_prepend (NULL, attachment); - g_object_ref (attachment); - - return attachments; -} - - -/** - * e_attachment_bar_get_all_attachments: - * @bar: an #EAttachmentBar object - * - * Returns a newly allocated #GSList of ref'd #EAttachment objects. - **/ -GSList * -e_attachment_bar_get_all_attachments (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - GSList *attachments = NULL; - EAttachment *attachment; - int i; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL); - - priv = bar->priv; - - for (i = priv->attachments->len - 1; i >= 0; i--) { - attachment = priv->attachments->pdata[i]; - if (attachment->is_available_local) { - attachments = g_slist_prepend (attachments, attachment); - g_object_ref (attachment); - } - } - - return attachments; -} - -/* Just the GSList has to be freed by the caller */ -GSList * -e_attachment_bar_get_parts (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - GSList *parts = NULL; - int i; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL); - - priv = bar->priv; - - for (i = 0; i < priv->attachments->len; i++) { - attachment = priv->attachments->pdata[i]; - if (attachment->is_available_local) - parts = g_slist_prepend (parts, attachment->body); - } - - return parts; -} - -/* GtkObject methods. */ - -static void -destroy (GtkObject *object) -{ - EAttachmentBar *bar = (EAttachmentBar *) object; - struct _EAttachmentBarPrivate *priv = bar->priv; - EAttachment *attachment; - int i; - - if ((priv = bar->priv)) { - priv->batch_unref = TRUE; - for (i = 0; i < priv->attachments->len; i++) { - attachment = priv->attachments->pdata[i]; - g_object_weak_unref ((GObject *) attachment, (GWeakNotify) attachment_destroy, bar); - g_object_unref (attachment); - } - g_ptr_array_free (priv->attachments, TRUE); - - if (priv->attach) - gtk_widget_destroy (priv->attach); - - if (priv->recent) - gtk_widget_destroy (priv->recent); - - if (priv->path) - g_free (priv->path); - - g_free (priv); - bar->priv = NULL; - } - - if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL) - (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); -} - -static char * -temp_save_part (CamelMimePart *part, gboolean readonly) -{ - const char *filename; - char *tmpdir, *path, *mfilename = NULL, *utf8_mfilename = NULL; - CamelStream *stream; - CamelDataWrapper *wrapper; - - if (!(tmpdir = e_mkdtemp ("evolution-tmp-XXXXXX"))) - return NULL; - - if (!(filename = camel_mime_part_get_filename (part))) { - /* This is the default filename used for temporary file creation */ - filename = _("Unknown"); - } else { - utf8_mfilename = g_strdup (filename); - e_filename_make_safe (utf8_mfilename); - mfilename = g_filename_from_utf8 ((const char *) utf8_mfilename, -1, NULL, NULL, NULL); - g_free (utf8_mfilename); - filename = (const char *) mfilename; - } - - path = g_build_filename (tmpdir, filename, NULL); - g_free (tmpdir); - g_free (mfilename); - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - if (readonly) - stream = camel_stream_fs_new_with_name (path, O_RDWR|O_CREAT|O_TRUNC, 0444); - else - stream = camel_stream_fs_new_with_name (path, O_RDWR|O_CREAT|O_TRUNC, 0644); - - if (!stream) { - /* TODO handle error conditions */ - g_message ("DEBUG: could not open the file to write\n"); - g_free (path); - return NULL; - } - - if (camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) stream) == -1) { - g_free (path); - camel_stream_close (stream); - camel_object_unref (stream); - g_message ("DEBUG: could not write to file\n"); - return NULL; - } - - camel_stream_close(stream); - camel_object_unref(stream); - - return path; -} - -static void -eab_drag_data_get(EAttachmentBar *bar, GdkDragContext *drag, GtkSelectionData *data, guint info, guint time) -{ - struct _EAttachmentBarPrivate *priv = bar->priv; - EAttachment *attachment; - char *path, **uris; - int len, n, i = 0; - CamelURL *url; - GList *items; - - if (info) - return; - - items = gnome_icon_list_get_selection (GNOME_ICON_LIST (bar)); - len = g_list_length (items); - - uris = g_malloc0 (sizeof (char *) * (len + 1)); - - for ( ; items != NULL; items = items->next) { - if (!((n = GPOINTER_TO_INT (items->data)) < priv->attachments->len)) - continue; - - attachment = priv->attachments->pdata[n]; - - if (!attachment->is_available_local) - continue; - - if (attachment->store_uri) { - uris[i++] = attachment->store_uri; - continue; - } - - /* If we are not able to save, ignore it */ - if (!(path = temp_save_part (attachment->body, FALSE))) - continue; - - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, path); - attachment->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); - g_free (path); - - uris[i++] = attachment->store_uri; - } - - uris[i] = NULL; - - gtk_selection_data_set_uris (data, uris); - - g_free (uris); - - return; -} - -static gboolean -eab_button_release_event(EAttachmentBar *bar, GdkEventButton *event, gpointer dummy) -{ - GnomeIconList *icon_list = GNOME_ICON_LIST(bar); - GList *selected; - int length; - GtkTargetEntry drag_types[] = { - { "text/uri-list", 0, 0 }, - }; - - if (event && event->button == 1) { - selected = gnome_icon_list_get_selection(icon_list); - length = g_list_length (selected); - if (length) - gtk_drag_source_set((GtkWidget *)bar, GDK_BUTTON1_MASK, drag_types, G_N_ELEMENTS(drag_types), GDK_ACTION_COPY); - else - gtk_drag_source_unset((GtkWidget *)bar); - } - - return FALSE; -} - -static gboolean -eab_button_press_event(EAttachmentBar *bar, GdkEventButton *event, gpointer dummy) -{ - GnomeIconList *icon_list = GNOME_ICON_LIST(bar); - GList *selected = NULL, *tmp; - int length, icon_number; - gboolean take_selected = FALSE; - GtkTargetEntry drag_types[] = { - { "text/uri-list", 0, 0 }, - }; - - selected = gnome_icon_list_get_selection(icon_list); - length = g_list_length (selected); - - if (event) { - icon_number = gnome_icon_list_get_icon_at(icon_list, event->x, event->y); - if (icon_number < 0) { - /* When nothing is selected, deselect all */ - gnome_icon_list_unselect_all (icon_list); - length = 0; - selected = NULL; - } - - if (event->button == 1) { - /* If something is selected, then allow drag or else help to select */ - if (length) - gtk_drag_source_set((GtkWidget *)bar, GDK_BUTTON1_MASK, drag_types, G_N_ELEMENTS(drag_types), GDK_ACTION_COPY); - else - gtk_drag_source_unset((GtkWidget *)bar); - return FALSE; - } - - /* If not r-click dont progress any more.*/ - if (event->button != 3) - return FALSE; - - /* When a r-click on something, if it is in the already selected list, consider a r-click of multiple things - * or deselect all and select only this for r-click - */ - if (icon_number >= 0) { - for (tmp = selected; tmp; tmp = tmp->next) { - if (GPOINTER_TO_INT(tmp->data) == icon_number) - take_selected = TRUE; - } - - if (!take_selected) { - gnome_icon_list_unselect_all(icon_list); - gnome_icon_list_select_icon(icon_list, icon_number); - } - } - } - - return FALSE; -} - -static gboolean -eab_icon_clicked_cb (EAttachmentBar *bar, GdkEvent *event, gpointer *dummy) -{ - EAttachment *attachment; - gboolean ret = FALSE; - CamelURL *url; - char *path; - GSList *p; - - if (E_IS_ATTACHMENT_BAR (bar) && event->type == GDK_2BUTTON_PRESS) { - p = e_attachment_bar_get_selected (bar); - /* check if has body already, remote files can take longer to fetch */ - if (p && p->next == NULL && ((EAttachment *)p->data)->body) { - attachment = p->data; - - /* Check if the file is stored already */ - if (!attachment->store_uri) { - path = temp_save_part (attachment->body, TRUE); - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, path); - attachment->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); - g_free (path); - } - - /* FIXME Pass a parent window. */ - e_show_uri (NULL, attachment->store_uri); - - ret = TRUE; - } - - if (p) { - g_slist_foreach (p, (GFunc) g_object_unref, NULL); - g_slist_free (p); - } - } - - return ret; -} - -/* Initialization. */ - -static void -class_init (EAttachmentBarClass *klass) -{ - GtkObjectClass *object_class; - - object_class = GTK_OBJECT_CLASS (klass); - - parent_class = g_type_class_ref (gnome_icon_list_get_type ()); - - object_class->destroy = destroy; - - /* Setup signals. */ - - signals[CHANGED] = - g_signal_new ("changed", - E_TYPE_ATTACHMENT_BAR, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EAttachmentBarClass, changed), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -static void -init (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - - priv = g_new (struct _EAttachmentBarPrivate, 1); - - priv->attach = NULL; - priv->batch_unref = FALSE; - priv->attachments = g_ptr_array_new (); - - priv->recent = gtk_recent_chooser_menu_new (); - gtk_recent_chooser_menu_set_show_numbers (GTK_RECENT_CHOOSER_MENU (priv->recent), TRUE); - gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (priv->recent), GTK_RECENT_SORT_MRU); - gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER (priv->recent), FALSE); - gtk_recent_chooser_set_show_private (GTK_RECENT_CHOOSER (priv->recent), FALSE); - gtk_recent_chooser_set_show_icons (GTK_RECENT_CHOOSER (priv->recent), TRUE); - gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER (priv->recent), TRUE); - - priv->path = NULL; - - bar->priv = priv; - bar->expand = FALSE; -} - - -GType -e_attachment_bar_get_type (void) -{ - static GType type = 0; - - if (type == 0) { - static const GTypeInfo info = { - sizeof (EAttachmentBarClass), - NULL, NULL, - (GClassInitFunc) class_init, - NULL, NULL, - sizeof (EAttachmentBar), - 0, - (GInstanceInitFunc) init, - }; - - type = g_type_register_static (GNOME_TYPE_ICON_LIST, "EAttachmentBar", &info, 0); - } - - return type; -} - -GtkWidget * -e_attachment_bar_new (GtkAdjustment *adj) -{ - EAttachmentBar *new; - GnomeIconList *icon_list; - int icon_width, window_height; - - new = g_object_new (e_attachment_bar_get_type (), NULL); - - icon_list = GNOME_ICON_LIST (new); - - calculate_height_width (new, &icon_width, &window_height); - - gnome_icon_list_construct (icon_list, icon_width, adj, 0); - - gtk_widget_set_size_request (GTK_WIDGET (new), icon_width * 4, window_height); - - GTK_WIDGET_SET_FLAGS (new, GTK_CAN_FOCUS); - - gnome_icon_list_set_separators (icon_list, ICON_SEPARATORS); - gnome_icon_list_set_row_spacing (icon_list, ICON_ROW_SPACING); - gnome_icon_list_set_col_spacing (icon_list, ICON_COL_SPACING); - gnome_icon_list_set_icon_border (icon_list, ICON_BORDER); - gnome_icon_list_set_text_spacing (icon_list, ICON_TEXT_SPACING); - gnome_icon_list_set_selection_mode (icon_list, GTK_SELECTION_MULTIPLE); - - atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (new)), - _("Attachment Bar")); - - g_signal_connect (new, "button_release_event", G_CALLBACK(eab_button_release_event), NULL); - g_signal_connect (new, "button_press_event", G_CALLBACK(eab_button_press_event), NULL); - g_signal_connect (new, "drag-data-get", G_CALLBACK(eab_drag_data_get), NULL); - g_signal_connect (icon_list, "event", G_CALLBACK (eab_icon_clicked_cb), NULL); - - return GTK_WIDGET (new); -} - -static char * -get_default_charset (void) -{ - GConfClient *gconf; - const char *locale; - char *charset; - - gconf = gconf_client_get_default (); - charset = gconf_client_get_string (gconf, "/apps/evolution/mail/composer/charset", NULL); - - if (!charset || charset[0] == '\0') { - g_free (charset); - charset = gconf_client_get_string (gconf, "/apps/evolution/mail/format/charset", NULL); - if (charset && charset[0] == '\0') { - g_free (charset); - charset = NULL; - } - } - - g_object_unref (gconf); - - if (!charset && (locale = camel_iconv_locale_charset ())) - charset = g_strdup (locale); - - return charset ? charset : g_strdup ("us-ascii"); -} - -static void -attach_to_multipart (CamelMultipart *multipart, - EAttachment *attachment, - const char *default_charset) -{ - CamelContentType *content_type; - CamelDataWrapper *content; - - if (!attachment->body) - return; - - content_type = camel_mime_part_get_content_type (attachment->body); - content = camel_medium_get_content_object (CAMEL_MEDIUM (attachment->body)); - - if (!CAMEL_IS_MULTIPART (content)) { - if (camel_content_type_is (content_type, "text", "*")) { - CamelTransferEncoding encoding; - CamelStreamFilter *filter_stream; - CamelMimeFilterBestenc *bestenc; - CamelStream *stream; - const char *charset; - char *buf = NULL; - char *type; - - charset = camel_content_type_param (content_type, "charset"); - - stream = camel_stream_null_new (); - filter_stream = camel_stream_filter_new_with_stream (stream); - bestenc = camel_mime_filter_bestenc_new (CAMEL_BESTENC_GET_ENCODING); - camel_stream_filter_add (filter_stream, CAMEL_MIME_FILTER (bestenc)); - camel_object_unref (stream); - - camel_data_wrapper_decode_to_stream (content, CAMEL_STREAM (filter_stream)); - camel_object_unref (filter_stream); - - encoding = camel_mime_filter_bestenc_get_best_encoding (bestenc, CAMEL_BESTENC_8BIT); - camel_mime_part_set_encoding (attachment->body, encoding); - - if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) { - /* the text fits within us-ascii so this is safe */ - /* FIXME: check that this isn't iso-2022-jp? */ - default_charset = "us-ascii"; - } else if (!charset) { - if (!default_charset) - default_charset = buf = get_default_charset (); - - /* FIXME: We should really check that this fits within the - default_charset and if not find one that does and/or - allow the user to specify? */ - } - - if (!charset) { - /* looks kinda nasty, but this is how ya have to do it */ - camel_content_type_set_param (content_type, "charset", default_charset); - type = camel_content_type_format (content_type); - camel_mime_part_set_content_type (attachment->body, type); - g_free (type); - g_free (buf); - } - - camel_object_unref (bestenc); - } else if (!CAMEL_IS_MIME_MESSAGE (content)) { - camel_mime_part_set_encoding (attachment->body, CAMEL_TRANSFER_ENCODING_BASE64); - } - } - - camel_multipart_add_part (multipart, attachment->body); -} - -void -e_attachment_bar_to_multipart (EAttachmentBar *bar, CamelMultipart *multipart, const char *default_charset) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - int i; - - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - g_return_if_fail (CAMEL_IS_MULTIPART (multipart)); - - priv = bar->priv; - - for (i = 0; i < priv->attachments->len; i++) { - attachment = priv->attachments->pdata[i]; - if (attachment->is_available_local) - attach_to_multipart (multipart, attachment, default_charset); - } -} - -guint -e_attachment_bar_get_num_attachments (EAttachmentBar *bar) -{ - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), 0); - - return bar->priv->attachments->len; -} - -void -e_attachment_bar_attach (EAttachmentBar *bar, const char *file_name, const char *disposition) -{ - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - g_return_if_fail (file_name != NULL && disposition != NULL); - - add_from_file (bar, file_name, disposition); -} - -void -e_attachment_bar_add_attachment (EAttachmentBar *bar, EAttachment *attachment) -{ - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - add_common (bar, attachment); -} - -void -e_attachment_bar_add_attachment_silent (EAttachmentBar *bar, EAttachment *attachment) -{ - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - g_return_if_fail (attachment != NULL); - - g_ptr_array_add (bar->priv->attachments, attachment); - g_object_weak_ref ((GObject *) attachment, (GWeakNotify) attachment_destroy, bar); - g_signal_connect (attachment, "changed", G_CALLBACK (attachment_changed_cb), bar); - - - g_signal_emit (bar, signals[CHANGED], 0); -} - -void -e_attachment_bar_refresh (EAttachmentBar *bar) -{ - update (bar); - -} - -int -e_attachment_bar_get_download_count (EAttachmentBar *bar) -{ - struct _EAttachmentBarPrivate *priv; - EAttachment *attachment; - int i, n = 0; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), 0); - - priv = bar->priv; - - for (i = 0; i < priv->attachments->len; i++) { - attachment = priv->attachments->pdata[i]; - if (!attachment->is_available_local) - n++; - } - - return n; -} - -void -e_attachment_bar_attach_remote_file (EAttachmentBar *bar, const char *url, const char *disposition) -{ - EAttachment *attachment; - CamelException ex; - GtkWindow *parent; - - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - if (!bar->priv->path) - bar->priv->path = e_mkdtemp ("attach-XXXXXX"); - - parent = (GtkWindow *) gtk_widget_get_toplevel ((GtkWidget *) bar); - camel_exception_init (&ex); - if ((attachment = e_attachment_new_remote_file (parent, url, disposition, bar->priv->path, &ex))) { - add_common (bar, attachment); - g_signal_connect (attachment, "update", G_CALLBACK (update_remote_file), bar); - } else { - e_error_run (parent, "mail-composer:no-attach", - url, camel_exception_get_description (&ex), NULL); - camel_exception_clear (&ex); - } -} - -void -e_attachment_bar_attach_mime_part (EAttachmentBar *bar, CamelMimePart *part) -{ - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - add_from_mime_part (bar, part); -} - -/* FIXME: Remove this API if nobody uses it */ -void -e_attachment_bar_bonobo_ui_populate_with_recent (BonoboUIComponent *uic, const char *path, - EAttachmentBar *bar, - BonoboUIVerbFn verb_cb, gpointer user_data) -{ - struct _EAttachmentBarPrivate *priv; - GList *items, *l; - gint limit, i; - GString *menuitems; - char *encoded_label, *label; - - g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); - - priv = bar->priv; - limit = gtk_recent_chooser_get_limit (GTK_RECENT_CHOOSER (priv->recent)); - items = gtk_recent_chooser_get_items (GTK_RECENT_CHOOSER (priv->recent)); - - menuitems = g_string_new ("\n"); - - for (l = g_list_first (items), i = 1; l && i <= limit; l = l->next, ++i) { - GtkRecentInfo *info = ((GtkRecentInfo *)(l->data)); - const gchar *info_dn = gtk_recent_info_get_display_name (info); - char *display_name, *u; - - /* escape _'s in the display name so that it doesn't become an underline in a GtkLabel */ - if ((u = strchr (info_dn, '_'))) { - int extra = 1; - char *d; - const char *s; - - while ((u = strchr (u + 1, '_'))) - extra++; - - d = display_name = g_alloca (strlen (info_dn) + extra + 1); - s = info_dn; - while (*s != '\0') { - if (*s == '_') - *d++ = '_'; - *d++ = *s++; - } - *d = '\0'; - } else - display_name = (char *) info_dn; - - /* Add menu item */ - label = g_strdup (display_name); - encoded_label = bonobo_ui_util_encode_str (label); - g_string_append_printf (menuitems, - " \n", - i, encoded_label); - g_free (encoded_label); - g_free (label); - } - - g_string_append (menuitems, "\n"); - - bonobo_ui_component_set (uic, path, menuitems->str, NULL); - - g_string_free (menuitems, TRUE); - - /* Add uri prop */ - for (l = g_list_first (items), i = 1; l && i <= limit; l = l->next, ++i) { - GtkRecentInfo *info = ((GtkRecentInfo *)(l->data)); - const gchar *info_uri = gtk_recent_info_get_uri (info); - label = g_strdup_printf ("/commands/Recent-%d", i); - bonobo_ui_component_set_prop (uic, label, "uri", info_uri, NULL); - g_free (label); - } - - /* Add verb */ - for (l = g_list_first (items), i = 1; l && i <= limit; l = l->next, ++i) { - label = g_strdup_printf ("Recent-%d", i); - bonobo_ui_component_add_verb (uic, label, verb_cb, user_data); - g_free (label); - } - - for (l = g_list_first (items); l; l = l->next) - gtk_recent_info_unref ((GtkRecentInfo *)(l->data)); - g_list_free (items); -} - -static void -action_recent_cb (GtkAction *action, - EAttachmentBar *attachment_bar) -{ - GtkRecentChooser *chooser; - GFile *file; - gchar *uri; - - chooser = GTK_RECENT_CHOOSER (action); - - /* Wish: gtk_recent_chooser_get_current_file() */ - uri = gtk_recent_chooser_get_current_uri (chooser); - file = g_file_new_for_uri (uri); - g_free (uri); - - if (g_file_is_native (file)) - e_attachment_bar_attach ( - E_ATTACHMENT_BAR (attachment_bar), - g_file_get_path (file), "attachment"); - else - e_attachment_bar_attach_remote_file ( - E_ATTACHMENT_BAR (attachment_bar), - g_file_get_uri (file), "attachment"); - - g_object_unref (file); -} - -GtkAction * -e_attachment_bar_recent_action_new (EAttachmentBar *bar, - const gchar *action_name, - const gchar *action_label) -{ - GtkAction *action; - GtkRecentChooser *chooser; - - g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), 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), bar); - - return action; -} - diff --git a/widgets/misc/e-attachment-bar.h b/widgets/misc/e-attachment-bar.h deleted file mode 100644 index 7f8b4795d1..0000000000 --- a/widgets/misc/e-attachment-bar.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Ettore Perazzoli - * Srinivasa Ragavan - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef __E_ATTACHMENT_BAR_H__ -#define __E_ATTACHMENT_BAR_H__ - -#include - -#include -#include - -#include -#include "e-attachment.h" - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif /* __cplusplus */ - -#define E_TYPE_ATTACHMENT_BAR \ - (e_attachment_bar_get_type ()) -#define E_ATTACHMENT_BAR(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_ATTACHMENT_BAR, EAttachmentBar)) -#define E_ATTACHMENT_BAR_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_ATTACHMENT_BAR, EAttachmentBarClass)) -#define E_IS_ATTACHMENT_BAR(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_ATTACHMENT_BAR)) -#define E_IS_ATTACHMENT_BAR_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_ATTACHMENT_BAR)) - -typedef struct _EAttachmentBar EAttachmentBar; -typedef struct _EAttachmentBarClass EAttachmentBarClass; - -struct _EAttachmentBar { - GnomeIconList parent; - gboolean expand; - - struct _EAttachmentBarPrivate *priv; -}; - -struct _EAttachmentBarClass { - GnomeIconListClass parent_class; - - void (* changed) (EAttachmentBar *bar); -}; - - -GType e_attachment_bar_get_type (void); - -GtkWidget *e_attachment_bar_new (GtkAdjustment *adj); -void e_attachment_bar_to_multipart (EAttachmentBar *bar, CamelMultipart *multipart, - const char *default_charset); -guint e_attachment_bar_get_num_attachments (EAttachmentBar *bar); -void e_attachment_bar_attach (EAttachmentBar *bar, const char *file_name, const char *disposition); -void e_attachment_bar_attach_mime_part (EAttachmentBar *bar, CamelMimePart *part); -int e_attachment_bar_get_download_count (EAttachmentBar *bar); -void e_attachment_bar_attach_remote_file (EAttachmentBar *bar, const char *url, const char *disposition); -GSList *e_attachment_bar_get_attachment (EAttachmentBar *bar, int id); -void e_attachment_bar_add_attachment (EAttachmentBar *bar, EAttachment *attachment); -void e_attachment_bar_edit_selected (EAttachmentBar *bar); -void e_attachment_bar_remove_selected (EAttachmentBar *bar); -GtkWidget ** e_attachment_bar_get_selector(EAttachmentBar *bar); -GSList *e_attachment_bar_get_parts (EAttachmentBar *bar); -GSList *e_attachment_bar_get_selected (EAttachmentBar *bar); -void e_attachment_bar_set_width(EAttachmentBar *bar, int bar_width); -GSList * e_attachment_bar_get_all_attachments (EAttachmentBar *bar); -void e_attachment_bar_create_attachment_cache (EAttachment *attachment); -void -e_attachment_bar_bonobo_ui_populate_with_recent (BonoboUIComponent *uic, const char *path, - EAttachmentBar *bar, - BonoboUIVerbFn verb_cb, gpointer user_data); -GtkAction * -e_attachment_bar_recent_action_new (EAttachmentBar *bar, - const gchar *action_name, - const gchar *action_label); -void -e_attachment_bar_add_attachment_silent (EAttachmentBar *bar, EAttachment *attachment); -void -e_attachment_bar_refresh (EAttachmentBar *bar); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* __E_ATTACHMENT_BAR_H__ */ diff --git a/widgets/misc/e-attachment.c b/widgets/misc/e-attachment.c index 4f5e9ace34..f472a26ff3 100644 --- a/widgets/misc/e-attachment.c +++ b/widgets/misc/e-attachment.c @@ -1,4 +1,5 @@ /* + * e-attachment.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,870 +15,2660 @@ * License along with the program; if not, see * * - * Authors: - * Ettore Perazzoli - * Jeffrey Stedfast - * Srinivasa Ragavan - * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ -#ifdef HAVE_CONFIG_H -#include -#endif - -#ifdef G_OS_WIN32 -/* Include early (as the gio stuff below will - * include it anyway, sigh) to workaround the DATADIR problem. - * (and the headers it includes) stomps all over the - * namespace like a baboon on crack, and especially the DATADIR enum - * in objidl.h causes problems. - */ -#undef DATADIR -#define DATADIR crap_DATADIR -#include -#undef DATADIR -#endif +#include "e-attachment.h" -#include -#include #include - -#include - +#include #include -#include - -#include +#include +#include +#include +#include +#include +#include +#include #include "e-util/e-util.h" -#include "e-util/e-error.h" #include "e-util/e-mktemp.h" -#include "e-util/e-util-private.h" - -#include "e-attachment.h" +#include "e-attachment-store.h" + +#define E_ATTACHMENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_ATTACHMENT, EAttachmentPrivate)) + +/* Fallback Icon */ +#define DEFAULT_ICON_NAME "mail-attachment" + +/* Emblems */ +#define EMBLEM_CANCELLED "gtk-cancel" +#define EMBLEM_LOADING "emblem-downloads" +#define EMBLEM_SAVING "document-save" +#define EMBLEM_ENCRYPT_WEAK "security-low" +#define EMBLEM_ENCRYPT_STRONG "security-high" +#define EMBLEM_ENCRYPT_UNKNOWN "security-medium" +#define EMBLEM_SIGN_BAD "stock_signature_bad" +#define EMBLEM_SIGN_GOOD "stock_signature-ok" +#define EMBLEM_SIGN_UNKNOWN "stock_signature" + +/* Attributes needed for EAttachmentStore columns. */ +#define ATTACHMENT_QUERY "standard::*,preview::*,thumbnail::*" + +struct _EAttachmentPrivate { + GFile *file; + GFileInfo *file_info; + GCancellable *cancellable; + CamelMimePart *mime_part; + guint emblem_timeout_id; + gchar *disposition; + gint percent; + + guint can_show : 1; + guint loading : 1; + guint saving : 1; + guint shown : 1; + + camel_cipher_validity_encrypt_t encrypted; + camel_cipher_validity_sign_t signed_; + + /* This is a reference to our row in an EAttachmentStore, + * serving as a means of broadcasting "row-changed" signals. + * If we are removed from the store, we lazily free the + * reference when it is found to be to be invalid. */ + GtkTreeRowReference *reference; +}; enum { - CHANGED, - UPDATE, - LAST_SIGNAL + PROP_0, + PROP_CAN_SHOW, + PROP_DISPOSITION, + PROP_ENCRYPTED, + PROP_FILE, + PROP_FILE_INFO, + PROP_LOADING, + PROP_MIME_PART, + PROP_PERCENT, + PROP_REFERENCE, + PROP_SAVING, + PROP_SHOWN, + PROP_SIGNED }; -static guint signals[LAST_SIGNAL] = { 0 }; +static gpointer parent_class; + +static gchar * +attachment_get_default_charset (void) +{ + GConfClient *client; + const gchar *key; + gchar *charset; + + /* XXX This doesn't really belong here. */ + + client = gconf_client_get_default (); + key = "/apps/evolution/mail/composer/charset"; + charset = gconf_client_get_string (client, key, NULL); + if (charset == NULL || *charset == '\0') { + g_free (charset); + key = "/apps/evolution/mail/format/charset"; + charset = gconf_client_get_string (client, key, NULL); + if (charset == NULL || *charset == '\0') { + g_free (charset); + charset = NULL; + } + } + g_object_unref (client); + + if (charset == NULL) + charset = g_strdup (camel_iconv_locale_charset ()); + + if (charset == NULL) + charset = g_strdup ("us-ascii"); + + return charset; +} + +static void +attachment_update_file_info_columns (EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GFileInfo *file_info; + const gchar *content_type; + const gchar *description; + const gchar *display_name; + gchar *content_desc; + gchar *display_size; + gchar *caption; + goffset size; + + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + return; + + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + return; + + 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); + + content_type = g_file_info_get_content_type (file_info); + display_name = g_file_info_get_display_name (file_info); + size = g_file_info_get_size (file_info); + + content_desc = g_content_type_get_description (content_type); + display_size = g_format_size_for_display (size); + + description = e_attachment_get_description (attachment); + if (description == NULL || *description == '\0') + description = display_name; + + if (size > 0) + caption = g_strdup_printf ( + "%s\n(%s)", description, display_size); + else + caption = g_strdup (description); + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_CAPTION, caption, + E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_desc, + E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, description, + E_ATTACHMENT_STORE_COLUMN_SIZE, size, + -1); + + g_free (content_desc); + g_free (display_size); + g_free (caption); +} + +static void +attachment_update_icon_column (EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GFileInfo *file_info; + GCancellable *cancellable; + GIcon *icon = NULL; + const gchar *emblem_name = NULL; + const gchar *thumbnail_path = NULL; + + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + return; + + 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); + + cancellable = attachment->priv->cancellable; + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) { + icon = g_file_info_get_icon (file_info); + thumbnail_path = g_file_info_get_attribute_byte_string ( + file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + } + + /* Prefer the thumbnail if we have one. */ + if (thumbnail_path != NULL && *thumbnail_path != '\0') { + GFile *file; + + file = g_file_new_for_path (thumbnail_path); + icon = g_file_icon_new (file); + g_object_unref (file); + + /* Else use the standard icon for the content type. */ + } else if (icon != NULL) + g_object_ref (icon); + + /* Last ditch fallback. (GFileInfo not yet loaded?) */ + else + icon = g_themed_icon_new (DEFAULT_ICON_NAME); + + /* Pick an emblem, limit one. Choices listed by priority. */ + + if (g_cancellable_is_cancelled (cancellable)) + emblem_name = EMBLEM_CANCELLED; + + else if (e_attachment_get_loading (attachment)) + emblem_name = EMBLEM_LOADING; + + else if (e_attachment_get_saving (attachment)) + emblem_name = EMBLEM_SAVING; + + else if (e_attachment_get_encrypted (attachment)) + switch (e_attachment_get_encrypted (attachment)) { + case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK: + emblem_name = EMBLEM_ENCRYPT_WEAK; + break; + + case CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED: + emblem_name = EMBLEM_ENCRYPT_UNKNOWN; + break; + + case CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG: + emblem_name = EMBLEM_ENCRYPT_STRONG; + break; + + default: + g_warn_if_reached (); + break; + } + + else if (e_attachment_get_signed (attachment)) + switch (e_attachment_get_signed (attachment)) { + case CAMEL_CIPHER_VALIDITY_SIGN_GOOD: + emblem_name = EMBLEM_SIGN_GOOD; + break; + + case CAMEL_CIPHER_VALIDITY_SIGN_BAD: + emblem_name = EMBLEM_SIGN_BAD; + break; + + case CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN: + case CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY: + emblem_name = EMBLEM_SIGN_UNKNOWN; + break; + + default: + g_warn_if_reached (); + break; + } + + if (emblem_name != NULL) { + GIcon *emblemed_icon; + GEmblem *emblem; + + emblemed_icon = g_themed_icon_new (emblem_name); + emblem = g_emblem_new (emblemed_icon); + g_object_unref (emblemed_icon); + + emblemed_icon = g_emblemed_icon_new (icon, emblem); + g_object_unref (emblem); + g_object_unref (icon); + + icon = emblemed_icon; + } + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_ICON, icon, + -1); + + g_object_unref (icon); +} + +static void +attachment_update_progress_columns (EAttachment *attachment) +{ + GtkTreeRowReference *reference; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + gboolean loading; + gboolean saving; + gint percent; + + reference = e_attachment_get_reference (attachment); + if (!gtk_tree_row_reference_valid (reference)) + return; + + 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); + + /* Don't show progress bars until we have progress to report. */ + percent = e_attachment_get_percent (attachment); + loading = e_attachment_get_loading (attachment) && (percent > 0); + saving = e_attachment_get_saving (attachment) && (percent > 0); + + gtk_list_store_set ( + GTK_LIST_STORE (model), &iter, + E_ATTACHMENT_STORE_COLUMN_LOADING, loading, + E_ATTACHMENT_STORE_COLUMN_PERCENT, percent, + E_ATTACHMENT_STORE_COLUMN_SAVING, saving, + -1); +} + +static void +attachment_set_file_info (EAttachment *attachment, + GFileInfo *file_info) +{ + GtkTreeRowReference *reference; + GIcon *icon; + + reference = e_attachment_get_reference (attachment); + + if (file_info != NULL) + g_object_ref (file_info); + + if (attachment->priv->file_info != NULL) + g_object_unref (attachment->priv->file_info); + + attachment->priv->file_info = file_info; + + /* If the GFileInfo contains a GThemedIcon, append a + * fallback icon name to ensure we display something. */ + icon = g_file_info_get_icon (file_info); + if (G_IS_THEMED_ICON (icon)) + g_themed_icon_append_name ( + G_THEMED_ICON (icon), DEFAULT_ICON_NAME); + + g_object_notify (G_OBJECT (attachment), "file-info"); + + /* Tell the EAttachmentStore its total size changed. */ + if (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + model = gtk_tree_row_reference_get_model (reference); + g_object_notify (G_OBJECT (model), "total-size"); + } +} + +static void +attachment_set_loading (EAttachment *attachment, + gboolean loading) +{ + GtkTreeRowReference *reference; + + reference = e_attachment_get_reference (attachment); + + attachment->priv->percent = 0; + attachment->priv->loading = loading; + + g_object_freeze_notify (G_OBJECT (attachment)); + g_object_notify (G_OBJECT (attachment), "percent"); + g_object_notify (G_OBJECT (attachment), "loading"); + g_object_thaw_notify (G_OBJECT (attachment)); + + if (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + model = gtk_tree_row_reference_get_model (reference); + g_object_notify (G_OBJECT (model), "num-loading"); + } +} + +static void +attachment_set_saving (EAttachment *attachment, + gboolean saving) +{ + attachment->priv->percent = 0; + attachment->priv->saving = saving; + + g_object_freeze_notify (G_OBJECT (attachment)); + g_object_notify (G_OBJECT (attachment), "percent"); + g_object_notify (G_OBJECT (attachment), "saving"); + g_object_thaw_notify (G_OBJECT (attachment)); +} + +static void +attachment_progress_cb (goffset current_num_bytes, + goffset total_num_bytes, + EAttachment *attachment) +{ + attachment->priv->percent = + (current_num_bytes * 100) / total_num_bytes; + + g_object_notify (G_OBJECT (attachment), "percent"); +} + +static gboolean +attachment_cancelled_timeout_cb (EAttachment *attachment) +{ + attachment->priv->emblem_timeout_id = 0; + g_cancellable_reset (attachment->priv->cancellable); + + attachment_update_icon_column (attachment); + + return FALSE; +} + +static void +attachment_cancelled_cb (EAttachment *attachment) +{ + /* Reset the GCancellable after one second. This causes a + * cancel emblem to be briefly shown on the attachment icon + * as visual feedback that an operation was cancelled. */ + + if (attachment->priv->emblem_timeout_id > 0) + g_source_remove (attachment->priv->emblem_timeout_id); + + attachment->priv->emblem_timeout_id = g_timeout_add_seconds ( + 1, (GSourceFunc) attachment_cancelled_timeout_cb, attachment); + + attachment_update_icon_column (attachment); +} + +static void +attachment_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CAN_SHOW: + e_attachment_set_can_show ( + E_ATTACHMENT (object), + g_value_get_boolean (value)); + return; + + case PROP_DISPOSITION: + e_attachment_set_disposition ( + E_ATTACHMENT (object), + g_value_get_string (value)); + return; + + case PROP_ENCRYPTED: + e_attachment_set_encrypted ( + E_ATTACHMENT (object), + g_value_get_int (value)); + return; + + case PROP_FILE: + e_attachment_set_file ( + E_ATTACHMENT (object), + g_value_get_object (value)); + return; + + case PROP_SHOWN: + e_attachment_set_shown ( + E_ATTACHMENT (object), + g_value_get_boolean (value)); + return; + + case PROP_MIME_PART: + e_attachment_set_mime_part ( + E_ATTACHMENT (object), + g_value_get_boxed (value)); + return; + + case PROP_REFERENCE: + e_attachment_set_reference ( + E_ATTACHMENT (object), + g_value_get_boxed (value)); + return; + + case PROP_SIGNED: + e_attachment_set_signed ( + E_ATTACHMENT (object), + g_value_get_int (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +attachment_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CAN_SHOW: + g_value_set_boolean ( + value, e_attachment_get_can_show ( + E_ATTACHMENT (object))); + return; + + case PROP_DISPOSITION: + g_value_set_string ( + value, e_attachment_get_disposition ( + E_ATTACHMENT (object))); + return; + + case PROP_ENCRYPTED: + g_value_set_int ( + value, e_attachment_get_encrypted ( + E_ATTACHMENT (object))); + return; + + case PROP_FILE: + g_value_set_object ( + value, e_attachment_get_file ( + E_ATTACHMENT (object))); + return; + + case PROP_FILE_INFO: + g_value_set_object ( + value, e_attachment_get_file_info ( + E_ATTACHMENT (object))); + return; + + case PROP_SHOWN: + g_value_set_boolean ( + value, e_attachment_get_shown ( + E_ATTACHMENT (object))); + return; + + case PROP_LOADING: + g_value_set_boolean ( + value, e_attachment_get_loading ( + E_ATTACHMENT (object))); + return; + + case PROP_MIME_PART: + g_value_set_boxed ( + value, e_attachment_get_mime_part ( + E_ATTACHMENT (object))); + return; + + case PROP_PERCENT: + g_value_set_int ( + value, e_attachment_get_percent ( + E_ATTACHMENT (object))); + return; + + case PROP_REFERENCE: + g_value_set_boxed ( + value, e_attachment_get_reference ( + E_ATTACHMENT (object))); + return; + + case PROP_SAVING: + g_value_set_boolean ( + value, e_attachment_get_saving ( + E_ATTACHMENT (object))); + return; + + case PROP_SIGNED: + g_value_set_int ( + value, e_attachment_get_signed ( + E_ATTACHMENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +attachment_dispose (GObject *object) +{ + EAttachmentPrivate *priv; + + priv = E_ATTACHMENT_GET_PRIVATE (object); + + if (priv->file != NULL) { + g_object_unref (priv->file); + priv->file = NULL; + } + + if (priv->file_info != NULL) { + g_object_unref (priv->file_info); + priv->file_info = NULL; + } + + if (priv->cancellable != NULL) { + g_object_unref (priv->cancellable); + priv->cancellable = NULL; + } + + if (priv->mime_part != NULL) { + camel_object_unref (priv->mime_part); + priv->mime_part = NULL; + } + + if (priv->emblem_timeout_id > 0) { + g_source_remove (priv->emblem_timeout_id); + priv->emblem_timeout_id = 0; + } + + /* This accepts NULL arguments. */ + gtk_tree_row_reference_free (priv->reference); + priv->reference = NULL; + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +attachment_finalize (GObject *object) +{ + EAttachmentPrivate *priv; + + priv = E_ATTACHMENT_GET_PRIVATE (object); + + g_free (priv->disposition); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +attachment_class_init (EAttachmentClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (EAttachmentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = attachment_set_property; + object_class->get_property = attachment_get_property; + object_class->dispose = attachment_dispose; + object_class->finalize = attachment_finalize; + + g_object_class_install_property ( + object_class, + PROP_CAN_SHOW, + g_param_spec_boolean ( + "can-show", + "Can Show", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_DISPOSITION, + g_param_spec_string ( + "disposition", + "Disposition", + NULL, + "attachment", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /* FIXME Define a GEnumClass for this. */ + g_object_class_install_property ( + object_class, + PROP_ENCRYPTED, + g_param_spec_int ( + "encrypted", + "Encrypted", + NULL, + CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE, + CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG, + CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_FILE, + g_param_spec_object ( + "file", + "File", + NULL, + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_FILE_INFO, + g_param_spec_object ( + "file-info", + "File Info", + NULL, + G_TYPE_FILE_INFO, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_LOADING, + g_param_spec_boolean ( + "loading", + "Loading", + NULL, + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_MIME_PART, + g_param_spec_boxed ( + "mime-part", + "MIME Part", + NULL, + E_TYPE_CAMEL_OBJECT, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_PERCENT, + g_param_spec_int ( + "percent", + "Percent", + NULL, + 0, + 100, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_REFERENCE, + g_param_spec_boxed ( + "reference", + "Reference", + NULL, + GTK_TYPE_TREE_ROW_REFERENCE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_SAVING, + g_param_spec_boolean ( + "saving", + "Saving", + NULL, + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_SHOWN, + g_param_spec_boolean ( + "shown", + "Shown", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /* FIXME Define a GEnumClass for this. */ + g_object_class_install_property ( + object_class, + PROP_SIGNED, + g_param_spec_int ( + "signed", + "Signed", + NULL, + CAMEL_CIPHER_VALIDITY_SIGN_NONE, + CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY, + CAMEL_CIPHER_VALIDITY_SIGN_NONE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +attachment_init (EAttachment *attachment) +{ + attachment->priv = E_ATTACHMENT_GET_PRIVATE (attachment); + attachment->priv->cancellable = g_cancellable_new (); + attachment->priv->encrypted = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE; + attachment->priv->signed_ = CAMEL_CIPHER_VALIDITY_SIGN_NONE; + + g_signal_connect ( + attachment, "notify::encrypted", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::file-info", + G_CALLBACK (attachment_update_file_info_columns), NULL); + + g_signal_connect ( + attachment, "notify::file-info", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::loading", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::loading", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::percent", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_file_info_columns), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::reference", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::saving", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect ( + attachment, "notify::saving", + G_CALLBACK (attachment_update_progress_columns), NULL); + + g_signal_connect ( + attachment, "notify::signed", + G_CALLBACK (attachment_update_icon_column), NULL); + + g_signal_connect_swapped ( + attachment->priv->cancellable, "cancelled", + G_CALLBACK (attachment_cancelled_cb), attachment); +} + +GType +e_attachment_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EAttachmentClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) attachment_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EAttachment), + 0, /* n_preallocs */ + (GInstanceInitFunc) attachment_init, + NULL /* value_table */ + }; + + type = g_type_register_static ( + G_TYPE_OBJECT, "EAttachment", &type_info, 0); + } + + return type; +} + +EAttachment * +e_attachment_new (void) +{ + return g_object_new (E_TYPE_ATTACHMENT, NULL); +} + +EAttachment * +e_attachment_new_for_path (const gchar *path) +{ + EAttachment *attachment; + GFile *file; + + g_return_val_if_fail (path != NULL, NULL); + + file = g_file_new_for_path (path); + attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL); + g_object_unref (file); + + return attachment; +} + +EAttachment * +e_attachment_new_for_uri (const gchar *uri) +{ + EAttachment *attachment; + GFile *file; + + g_return_val_if_fail (uri != NULL, NULL); + + file = g_file_new_for_uri (uri); + attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL); + g_object_unref (file); + + return attachment; +} + +EAttachment * +e_attachment_new_for_message (CamelMimeMessage *message) +{ + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + EAttachment *attachment; + GString *description; + const gchar *subject; + + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); + + mime_part = camel_mime_part_new (); + camel_mime_part_set_disposition (mime_part, "inline"); + subject = camel_mime_message_get_subject (message); + + description = g_string_new (_("Attached message")); + if (subject != NULL) + g_string_append_printf (description, " - %s", subject); + camel_mime_part_set_description (mime_part, description->str); + g_string_free (description, TRUE); + + wrapper = CAMEL_DATA_WRAPPER (message); + camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); + camel_mime_part_set_content_type (mime_part, "message/rfc822"); + + attachment = e_attachment_new (); + e_attachment_set_mime_part (attachment, mime_part); + camel_object_unref (mime_part); + + return attachment; +} + +void +e_attachment_add_to_multipart (EAttachment *attachment, + CamelMultipart *multipart, + const gchar *default_charset) +{ + CamelContentType *content_type; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + + /* XXX EMsgComposer might be a better place for this function. */ + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (CAMEL_IS_MULTIPART (multipart)); + + /* Still loading? Too bad. */ + mime_part = e_attachment_get_mime_part (attachment); + if (mime_part == NULL) + return; + + content_type = camel_mime_part_get_content_type (mime_part); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + + if (CAMEL_IS_MULTIPART (wrapper)) + goto exit; + + /* For text content, determine the best encoding and character set. */ + if (camel_content_type_is (content_type, "text", "*")) { + CamelTransferEncoding encoding; + CamelStreamFilter *filtered_stream; + CamelMimeFilterBestenc *filter; + CamelStream *stream; + const gchar *charset; + + charset = camel_content_type_param (content_type, "charset"); + + /* Determine the best encoding by writing the MIME + * part to a NULL stream with a "bestenc" filter. */ + stream = camel_stream_null_new (); + filtered_stream = camel_stream_filter_new_with_stream (stream); + filter = camel_mime_filter_bestenc_new ( + CAMEL_BESTENC_GET_ENCODING); + camel_stream_filter_add ( + filtered_stream, CAMEL_MIME_FILTER (filter)); + camel_data_wrapper_decode_to_stream ( + wrapper, CAMEL_STREAM (filtered_stream)); + camel_object_unref (filtered_stream); + camel_object_unref (stream); + + /* Retrieve the best encoding from the filter. */ + encoding = camel_mime_filter_bestenc_get_best_encoding ( + filter, CAMEL_BESTENC_8BIT); + camel_mime_part_set_encoding (mime_part, encoding); + camel_object_unref (filter); + + if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) { + /* The text fits within us-ascii, so this is safe. + * FIXME Check that this isn't iso-2022-jp? */ + default_charset = "us-ascii"; + + } else if (charset == NULL && default_charset == NULL) { + default_charset = attachment_get_default_charset (); + /* FIXME Check that this fits within the + * default_charset and if not, find one + * that does and/or allow the user to + * specify. */ + } + + if (charset == NULL) { + gchar *type; + + camel_content_type_set_param ( + content_type, "charset", default_charset); + type = camel_content_type_format (content_type); + camel_mime_part_set_content_type (mime_part, type); + g_free (type); + } + + /* Otherwise, unless it's a message/rfc822, Base64 encode it. */ + } else if (!CAMEL_IS_MIME_MESSAGE (wrapper)) + camel_mime_part_set_encoding ( + mime_part, CAMEL_TRANSFER_ENCODING_BASE64); + +exit: + camel_multipart_add_part (multipart, mime_part); +} + +void +e_attachment_cancel (EAttachment *attachment) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + g_cancellable_cancel (attachment->priv->cancellable); +} + +gboolean +e_attachment_get_can_show (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->can_show; +} + +void +e_attachment_set_can_show (EAttachment *attachment, + gboolean can_show) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->can_show = can_show; + + g_object_notify (G_OBJECT (attachment), "can-show"); +} + +const gchar * +e_attachment_get_disposition (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->disposition; +} + +void +e_attachment_set_disposition (EAttachment *attachment, + const gchar *disposition) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + g_free (attachment->priv->disposition); + attachment->priv->disposition = g_strdup (disposition); + + g_object_notify (G_OBJECT (attachment), "disposition"); +} + +GFile * +e_attachment_get_file (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->file; +} + +void +e_attachment_set_file (EAttachment *attachment, + GFile *file) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (file != NULL) { + g_return_if_fail (G_IS_FILE (file)); + g_object_ref (file); + } + + if (attachment->priv->file != NULL) + g_object_unref (attachment->priv->file); + + attachment->priv->file = file; + + g_object_notify (G_OBJECT (attachment), "file"); +} + +GFileInfo * +e_attachment_get_file_info (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->file_info; +} + +gboolean +e_attachment_get_loading (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->loading; +} + +CamelMimePart * +e_attachment_get_mime_part (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->mime_part; +} + +void +e_attachment_set_mime_part (EAttachment *attachment, + CamelMimePart *mime_part) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (mime_part != NULL) { + g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); + camel_object_ref (mime_part); + } + + if (attachment->priv->mime_part != NULL) + camel_object_unref (attachment->priv->mime_part); + + attachment->priv->mime_part = mime_part; + + g_object_notify (G_OBJECT (attachment), "mime-part"); +} + +gint +e_attachment_get_percent (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0); + + return attachment->priv->percent; +} + +GtkTreeRowReference * +e_attachment_get_reference (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + return attachment->priv->reference; +} + +void +e_attachment_set_reference (EAttachment *attachment, + GtkTreeRowReference *reference) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (reference != NULL) + reference = gtk_tree_row_reference_copy (reference); + + gtk_tree_row_reference_free (attachment->priv->reference); + attachment->priv->reference = reference; + + g_object_notify (G_OBJECT (attachment), "reference"); +} + +gboolean +e_attachment_get_saving (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->saving; +} + +gboolean +e_attachment_get_shown (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->shown; +} + +void +e_attachment_set_shown (EAttachment *attachment, + gboolean shown) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->shown = shown; + + g_object_notify (G_OBJECT (attachment), "shown"); +} + +camel_cipher_validity_encrypt_t +e_attachment_get_encrypted (EAttachment *attachment) +{ + g_return_val_if_fail ( + E_IS_ATTACHMENT (attachment), + CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE); + + return attachment->priv->encrypted; +} + +void +e_attachment_set_encrypted (EAttachment *attachment, + camel_cipher_validity_encrypt_t encrypted) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->encrypted = encrypted; + + g_object_notify (G_OBJECT (attachment), "encrypted"); +} + +camel_cipher_validity_sign_t +e_attachment_get_signed (EAttachment *attachment) +{ + g_return_val_if_fail ( + E_IS_ATTACHMENT (attachment), + CAMEL_CIPHER_VALIDITY_SIGN_NONE); + + return attachment->priv->signed_; +} + +void +e_attachment_set_signed (EAttachment *attachment, + camel_cipher_validity_sign_t signed_) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + attachment->priv->signed_ = signed_; + + g_object_notify (G_OBJECT (attachment), "signed"); +} + +const gchar * +e_attachment_get_description (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *attribute; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION; + file_info = e_attachment_get_file_info (attachment); + + if (file_info == NULL) + return NULL; + + return g_file_info_get_attribute_string (file_info, attribute); +} + +const gchar * +e_attachment_get_thumbnail_path (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *attribute; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH; + file_info = e_attachment_get_file_info (attachment); + + if (file_info == NULL) + return NULL; + + return g_file_info_get_attribute_byte_string (file_info, attribute); +} + +gboolean +e_attachment_is_rfc822 (EAttachment *attachment) +{ + GFileInfo *file_info; + const gchar *content_type; + gchar *mime_type; + gboolean is_rfc822; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + return FALSE; + + content_type = g_file_info_get_content_type (file_info); + if (content_type == NULL) + return FALSE; + + mime_type = g_content_type_get_mime_type (content_type); + is_rfc822 = (g_ascii_strcasecmp (mime_type, "message/rfc822") == 0); + g_free (mime_type); + + return is_rfc822; +} + +GList * +e_attachment_list_apps (EAttachment *attachment) +{ + GList *app_info_list; + GFileInfo *file_info; + const gchar *content_type; + const gchar *display_name; + gchar *allocated; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL); + + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + return NULL; + + content_type = g_file_info_get_content_type (file_info); + display_name = g_file_info_get_display_name (file_info); + g_return_val_if_fail (content_type != NULL, NULL); + + app_info_list = g_app_info_get_all_for_type (content_type); + + if (app_info_list != NULL || display_name == NULL) + goto exit; + + if (!g_content_type_is_unknown (content_type)) + goto exit; + + allocated = g_content_type_guess (display_name, NULL, 0, NULL); + app_info_list = g_app_info_get_all_for_type (allocated); + g_free (allocated); + +exit: + return app_info_list; +} + +/************************* e_attachment_load_async() *************************/ + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + + GInputStream *input_stream; + GOutputStream *output_stream; + GFileInfo *file_info; + goffset total_num_bytes; + gssize bytes_read; + gchar buffer[4096]; +}; + +/* Forward Declaration */ +static void +attachment_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context); + +static LoadContext * +attachment_load_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_load_async); + + load_context = g_slice_new0 (LoadContext); + load_context->attachment = g_object_ref (attachment); + load_context->simple = simple; + + attachment_set_loading (load_context->attachment, TRUE); + + return load_context; +} + +static void +attachment_load_context_free (LoadContext *load_context) +{ + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (load_context->attachment); + + if (load_context->input_stream != NULL) + g_object_unref (load_context->input_stream); + + if (load_context->output_stream != NULL) + g_object_unref (load_context->output_stream); + + if (load_context->file_info != NULL) + g_object_unref (load_context->file_info); + + g_slice_free (LoadContext, load_context); +} + +static gboolean +attachment_load_check_for_error (LoadContext *load_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + /* Steal the result. */ + simple = load_context->simple; + load_context->simple = NULL; + + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); + + attachment_load_context_free (load_context); + + return TRUE; +} + +static void +attachment_load_finish (LoadContext *load_context) +{ + GFileInfo *file_info; + EAttachment *attachment; + GMemoryOutputStream *output_stream; + GSimpleAsyncResult *simple; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + const gchar *attribute; + const gchar *content_type; + const gchar *display_name; + const gchar *description; + const gchar *disposition; + gchar *mime_type; + gpointer data; + gsize size; + + /* Steal the result. */ + simple = load_context->simple; + load_context->simple = NULL; + + file_info = load_context->file_info; + attachment = load_context->attachment; + output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream); + + if (e_attachment_is_rfc822 (attachment)) + wrapper = (CamelDataWrapper *) camel_mime_message_new (); + else + wrapper = camel_data_wrapper_new (); + + content_type = g_file_info_get_content_type (file_info); + mime_type = g_content_type_get_mime_type (content_type); + + data = g_memory_output_stream_get_data (output_stream); + size = g_memory_output_stream_get_data_size (output_stream); + + stream = camel_stream_mem_new_with_buffer (data, size); + camel_data_wrapper_construct_from_stream (wrapper, stream); + camel_data_wrapper_set_mime_type (wrapper, mime_type); + camel_stream_close (stream); + camel_object_unref (stream); + + mime_part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (mime_part), wrapper); + + camel_object_unref (wrapper); + g_free (mime_type); + + display_name = g_file_info_get_display_name (file_info); + if (display_name != NULL) + camel_mime_part_set_filename (mime_part, display_name); + + attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION; + description = g_file_info_get_attribute_string (file_info, attribute); + if (description != NULL) + camel_mime_part_set_description (mime_part, description); + + disposition = e_attachment_get_disposition (attachment); + if (disposition != NULL) + camel_mime_part_set_disposition (mime_part, disposition); + + g_simple_async_result_set_op_res_gpointer ( + simple, mime_part, (GDestroyNotify) camel_object_unref); + + g_simple_async_result_complete (simple); + + attachment_load_context_free (load_context); +} + +static void +attachment_load_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); + + if (attachment_load_check_for_error (load_context, error)) + return; + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + input_stream = load_context->input_stream; + + attachment_progress_cb ( + g_seekable_tell (G_SEEKABLE (output_stream)), + load_context->total_num_bytes, attachment); + + if (bytes_written < load_context->bytes_read) { + g_memmove ( + load_context->buffer, + load_context->buffer + bytes_written, + load_context->bytes_read - bytes_written); + load_context->bytes_read -= bytes_written; + + g_output_stream_write_async ( + output_stream, + load_context->buffer, + load_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_load_write_cb, + load_context); + } else + g_input_stream_read_async ( + input_stream, + load_context->buffer, + sizeof (load_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_load_stream_read_cb, + load_context); +} + +static void +attachment_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); + + if (attachment_load_check_for_error (load_context, error)) + return; + + if (bytes_read == 0) { + attachment_load_finish (load_context); + return; + } + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = load_context->output_stream; + load_context->bytes_read = bytes_read; + + g_output_stream_write_async ( + output_stream, + load_context->buffer, + load_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_load_write_cb, + load_context); +} + +static void +attachment_load_file_read_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileInputStream *input_stream; + GOutputStream *output_stream; + GError *error = NULL; + + /* Input stream might be NULL, so don't use cast macro. */ + input_stream = g_file_read_finish (file, result, &error); + load_context->input_stream = (GInputStream *) input_stream; + + if (attachment_load_check_for_error (load_context, error)) + return; + + /* Load the contents into a GMemoryOutputStream. */ + output_stream = g_memory_output_stream_new ( + NULL, 0, g_realloc, g_free); + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + load_context->output_stream = output_stream; + + g_input_stream_read_async ( + load_context->input_stream, + load_context->buffer, + sizeof (load_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_load_stream_read_cb, + load_context); +} + +static void +attachment_load_query_info_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileInfo *file_info; + GError *error = NULL; + + attachment = load_context->attachment; + cancellable = attachment->priv->cancellable; + + file_info = g_file_query_info_finish (file, result, &error); + attachment_set_file_info (attachment, file_info); + load_context->file_info = file_info; + + if (attachment_load_check_for_error (load_context, error)) + return; + + load_context->total_num_bytes = g_file_info_get_size (file_info); + + g_file_read_async ( + file, G_PRIORITY_DEFAULT, + cancellable, (GAsyncReadyCallback) + attachment_load_file_read_cb, load_context); +} + +static void +attachment_load_from_mime_part (LoadContext *load_context) +{ + GFileInfo *file_info; + EAttachment *attachment; + GSimpleAsyncResult *simple; + CamelContentType *content_type; + CamelMimePart *mime_part; + const gchar *attribute; + const gchar *string; + gchar *allocated; + goffset size; + + attachment = load_context->attachment; + mime_part = e_attachment_get_mime_part (attachment); + + file_info = g_file_info_new (); + load_context->file_info = file_info; + + content_type = camel_mime_part_get_content_type (mime_part); + allocated = camel_content_type_simple (content_type); + if (allocated != NULL) { + GIcon *icon; + gchar *cp; + + /* GIO expects lowercase MIME types. */ + for (cp = allocated; *cp != '\0'; cp++) + *cp = g_ascii_tolower (*cp); + + /* Swap the MIME type for a content type. */ + cp = g_content_type_from_mime_type (allocated); + g_free (allocated); + allocated = cp; + + /* Use the MIME part's filename if we have to. */ + if (g_content_type_is_unknown (allocated)) { + string = camel_mime_part_get_filename (mime_part); + if (string != NULL) { + g_free (allocated); + allocated = g_content_type_guess ( + string, NULL, 0, NULL); + } + } + + g_file_info_set_content_type (file_info, allocated); + + icon = g_content_type_get_icon (allocated); + if (icon != NULL) { + g_file_info_set_icon (file_info, icon); + g_object_unref (icon); + } + } + g_free (allocated); + + string = camel_mime_part_get_filename (mime_part); + if (string == NULL) + /* Translators: Default attachment filename. */ + string = _("attachment.dat"); + g_file_info_set_display_name (file_info, string); + + attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION; + string = camel_mime_part_get_description (mime_part); + if (string != NULL) + g_file_info_set_attribute_string ( + file_info, attribute, string); + + size = (goffset) camel_mime_part_get_content_size (mime_part); + g_file_info_set_size (file_info, size); + + string = camel_mime_part_get_disposition (mime_part); + e_attachment_set_disposition (attachment, string); + + attachment_set_file_info (attachment, file_info); + + /* Steal the result. */ + simple = load_context->simple; + load_context->simple = NULL; + + camel_object_ref (mime_part); + g_simple_async_result_set_op_res_gpointer ( + simple, mime_part, + (GDestroyNotify) camel_object_unref); + g_simple_async_result_complete_in_idle (simple); + + attachment_load_context_free (load_context); +} + +void +e_attachment_load_async (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContext *load_context; + GCancellable *cancellable; + CamelMimePart *mime_part; + GFile *file; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (callback != NULL); + + if (e_attachment_get_loading (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A load operation is already in progress")); + return; + } + + if (e_attachment_get_saving (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A save operation is already in progress")); + return; + } + + file = e_attachment_get_file (attachment); + mime_part = e_attachment_get_mime_part (attachment); + g_return_if_fail (file != NULL || mime_part != NULL); + + load_context = attachment_load_context_new ( + attachment, callback, user_data); + + cancellable = attachment->priv->cancellable; + g_cancellable_reset (cancellable); + + if (file != NULL) + g_file_query_info_async ( + file, ATTACHMENT_QUERY, + G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT, + cancellable, (GAsyncReadyCallback) + attachment_load_query_info_cb, load_context); + + else if (mime_part != NULL) + attachment_load_from_mime_part (load_context); -static GObjectClass *parent_class = NULL; +} -static void -changed (EAttachment *attachment) +gboolean +e_attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) { - g_signal_emit (attachment, signals[CHANGED], 0); -} + GSimpleAsyncResult *simple; + CamelMimePart *mime_part; + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); -/* GtkObject methods. */ + simple = G_SIMPLE_ASYNC_RESULT (result); + mime_part = g_simple_async_result_get_op_res_gpointer (simple); + if (mime_part != NULL) + e_attachment_set_mime_part (attachment, mime_part); + g_simple_async_result_propagate_error (simple, error); + g_object_unref (simple); -static void -finalise (GObject *object) + attachment_set_loading (attachment, FALSE); + + return (mime_part != NULL); +} + +void +e_attachment_load_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) { - EAttachment *attachment = (EAttachment *) object; GtkWidget *dialog; + GFileInfo *file_info; + GtkTreeRowReference *reference; + const gchar *display_name; + const gchar *primary_text; + GError *error = NULL; - if (attachment->editor_gui != NULL) { - dialog = glade_xml_get_widget (attachment->editor_gui, "dialog"); - g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_CLOSE); - } + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (GTK_IS_WINDOW (parent)); - if (attachment->is_available_local) { - camel_object_unref (attachment->body); - if (attachment->pixbuf_cache != NULL) - g_object_unref (attachment->pixbuf_cache); - } else { - if (attachment->cancellable) { - /* the operation is still running, so cancel it */ - g_cancellable_cancel (attachment->cancellable); - attachment->cancellable = NULL; - } - g_free (attachment->description); + if (e_attachment_load_finish (attachment, result, &error)) + return; + + /* XXX Calling EAttachmentStore functions from here violates + * the abstraction, but for now it's not hurting anything. */ + reference = e_attachment_get_reference (attachment); + if (gtk_tree_row_reference_valid (reference)) { + GtkTreeModel *model; + + model = gtk_tree_row_reference_get_model (reference); + + e_attachment_store_remove_attachment ( + E_ATTACHMENT_STORE (model), attachment); } - g_free (attachment->file_name); - g_free (attachment->store_uri); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; - G_OBJECT_CLASS (parent_class)->finalize (object); -} + file_info = e_attachment_get_file_info (attachment); + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; -/* Signals. */ + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not load '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not load the attachment")); -static void -real_changed (EAttachment *attachment) -{ - g_return_if_fail (E_IS_ATTACHMENT (attachment)); -} + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", primary_text); -static void -real_update_attachment (EAttachment *attachment, char *msg) -{ - g_return_if_fail (E_IS_ATTACHMENT (attachment)); + 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); } +/************************* e_attachment_open_async() *************************/ -static void -class_init (EAttachmentClass *klass) -{ - GObjectClass *object_class; +typedef struct _OpenContext OpenContext; - object_class = (GObjectClass*) klass; - parent_class = g_type_class_ref (G_TYPE_OBJECT); - - object_class->finalize = finalise; - klass->changed = real_changed; - klass->update = real_update_attachment; - - signals[CHANGED] = g_signal_new ("changed", - E_TYPE_ATTACHMENT, - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (EAttachmentClass, changed), - NULL, - NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - signals[UPDATE] = g_signal_new ("update", - E_TYPE_ATTACHMENT, - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (EAttachmentClass, update), - NULL, - NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - -} - -static void -init (EAttachment *attachment) -{ - attachment->editor_gui = NULL; - attachment->body = NULL; - attachment->size = 0; - attachment->pixbuf_cache = NULL; - attachment->index = -1; - attachment->file_name = NULL; - attachment->percentage = -1; - attachment->description = NULL; - attachment->disposition = FALSE; - attachment->sign = CAMEL_CIPHER_VALIDITY_SIGN_NONE; - attachment->encrypt = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE; - attachment->store_uri = NULL; - attachment->cancellable = NULL; -} +struct _OpenContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; -GType -e_attachment_get_type (void) + GAppInfo *app_info; +}; + +static OpenContext * +attachment_open_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) { - static GType type = 0; + OpenContext *open_context; + GSimpleAsyncResult *simple; - if (type == 0) { - static const GTypeInfo info = { - sizeof (EAttachmentClass), - NULL, - NULL, - (GClassInitFunc) class_init, - NULL, - NULL, - sizeof (EAttachment), - 0, - (GInstanceInitFunc) init, - }; + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_open_async); - type = g_type_register_static (G_TYPE_OBJECT, "EAttachment", &info, 0); - } + open_context = g_slice_new0 (OpenContext); + open_context->attachment = g_object_ref (attachment); + open_context->simple = simple; - return type; + return open_context; } -/** - * file_ext_is: - * @param file_name: path for file - * @param ext: desired extension, with a dot - * @return if file_name has extension ext or not - **/ +static void +attachment_open_context_free (OpenContext *open_context) +{ + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (open_context->attachment); + + if (open_context->app_info != NULL) + g_object_unref (open_context->app_info); + + g_slice_free (OpenContext, open_context); +} static gboolean -file_ext_is (const char *file_name, const char *ext) +attachment_open_check_for_error (OpenContext *open_context, + GError *error) { - int i, dot = -1; + GSimpleAsyncResult *simple; - if (!file_name || !ext) + if (error == NULL) return FALSE; - for (i = 0; file_name[i]; i++) { - if (file_name [i] == '.') - dot = i; - } + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; - if (dot > 0) { - return 0 == g_ascii_strcasecmp (file_name + dot, ext); - } + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); - return FALSE; + attachment_open_context_free (open_context); + + return TRUE; } -static char * -attachment_guess_mime_type (const char *file_name) +static void +attachment_open_file (GFile *file, + OpenContext *open_context) { - char *type; - gchar *content = NULL; + GdkAppLaunchContext *context; + GSimpleAsyncResult *simple; + GList *file_list; + gboolean success; + GError *error = NULL; - type = e_util_guess_mime_type (file_name, TRUE); + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; - if (type && strcmp (type, "text/directory") == 0 && - file_ext_is (file_name, ".vcf") && - g_file_get_contents (file_name, &content, NULL, NULL) && - content) { - EVCard *vc = e_vcard_new_from_string (content); + /* Find a default app based on content type. */ + if (open_context->app_info == NULL) { + EAttachment *attachment; + GFileInfo *file_info; + const gchar *content_type; - if (vc) { - g_free (type); - g_object_unref (G_OBJECT (vc)); + attachment = open_context->attachment; + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + goto exit; - type = g_strdup ("text/x-vcard"); - } + content_type = g_file_info_get_content_type (file_info); + if (content_type == NULL) + goto exit; + open_context->app_info = g_app_info_get_default_for_type ( + content_type, FALSE); } - g_free (content); + if (open_context->app_info == NULL) + goto exit; - if (type) { - /* Check if returned mime_type is valid */ - CamelContentType *ctype = camel_content_type_decode (type); + context = gdk_app_launch_context_new (); + file_list = g_list_prepend (NULL, file); - if (!ctype) { - g_free (type); - type = NULL; - } else - camel_content_type_unref (ctype); + success = g_app_info_launch ( + open_context->app_info, file_list, + G_APP_LAUNCH_CONTEXT (context), &error); + + g_simple_async_result_set_op_res_gboolean (simple, success); + + g_list_free (file_list); + g_object_unref (context); + +exit: + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); } - return type; + g_simple_async_result_complete (simple); + attachment_open_context_free (open_context); } - -/** - * e_attachment_new: - * @file_name: filename to attach - * @disposition: Content-Disposition of the attachment - * @ex: exception - * - * Return value: the new attachment, or %NULL on error - **/ -EAttachment * -e_attachment_new (const char *file_name, const char *disposition, CamelException *ex) +static void +attachment_open_save_finished_cb (EAttachment *attachment, + GAsyncResult *result, + OpenContext *open_context) { - EAttachment *new; - CamelMimePart *part; - CamelDataWrapper *wrapper; - CamelStream *stream; - struct stat statbuf; - char *mime_type; - char *filename; - CamelURL *url; + GFile *file; + GError *error = NULL; - g_return_val_if_fail (file_name != NULL, NULL); + file = e_attachment_save_finish (attachment, result, &error); - if (g_stat (file_name, &statbuf) < 0) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); - return NULL; - } + if (attachment_open_check_for_error (open_context, error)) + return; - /* return if it's not a regular file */ - if (!S_ISREG (statbuf.st_mode)) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: not a regular file"), - file_name); - return NULL; - } + attachment_open_file (file, open_context); + g_object_unref (file); +} - if (!(stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); - return NULL; - } +static void +attachment_open_save_temporary (OpenContext *open_context) +{ + GFile *file; + gchar *template; + gchar *path; + GError *error = NULL; - if ((mime_type = attachment_guess_mime_type (file_name))) { - if (!g_ascii_strcasecmp (mime_type, "message/rfc822")) { - wrapper = (CamelDataWrapper *) camel_mime_message_new (); - } else { - wrapper = camel_data_wrapper_new (); - } + errno = 0; - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - } else { - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, "application/octet-stream"); - } + /* XXX This could trigger a blocking temp directory cleanup. */ + template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mktemp (template); + g_free (template); - camel_object_unref (stream); + /* XXX Let's hope errno got set properly. */ + if (path == NULL) + g_set_error ( + &error, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); - part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); - camel_object_unref (wrapper); + /* We already know if there's an error, but this does the cleanup. */ + if (attachment_open_check_for_error (open_context, error)) + return; - camel_mime_part_set_disposition (part, disposition); - filename = g_path_get_basename (file_name); - camel_mime_part_set_filename (part, filename); - -#if 0 - /* Note: Outlook 2002 is broken with respect to Content-Ids on - non-multipart/related parts, so as an interoperability - workaround, don't set a Content-Id on these parts. Fixes - bug #10032 */ - /* set the Content-Id */ - content_id = camel_header_msgid_generate (); - camel_mime_part_set_content_id (part, content_id); - g_free (content_id); -#endif + file = g_file_new_for_path (path); - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - new->body = part; - new->size = statbuf.st_size; - new->guessed_type = TRUE; - new->cancellable = NULL; - new->is_available_local = TRUE; - new->file_name = filename; + g_free (path); - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, file_name); - new->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); + e_attachment_save_async ( + open_context->attachment, file, (GAsyncReadyCallback) + attachment_open_save_finished_cb, open_context); - return new; + g_object_unref (file); } +void +e_attachment_open_async (EAttachment *attachment, + GAppInfo *app_info, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OpenContext *open_context; + CamelMimePart *mime_part; + GFile *file; -typedef struct { - EAttachment *attachment; - char *file_name; - char *uri; - GtkWindow *parent; /* for error dialog */ - - guint64 file_size; /* zero indicates unknown size */ - GInputStream *istream; /* read from here ... */ - GOutputStream *ostream; /* ...and write into this. */ - gboolean was_error; - GCancellable *cancellable; + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (callback != NULL); - void *buffer; /* read into this, not more than buffer_size bytes */ - gsize buffer_size; -} DownloadInfo; + file = e_attachment_get_file (attachment); + mime_part = e_attachment_get_mime_part (attachment); + g_return_if_fail (file != NULL || mime_part != NULL); -static void -download_info_free (DownloadInfo *download_info) -{ - /* if there was an error, then free attachment too */ - if (download_info->was_error) - g_object_unref (download_info->attachment); + open_context = attachment_open_context_new ( + attachment, callback, user_data); + + if (G_IS_APP_INFO (app_info)) + open_context->app_info = g_object_ref (app_info); + + /* If the attachment already references a GFile, we can launch + * the application directly. Otherwise we have to save the MIME + * part to a temporary file and launch the application from that. */ + if (file != NULL) { + attachment_open_file (file, open_context); + + } else if (mime_part != NULL) + attachment_open_save_temporary (open_context); +} - if (download_info->ostream) - g_object_unref (download_info->ostream); +gboolean +e_attachment_open_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gboolean success; - if (download_info->istream) - g_object_unref (download_info->istream); + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); - if (download_info->cancellable) - g_object_unref (download_info->cancellable); + simple = G_SIMPLE_ASYNC_RESULT (result); + success = g_simple_async_result_get_op_res_gboolean (simple); + g_simple_async_result_propagate_error (simple, error); + g_object_unref (simple); - g_free (download_info->file_name); - g_free (download_info->uri); - g_free (download_info->buffer); - g_free (download_info); + return success; } -static void -data_ready_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +void +e_attachment_open_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) { - DownloadInfo *download_info = (DownloadInfo *)user_data; + GtkWidget *dialog; + GFileInfo *file_info; + const gchar *display_name; + const gchar *primary_text; GError *error = NULL; - gssize read; - g_return_if_fail (download_info != NULL); - - if (g_cancellable_is_cancelled (download_info->cancellable)) { - /* finish the operation and close both streams */ - g_input_stream_read_finish (G_INPUT_STREAM (source_object), res, NULL); + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (GTK_IS_WINDOW (parent)); - g_output_stream_close (download_info->ostream, NULL, NULL); - g_input_stream_close (download_info->istream, NULL, NULL); + if (e_attachment_open_finish (attachment, result, &error)) + return; - /* The only way how to get this canceled is in EAttachment's finalize method, - and because the download_info_free free's the attachment on error, - then do not consider cancellation as an error. */ - download_info_free (download_info); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; - } - read = g_input_stream_read_finish (G_INPUT_STREAM (source_object), res, &error); + file_info = e_attachment_get_file_info (attachment); - if (!error) - g_output_stream_write_all (download_info->ostream, download_info->buffer, read, NULL, download_info->cancellable, &error); + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; - if (error) { - download_info->was_error = error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED; - if (download_info->was_error) - e_error_run (download_info->parent, "mail-composer:no-attach", download_info->uri, error->message, NULL); + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not open '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not open the attachment")); - g_error_free (error); + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", primary_text); - download_info->attachment->cancellable = NULL; - download_info_free (download_info); - return; - } + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - if (read == 0) { - CamelException ex; + gtk_dialog_run (GTK_DIALOG (dialog)); - /* done with reading */ - g_output_stream_close (download_info->ostream, NULL, NULL); - g_input_stream_close (download_info->istream, NULL, NULL); + gtk_widget_destroy (dialog); + g_error_free (error); +} - download_info->attachment->cancellable = NULL; +/************************* e_attachment_save_async() *************************/ - camel_exception_init (&ex); - e_attachment_build_remote_file (download_info->file_name, download_info->attachment, "attachment", &ex); +typedef struct _SaveContext SaveContext; - if (camel_exception_is_set (&ex)) { - download_info->was_error = TRUE; - e_error_run (download_info->parent, "mail-composer:no-attach", download_info->uri, camel_exception_get_description (&ex), NULL); - camel_exception_clear (&ex); - } +struct _SaveContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + + GFile *directory; + GFile *destination; + GInputStream *input_stream; + GOutputStream *output_stream; + goffset total_num_bytes; + gssize bytes_read; + gchar buffer[4096]; + gint count; +}; - download_info->attachment->percentage = -1; - download_info->attachment->is_available_local = TRUE; - g_signal_emit (download_info->attachment, signals[UPDATE], 0); +/* Forward Declaration */ +static void +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context); + +static SaveContext * +attachment_save_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GSimpleAsyncResult *simple; - download_info_free (download_info); - return; - } else if (download_info->file_size) { - download_info->attachment->percentage = read * 100 / download_info->file_size; - download_info->file_size -= MIN (download_info->file_size, read); - g_signal_emit (download_info->attachment, signals[UPDATE], 0); - } else { - download_info->attachment->percentage = 0; - g_signal_emit (download_info->attachment, signals[UPDATE], 0); - } + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_save_async); + + save_context = g_slice_new0 (SaveContext); + save_context->attachment = g_object_ref (attachment); + save_context->simple = simple; - /* read next chunk */ - g_input_stream_read_async (download_info->istream, download_info->buffer, download_info->buffer_size, G_PRIORITY_DEFAULT, download_info->cancellable, data_ready_cb, download_info); + attachment_set_saving (save_context->attachment, TRUE); + + return save_context; } -static gboolean -download_to_local_path (DownloadInfo *download_info, CamelException *ex) +static void +attachment_save_context_free (SaveContext *save_context) { - GError *error = NULL; - GFile *src = g_file_new_for_uri (download_info->uri); - GFile *des = g_file_new_for_path (download_info->file_name); - gboolean res = FALSE; + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (save_context->attachment); - g_return_val_if_fail (src != NULL && des != NULL, FALSE); + if (save_context->directory != NULL) + g_object_unref (save_context->directory); - download_info->ostream = G_OUTPUT_STREAM (g_file_replace (des, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error)); + if (save_context->destination != NULL) + g_object_unref (save_context->destination); - if (download_info->ostream && !error) { - GFileInfo *fi; + if (save_context->input_stream != NULL) + g_object_unref (save_context->input_stream); - fi = g_file_query_info (src, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (save_context->output_stream != NULL) + g_object_unref (save_context->output_stream); - if (fi) { - download_info->file_size = g_file_info_get_attribute_uint64 (fi, G_FILE_ATTRIBUTE_STANDARD_SIZE); - g_object_unref (fi); - } else { - download_info->file_size = 0; - } + g_slice_free (SaveContext, save_context); +} - download_info->istream = G_INPUT_STREAM (g_file_read (src, NULL, &error)); +static gboolean +attachment_save_check_for_error (SaveContext *save_context, + GError *error) +{ + GSimpleAsyncResult *simple; - if (download_info->istream && !error) { - download_info->cancellable = g_cancellable_new (); - download_info->attachment->cancellable = download_info->cancellable; - download_info->buffer_size = 10240; /* max 10KB chunk */ - download_info->buffer = g_malloc (sizeof (char) * download_info->buffer_size); + if (error == NULL) + return FALSE; - g_input_stream_read_async (download_info->istream, download_info->buffer, download_info->buffer_size, G_PRIORITY_DEFAULT, download_info->cancellable, data_ready_cb, download_info); + /* Steal the result. */ + simple = save_context->simple; + save_context->simple = NULL; - res = TRUE; - } - } + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); - if (error) { - /* propagate error */ - if (ex) - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, error->message); + attachment_save_context_free (save_context); - g_error_free (error); - download_info->was_error = TRUE; - download_info_free (download_info); + return TRUE; +} + +static GFile * +attachment_save_new_candidate (SaveContext *save_context) +{ + GFile *candidate; + GFileInfo *file_info; + EAttachment *attachment; + const gchar *display_name; + gchar *basename; + + attachment = save_context->attachment; + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + if (display_name == NULL) + /* Translators: Default attachment filename. */ + display_name = _("attachment.dat"); + + if (save_context->count == 0) + basename = g_strdup (display_name); + else { + GString *string; + const gchar *ext; + gsize length; + + string = g_string_sized_new (strlen (display_name)); + ext = g_utf8_strchr (display_name, -1, '.'); + + if (ext != NULL) + length = ext - display_name; + else + length = strlen (display_name); + + g_string_append_len (string, display_name, length); + g_string_append_printf (string, " (%d)", save_context->count); + g_string_append (string, (ext != NULL) ? ext : ""); + + basename = g_string_free (string, FALSE); } - g_object_unref (src); - g_object_unref (des); + save_context->count++; + + candidate = g_file_get_child (save_context->directory, basename); + + g_free (basename); - return res; + return candidate; } -EAttachment * -e_attachment_new_remote_file (GtkWindow *error_dlg_parent, const char *uri, const char *disposition, const char *path, CamelException *ex) +static void +attachment_save_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + SaveContext *save_context) { - EAttachment *new; - DownloadInfo *download_info; - CamelURL *url; - char *base; + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; - g_return_val_if_fail (uri != NULL, NULL); + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); - url = camel_url_new (uri, NULL); - base = g_path_get_basename (url->path); - camel_url_free (url); - - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - new->body = NULL; - new->size = 0; - new->guessed_type = FALSE; - new->cancellable = NULL; - new->is_available_local = FALSE; - new->percentage = 0; - new->file_name = g_build_filename (path, base, NULL); - - g_free (base); - - download_info = g_new0 (DownloadInfo, 1); - download_info->attachment = new; - download_info->file_name = g_strdup (new->file_name); - download_info->uri = g_strdup (uri); - download_info->parent = error_dlg_parent; - download_info->was_error = FALSE; - - /* it frees all on the error, so do not free it twice */ - if (!download_to_local_path (download_info, ex)) - return NULL; + if (attachment_save_check_for_error (save_context, error)) + return; - return new; + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + input_stream = save_context->input_stream; + + if (bytes_written < save_context->bytes_read) { + g_memmove ( + save_context->buffer, + save_context->buffer + bytes_written, + save_context->bytes_read - bytes_written); + save_context->bytes_read -= bytes_written; + + g_output_stream_write_async ( + output_stream, + save_context->buffer, + save_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_write_cb, + save_context); + } else + g_input_stream_read_async ( + input_stream, + save_context->buffer, + sizeof (save_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_read_cb, + save_context); } - -void -e_attachment_build_remote_file (const char *file_name, EAttachment *attachment, const char *disposition, CamelException *ex) +static void +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context) { - CamelMimePart *part; - CamelDataWrapper *wrapper; - CamelStream *stream; - struct stat statbuf; - char *mime_type; - char *filename; - CamelURL *url; - - g_return_if_fail (file_name != NULL); - - if (g_stat (file_name, &statbuf) == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); - g_message ("Cannot attach file %s: %s\n", file_name, g_strerror (errno)); - return; - } + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; - /* return if it's not a regular file */ - if (!S_ISREG (statbuf.st_mode)) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: not a regular file"), - file_name); - g_message ("Cannot attach file %s: not a regular file", file_name); - return; - } + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); - if (!(stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Cannot attach file %s: %s"), - file_name, g_strerror (errno)); + if (attachment_save_check_for_error (save_context, error)) return; - } - - if ((mime_type = attachment_guess_mime_type (file_name))) { - if (!g_ascii_strcasecmp (mime_type, "message/rfc822")) { - wrapper = (CamelDataWrapper *) camel_mime_message_new (); - } else { - wrapper = camel_data_wrapper_new (); - } - - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - } else { - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_data_wrapper_set_mime_type (wrapper, "application/octet-stream"); - } - camel_object_unref (stream); + if (bytes_read == 0) { + GSimpleAsyncResult *simple; + GFile *destination; - part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); - camel_object_unref (wrapper); + /* Steal the result. */ + simple = save_context->simple; + save_context->simple = NULL; - if (attachment->disposition) - camel_mime_part_set_disposition (part, "inline"); - else - camel_mime_part_set_disposition (part, "attachment"); + /* Steal the destination. */ + destination = save_context->destination; + save_context->destination = NULL; - if (!attachment->file_name) - filename = g_path_get_basename (file_name); - else - filename = g_path_get_basename (attachment->file_name); + g_simple_async_result_set_op_res_gpointer ( + simple, destination, (GDestroyNotify) g_object_unref); + g_simple_async_result_complete (simple); - camel_mime_part_set_filename (part, filename); + attachment_save_context_free (save_context); - if (attachment->description) { - camel_mime_part_set_description (part, attachment->description); - g_free (attachment->description); - attachment->description = NULL; + return; } - attachment->editor_gui = NULL; - attachment->body = part; - attachment->size = statbuf.st_size; - attachment->guessed_type = TRUE; - g_free (attachment->file_name); - attachment->file_name = filename; + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = save_context->output_stream; + save_context->bytes_read = bytes_read; + + attachment_progress_cb ( + g_seekable_tell (G_SEEKABLE (input_stream)), + save_context->total_num_bytes, attachment); + + g_output_stream_write_async ( + output_stream, + save_context->buffer, + save_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_write_cb, + save_context); +} - url = camel_url_new ("file://", NULL); - camel_url_set_path (url, file_name); - attachment->store_uri = camel_url_to_string (url, 0); - camel_url_free (url); +static void +attachment_save_got_output_stream (SaveContext *save_context) +{ + GCancellable *cancellable; + GInputStream *input_stream; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + EAttachment *attachment; + GByteArray *buffer; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + mime_part = e_attachment_get_mime_part (attachment); + + /* Decode the MIME part to an in-memory buffer. We have to do + * this because CamelStream is synchronous-only, and using threads + * is dangerous because CamelDataWrapper is not reentrant. */ + buffer = g_byte_array_new (); + stream = camel_stream_mem_new (); + camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + camel_data_wrapper_decode_to_stream (wrapper, stream); + camel_object_unref (stream); + /* Load the buffer into a GMemoryInputStream. */ + input_stream = g_memory_input_stream_new_from_data ( + buffer->data, (gssize) buffer->len, + (GDestroyNotify) g_free); + save_context->input_stream = input_stream; + save_context->total_num_bytes = (goffset) buffer->len; + g_byte_array_free (buffer, FALSE); + + g_input_stream_read_async ( + input_stream, + save_context->buffer, + sizeof (save_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_read_cb, + save_context); } - -/** - * e_attachment_new_from_mime_part: - * @part: a CamelMimePart - * - * Return value: a new EAttachment based on the mime part - **/ -EAttachment * -e_attachment_new_from_mime_part (CamelMimePart *part) +static void +attachment_save_create_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) { - EAttachment *new; + EAttachment *attachment; + GCancellable *cancellable; + GFileOutputStream *output_stream; + GError *error = NULL; - g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_create_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; - new = g_object_new (E_TYPE_ATTACHMENT, NULL); - new->editor_gui = NULL; - camel_object_ref (part); - new->body = part; - new->guessed_type = FALSE; - new->is_available_local = TRUE; - new->size = camel_mime_part_get_content_size (part); - new->file_name = g_strdup (camel_mime_part_get_filename(part)); + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; - return new; -} + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + destination = attachment_save_new_candidate (save_context); - -/* The attachment property dialog. */ + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); -typedef struct { - GtkWidget *dialog; - GtkEntry *file_name_entry; - GtkEntry *description_entry; - GtkEntry *mime_type_entry; - GtkToggleButton *disposition_checkbox; - EAttachment *attachment; -} DialogData; + g_object_unref (destination); + g_error_free (error); + return; + } -static void -destroy_dialog_data (DialogData *data) -{ - g_free (data); -} + if (attachment_save_check_for_error (save_context, error)) + return; -/* - * fixme: I am converting EVERYTHING to/from UTF-8, although mime types - * are in ASCII. This is not strictly necessary, but we want to be - * consistent and possibly check for errors somewhere. - */ + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); +} static void -set_entry (GladeXML *xml, const char *widget_name, const char *value) +attachment_save_replace_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) { - GtkEntry *entry; + GFileOutputStream *output_stream; + GError *error = NULL; - entry = GTK_ENTRY (glade_xml_get_widget (xml, widget_name)); - if (entry == NULL) - g_warning ("Entry for `%s' not found.", widget_name); - else - gtk_entry_set_text (entry, value ? value : ""); -} + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_replace_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; -static void -connect_widget (GladeXML *gui, const char *name, const char *signal_name, - GCallback func, gpointer data) -{ - GtkWidget *widget; + if (attachment_save_check_for_error (save_context, error)) + return; - widget = glade_xml_get_widget (gui, name); - g_signal_connect (widget, signal_name, func, data); + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); } static void -close_cb (GtkWidget *widget, gpointer data) +attachment_save_query_info_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) { EAttachment *attachment; - DialogData *dialog_data; + GCancellable *cancellable; + GFileInfo *file_info; + GFileType file_type; + GError *error = NULL; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + + file_info = g_file_query_info_finish (destination, result, &error); + + /* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_error_free (error); + goto replace; + } - dialog_data = (DialogData *) data; - attachment = dialog_data->attachment; + if (attachment_save_check_for_error (save_context, error)) + return; + + file_type = g_file_info_get_file_type (file_info); + g_object_unref (file_info); + + if (file_type == G_FILE_TYPE_DIRECTORY) { + save_context->directory = g_object_ref (destination); + destination = attachment_save_new_candidate (save_context); + + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); - gtk_widget_destroy (dialog_data->dialog); - g_object_unref (attachment->editor_gui); - attachment->editor_gui = NULL; + g_object_unref (destination); - destroy_dialog_data (dialog_data); + return; + } + +replace: + g_file_replace_async ( + destination, NULL, FALSE, +#if GLIB_CHECK_VERSION(2,20,0) + G_FILE_CREATE_REPLACE_DESTINATION, +#else + G_FILE_CREATE_NONE, +#endif + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_replace_cb, + save_context); } -static void -ok_cb (GtkWidget *widget, gpointer data) +void +e_attachment_save_async (EAttachment *attachment, + GFile *destination, + GAsyncReadyCallback callback, + gpointer user_data) { - DialogData *dialog_data; - EAttachment *attachment; - const char *str; - - dialog_data = (DialogData *) data; - attachment = dialog_data->attachment; - - str = gtk_entry_get_text (dialog_data->file_name_entry); - if (attachment->is_available_local) - camel_mime_part_set_filename (attachment->body, str); - g_free (attachment->file_name); - attachment->file_name = g_strdup (str); - - str = gtk_entry_get_text (dialog_data->description_entry); - if (attachment->is_available_local) { - camel_mime_part_set_description (attachment->body, str); - } else { - g_free (attachment->description); - attachment->description = g_strdup (str); + SaveContext *save_context; + GCancellable *cancellable; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_FILE (destination)); + g_return_if_fail (callback != NULL); + + if (e_attachment_get_loading (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A load operation is already in progress")); + return; } - str = gtk_entry_get_text (dialog_data->mime_type_entry); - if (attachment->is_available_local) { - camel_mime_part_set_content_type (attachment->body, str); - camel_data_wrapper_set_mime_type(camel_medium_get_content_object(CAMEL_MEDIUM (attachment->body)), str); + if (e_attachment_get_saving (attachment)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_BUSY, + _("A save operation is already in progress")); + return; } - if (attachment->is_available_local) { - switch (gtk_toggle_button_get_active (dialog_data->disposition_checkbox)) { - case 0: - camel_mime_part_set_disposition (attachment->body, "attachment"); - break; - case 1: - camel_mime_part_set_disposition (attachment->body, "inline"); - break; - default: - /* Hmmmm? */ - break; - } - } else { - attachment->disposition = gtk_toggle_button_get_active (dialog_data->disposition_checkbox); + if (e_attachment_get_mime_part (attachment) == NULL) { + g_simple_async_report_error_in_idle ( + G_OBJECT (attachment), callback, user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Attachment contents not loaded")); + return; } - changed (attachment); - close_cb (widget, data); + save_context = attachment_save_context_new ( + attachment, callback, user_data); + + cancellable = attachment->priv->cancellable; + g_cancellable_reset (cancellable); + + /* First we need to know if destination is a directory. */ + g_file_query_info_async ( + destination, G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, + cancellable, (GAsyncReadyCallback) + attachment_save_query_info_cb, save_context); } -static void -response_cb (GtkWidget *widget, gint response, gpointer data) +GFile * +e_attachment_save_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) { - if (response == GTK_RESPONSE_OK) - ok_cb (widget, data); - else - close_cb (widget, data); + GSimpleAsyncResult *simple; + GFile *destination; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + destination = g_simple_async_result_get_op_res_gpointer (simple); + if (destination != NULL) + g_object_ref (destination); + g_simple_async_result_propagate_error (simple, error); + g_object_unref (simple); + + attachment_set_saving (attachment, FALSE); + + return destination; } void -e_attachment_edit (EAttachment *attachment, GtkWidget *parent) +e_attachment_save_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) { - CamelContentType *content_type; - const char *disposition; - DialogData *dialog_data; - GladeXML *editor_gui; - GtkWidget *window; - char *type; - char *filename; + GFile *file; + GFileInfo *file_info; + GtkWidget *dialog; + const gchar *display_name; + const gchar *primary_text; + GError *error = NULL; g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (GTK_IS_WINDOW (parent)); + + file = e_attachment_save_finish (attachment, result, &error); - if (attachment->editor_gui != NULL) { - window = glade_xml_get_widget (attachment->editor_gui, "dialog"); - gdk_window_show (window->window); + if (file != NULL) { + g_object_unref (file); return; } - filename = g_build_filename (EVOLUTION_GLADEDIR, "e-attachment.glade", NULL); - editor_gui = glade_xml_new (filename, NULL, NULL); - g_free (filename); - - if (editor_gui == NULL) { - g_warning ("Cannot load `e-attachment.glade'"); + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; - } - attachment->editor_gui = editor_gui; - - gtk_window_set_transient_for (GTK_WINDOW (glade_xml_get_widget (editor_gui, "dialog")), - GTK_WINDOW (gtk_widget_get_toplevel (parent))); - - dialog_data = g_new (DialogData, 1); - dialog_data->attachment = attachment; - dialog_data->dialog = glade_xml_get_widget (editor_gui, "dialog"); - dialog_data->file_name_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "file_name_entry")); - dialog_data->description_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "description_entry")); - dialog_data->mime_type_entry = GTK_ENTRY (glade_xml_get_widget (editor_gui, "mime_type_entry")); - dialog_data->disposition_checkbox = GTK_TOGGLE_BUTTON (glade_xml_get_widget (editor_gui, "disposition_checkbox")); - - if (attachment->is_available_local && attachment->body) { - set_entry (editor_gui, "file_name_entry", camel_mime_part_get_filename (attachment->body)); - set_entry (editor_gui, "description_entry", camel_mime_part_get_description (attachment->body)); - content_type = camel_mime_part_get_content_type (attachment->body); - type = camel_content_type_simple (content_type); - set_entry (editor_gui, "mime_type_entry", type); - g_free (type); - - disposition = camel_mime_part_get_disposition (attachment->body); - gtk_toggle_button_set_active (dialog_data->disposition_checkbox, - disposition && !g_ascii_strcasecmp (disposition, "inline")); - } else { - set_entry (editor_gui, "file_name_entry", attachment->file_name); - set_entry (editor_gui, "description_entry", attachment->description); - if ((type = attachment_guess_mime_type (attachment->file_name))) { - set_entry (editor_gui, "mime_type_entry", type); - g_free (type); - } else { - set_entry (editor_gui, "mime_type_entry", ""); - } + file_info = e_attachment_get_file_info (attachment); - gtk_toggle_button_set_active (dialog_data->disposition_checkbox, attachment->disposition); - } + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; + + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not save '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not save the attachment")); + + dialog = gtk_message_dialog_new_with_markup ( + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", primary_text); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - connect_widget (editor_gui, "dialog", "response", (GCallback)response_cb, dialog_data); + gtk_dialog_run (GTK_DIALOG (dialog)); - /* make sure that when the parent gets hidden/closed that our windows also close */ - parent = gtk_widget_get_toplevel (parent); - gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog_data->dialog), TRUE); + gtk_widget_destroy (dialog); + g_error_free (error); } diff --git a/widgets/misc/e-attachment.h b/widgets/misc/e-attachment.h index 7b45f24ae5..934e1e04b6 100644 --- a/widgets/misc/e-attachment.h +++ b/widgets/misc/e-attachment.h @@ -1,4 +1,6 @@ /* + * e-attachment.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 @@ -13,91 +15,131 @@ * License along with the program; if not, see * * - * Authors: - * Ettore Perazzoli - * Srinivasa Ragavan - * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ -#ifndef __E_ATTACHMENT_H__ -#define __E_ATTACHMENT_H__ +#ifndef E_ATTACHMENT_H +#define E_ATTACHMENT_H -#include #include -#include -#include -#include #include - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif /* __cplusplus */ - -#define E_TYPE_ATTACHMENT (e_attachment_get_type ()) -#define E_ATTACHMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_ATTACHMENT, EAttachment)) -#define E_ATTACHMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_ATTACHMENT, EAttachmentClass)) -#define E_IS_ATTACHMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_ATTACHMENT)) -#define E_IS_ATTACHMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_ATTACHMENT)) - - -typedef struct _EAttachment EAttachment; -typedef struct _EAttachmentClass EAttachmentClass; +#include +#include +#include + +/* Standard GObject macros */ +#define E_TYPE_ATTACHMENT \ + (e_attachment_get_type ()) +#define E_ATTACHMENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_ATTACHMENT, EAttachment)) +#define E_ATTACHMENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_ATTACHMENT, EAttachmentClass)) +#define E_IS_ATTACHMENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_ATTACHMENT)) +#define E_IS_ATTACHMENT_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_ATTACHMENT)) +#define E_ATTACHMENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_ATTACHMENT, EAttachmentClass)) + +G_BEGIN_DECLS + +typedef struct _EAttachment EAttachment; +typedef struct _EAttachmentClass EAttachmentClass; +typedef struct _EAttachmentPrivate EAttachmentPrivate; struct _EAttachment { GObject parent; - - GladeXML *editor_gui; - - CamelMimePart *body; - gboolean guessed_type; - gulong size; - - GdkPixbuf *pixbuf_cache; - - GCancellable *cancellable; - - gboolean is_available_local; - int percentage; - char *file_name; - char *description; - gboolean disposition; - int index; - char *store_uri; - - /* Status of signed/encrypted attachments */ - camel_cipher_validity_sign_t sign; - camel_cipher_validity_encrypt_t encrypt; + EAttachmentPrivate *priv; }; struct _EAttachmentClass { GObjectClass parent_class; - - void (*changed) (EAttachment *attachment); - void (*update) (EAttachment *attachment, char *msg); }; -GType e_attachment_get_type (void); -EAttachment *e_attachment_new (const char *file_name, - const char *disposition, - CamelException *ex); -EAttachment * e_attachment_new_remote_file (GtkWindow *error_dlg_parent, - const char *url, - const char *disposition, - const char *path, - CamelException *ex); -void e_attachment_build_remote_file (const char *filename, - EAttachment *attachment, - const char *disposition, - CamelException *ex); -EAttachment *e_attachment_new_from_mime_part (CamelMimePart *part); -void e_attachment_edit (EAttachment *attachment, - GtkWidget *parent); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* __E_ATTACHMENT_H__ */ +GType e_attachment_get_type (void); +EAttachment * e_attachment_new (void); +EAttachment * e_attachment_new_for_path (const gchar *path); +EAttachment * e_attachment_new_for_uri (const gchar *uri); +EAttachment * e_attachment_new_for_message (CamelMimeMessage *message); +void e_attachment_add_to_multipart (EAttachment *attachment, + CamelMultipart *multipart, + const gchar *default_charset); +void e_attachment_cancel (EAttachment *attachment); +gboolean e_attachment_get_can_show (EAttachment *attachment); +void e_attachment_set_can_show (EAttachment *attachment, + gboolean can_show); +const gchar * e_attachment_get_disposition (EAttachment *attachment); +void e_attachment_set_disposition (EAttachment *attachment, + const gchar *disposition); +GFile * e_attachment_get_file (EAttachment *attachment); +void e_attachment_set_file (EAttachment *attachment, + GFile *file); +GFileInfo * e_attachment_get_file_info (EAttachment *attachment); +gboolean e_attachment_get_loading (EAttachment *attachment); +CamelMimePart * e_attachment_get_mime_part (EAttachment *attachment); +void e_attachment_set_mime_part (EAttachment *attachment, + CamelMimePart *mime_part); +gint e_attachment_get_percent (EAttachment *attachment); +GtkTreeRowReference * + e_attachment_get_reference (EAttachment *attachment); +void e_attachment_set_reference (EAttachment *attachment, + GtkTreeRowReference *reference); +gboolean e_attachment_get_saving (EAttachment *attachment); +gboolean e_attachment_get_shown (EAttachment *attachment); +void e_attachment_set_shown (EAttachment *attachment, + gboolean shown); +camel_cipher_validity_encrypt_t + e_attachment_get_encrypted (EAttachment *attachment); +void e_attachment_set_encrypted (EAttachment *attachment, + camel_cipher_validity_encrypt_t encrypted); +camel_cipher_validity_sign_t + e_attachment_get_signed (EAttachment *attachment); +void e_attachment_set_signed (EAttachment *attachment, + camel_cipher_validity_sign_t signed_); +const gchar * e_attachment_get_description (EAttachment *attachment); +const gchar * e_attachment_get_thumbnail_path (EAttachment *attachment); +gboolean e_attachment_is_rfc822 (EAttachment *attachment); +GList * e_attachment_list_apps (EAttachment *attachment); + +/* Asynchronous Operations */ +void e_attachment_load_async (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error); +void e_attachment_open_async (EAttachment *attachment, + GAppInfo *app_info, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_attachment_open_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error); +void e_attachment_save_async (EAttachment *attachment, + GFile *destination, + GAsyncReadyCallback callback, + gpointer user_data); +GFile * e_attachment_save_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error); + +/* Handy GAsyncReadyCallback Functions */ +void e_attachment_load_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent); +void e_attachment_open_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent); +void e_attachment_save_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent); + +G_END_DECLS + +#endif /* E_ATTACHMENT_H */ diff --git a/widgets/misc/e-expander.c b/widgets/misc/e-expander.c deleted file mode 100644 index 771598739d..0000000000 --- a/widgets/misc/e-expander.c +++ /dev/null @@ -1,1341 +0,0 @@ -/* - * GTK - The GIMP Toolkit - * - * 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 - * - * - * Authors: - * Mark McLoughlin - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * Copyright (C) 2003 Sun Microsystems, Inc. - * - */ - -#include - -#include "e-expander.h" -#include "ea-widgets.h" - -#include - -#define DEFAULT_EXPANDER_SIZE 10 -#define DEFAULT_EXPANDER_SPACING 2 - -/* ESTUFF */ -#ifndef _ -#define _(x) (x) -#endif -#define E_EXPANDER_GET_PRIVATE(expander) ((EExpanderPrivate *)g_object_get_data (G_OBJECT (expander), "e-expander-priv")) - -enum { - PROP_0, - PROP_EXPANDED, - PROP_LABEL, - PROP_USE_UNDERLINE, - PROP_PADDING, - PROP_LABEL_WIDGET -}; - -typedef struct { - GtkWidget *label_widget; - gint spacing; - - GtkExpanderStyle expander_style; - guint animation_timeout; - - guint expanded : 1; - guint use_underline : 1; - guint button_down : 1; -} EExpanderPrivate; - -static void e_expander_class_init (EExpanderClass *klass); -static void e_expander_init (EExpander *expander); - -static void e_expander_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void e_expander_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); - -static void e_expander_destroy (GtkObject *object); - -static void e_expander_realize (GtkWidget *widget); -static void e_expander_size_request (GtkWidget *widget, - GtkRequisition *requisition); -static void e_expander_size_allocate (GtkWidget *widget, - GtkAllocation *allocation); -static void e_expander_map (GtkWidget *widget); -static gboolean e_expander_expose (GtkWidget *widget, - GdkEventExpose *event); -static gboolean e_expander_button_press (GtkWidget *widget, - GdkEventButton *event); -static gboolean e_expander_button_release (GtkWidget *widget, - GdkEventButton *event); -static gboolean e_expander_motion_notify (GtkWidget *widget, - GdkEventMotion *event); -static gboolean e_expander_enter_notify (GtkWidget *widget, - GdkEventCrossing *event); -static gboolean e_expander_leave_notify (GtkWidget *widget, - GdkEventCrossing *event); -static gboolean e_expander_focus (GtkWidget *widget, - GtkDirectionType direction); - -static void e_expander_add (GtkContainer *container, - GtkWidget *widget); -static void e_expander_remove (GtkContainer *container, - GtkWidget *widget); -static void e_expander_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data); - -static void e_expander_activate (EExpander *expander); - -static GtkBinClass *parent_class = NULL; - -GType -e_expander_get_type (void) -{ - static GType expander_type = 0; - - if (!expander_type) - { - static const GTypeInfo expander_info = - { - sizeof (EExpanderClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) e_expander_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (EExpander), - 0, /* n_preallocs */ - (GInstanceInitFunc) e_expander_init, - }; - - expander_type = g_type_register_static (GTK_TYPE_BIN, - "EExpander", - &expander_info, 0); - } - - return expander_type; -} - -static void -e_expander_class_init (EExpanderClass *klass) -{ - GObjectClass *gobject_class; - GtkObjectClass *gtkobject_class; - GtkWidgetClass *widget_class; - GtkContainerClass *container_class; - - parent_class = g_type_class_peek_parent (klass); - - gobject_class = (GObjectClass *) klass; - gtkobject_class = (GtkObjectClass *) klass; - widget_class = (GtkWidgetClass *) klass; - container_class = (GtkContainerClass *) klass; - - gobject_class->set_property = e_expander_set_property; - gobject_class->get_property = e_expander_get_property; - - gtkobject_class->destroy = e_expander_destroy; - - widget_class->realize = e_expander_realize; - widget_class->size_request = e_expander_size_request; - widget_class->size_allocate = e_expander_size_allocate; - widget_class->map = e_expander_map; - widget_class->expose_event = e_expander_expose; - widget_class->button_press_event = e_expander_button_press; - widget_class->button_release_event = e_expander_button_release; - widget_class->motion_notify_event = e_expander_motion_notify; - widget_class->enter_notify_event = e_expander_enter_notify; - widget_class->leave_notify_event = e_expander_leave_notify; - widget_class->focus = e_expander_focus; - - container_class->add = e_expander_add; - container_class->remove = e_expander_remove; - container_class->forall = e_expander_forall; - - klass->activate = e_expander_activate; - - /* ESTUFF g_type_class_add_private (klass, sizeof (EExpanderPrivate)); */ - - g_object_class_install_property (gobject_class, - PROP_EXPANDED, - g_param_spec_boolean ("expanded", - _("Expanded"), - _("Whether or not the expander is expanded"), - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (gobject_class, - PROP_LABEL, - g_param_spec_string ("label", - _("Label"), - _("Text of the expander's label"), - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (gobject_class, - PROP_USE_UNDERLINE, - g_param_spec_boolean ("use_underline", - _("Use underline"), - _("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property (gobject_class, - PROP_PADDING, - g_param_spec_int ("spacing", - _("Spacing"), - _("Space to put between the label and the child"), - 0, - G_MAXINT, - 0, - G_PARAM_READWRITE)); - - g_object_class_install_property (gobject_class, - PROP_LABEL_WIDGET, - g_param_spec_object ("label_widget", - _("Label widget"), - _("A widget to display in place of the usual expander label"), - GTK_TYPE_WIDGET, - G_PARAM_READWRITE)); - - gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("expander-size", - _("Expander Size"), - _("Size of the expander arrow"), - 0, - G_MAXINT, - DEFAULT_EXPANDER_SIZE, - G_PARAM_READABLE)); - - gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("expander-spacing", - _("Indicator Spacing"), - _("Spacing around expander arrow"), - 0, - G_MAXINT, - DEFAULT_EXPANDER_SPACING, - G_PARAM_READABLE)); - - widget_class->activate_signal = - g_signal_new ("activate", - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (EExpanderClass, activate), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - e_expander_a11y_init(); -} - -static void -e_expander_init (EExpander *expander) -{ - EExpanderPrivate *priv; - - /* ESTUFF */ - priv = g_new0 (EExpanderPrivate, 1); - g_object_set_data_full (G_OBJECT (expander), "e-expander-priv", priv, g_free); - - /* ESTUFF priv = E_EXPANDER_GET_PRIVATE (expander); */ - - GTK_WIDGET_SET_FLAGS (expander, GTK_CAN_FOCUS); - GTK_WIDGET_UNSET_FLAGS (expander, GTK_NO_WINDOW); - - priv->label_widget = NULL; - priv->spacing = 0; - - priv->expander_style = GTK_EXPANDER_COLLAPSED; - priv->animation_timeout = 0; - - priv->expanded = FALSE; - priv->use_underline = FALSE; - priv->button_down = FALSE; - -} - -static void -e_expander_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - EExpander *expander = E_EXPANDER (object); - - switch (prop_id) - { - case PROP_EXPANDED: - e_expander_set_expanded (expander, g_value_get_boolean (value)); - break; - case PROP_LABEL: - e_expander_set_label (expander, g_value_get_string (value)); - break; - case PROP_USE_UNDERLINE: - e_expander_set_use_underline (expander, g_value_get_boolean (value)); - break; - case PROP_PADDING: - e_expander_set_spacing (expander, g_value_get_int (value)); - break; - case PROP_LABEL_WIDGET: - e_expander_set_label_widget (expander, g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -e_expander_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - EExpander *expander = E_EXPANDER (object); - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - switch (prop_id) - { - case PROP_EXPANDED: - g_value_set_boolean (value, priv->expanded); - break; - case PROP_LABEL: - g_value_set_string (value, e_expander_get_label (expander)); - break; - case PROP_USE_UNDERLINE: - g_value_set_boolean (value, priv->use_underline); - break; - case PROP_PADDING: - g_value_set_int (value, priv->spacing); - break; - case PROP_LABEL_WIDGET: - g_value_set_object (value, - priv->label_widget ? - G_OBJECT (priv->label_widget) : NULL); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -e_expander_destroy (GtkObject *object) -{ - EExpander *expander = E_EXPANDER (object); - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (priv->animation_timeout) - g_source_remove (priv->animation_timeout); - priv->animation_timeout = 0; - - GTK_OBJECT_CLASS (parent_class)->destroy (object); -} - -static void -e_expander_realize (GtkWidget *widget) -{ - GdkWindowAttr attributes; - gint attributes_mask; - gint border_width; - - GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); - - border_width = GTK_CONTAINER (widget)->border_width; - - attributes.x = widget->allocation.x + border_width; - attributes.y = widget->allocation.y + border_width; - attributes.width = widget->allocation.width - 2 * border_width; - attributes.height = widget->allocation.height - 2 * border_width; - attributes.window_type = GDK_WINDOW_CHILD; - attributes.wclass = GDK_INPUT_OUTPUT; - attributes.visual = gtk_widget_get_visual (widget); - attributes.colormap = gtk_widget_get_colormap (widget); - attributes.event_mask = gtk_widget_get_events (widget) | - GDK_POINTER_MOTION_MASK | - GDK_POINTER_MOTION_HINT_MASK | - GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_EXPOSURE_MASK | - GDK_ENTER_NOTIFY_MASK | - GDK_LEAVE_NOTIFY_MASK; - - attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; - - widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), - &attributes, attributes_mask); - gdk_window_set_user_data (widget->window, widget); - - widget->style = gtk_style_attach (widget->style, widget->window); - gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); -} - -static void -e_expander_size_request (GtkWidget *widget, - GtkRequisition *requisition) -{ - EExpander *expander; - GtkBin *bin; - EExpanderPrivate *priv; - gint border_width; - gint expander_size; - gint expander_spacing; - gboolean interior_focus; - gint focus_width; - gint focus_pad; - - expander = E_EXPANDER (widget); - bin = GTK_BIN (widget); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - border_width = GTK_CONTAINER (widget)->border_width; - - gtk_widget_style_get (widget, - "interior-focus", &interior_focus, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - "expander-size", &expander_size, - "expander-spacing", &expander_spacing, - NULL); - - requisition->width = expander_size + 2 * expander_spacing + - 2 * focus_width + 2 * focus_pad; - requisition->height = interior_focus ? (2 * focus_width + 2 * focus_pad) : 0; - - if (priv->label_widget && GTK_WIDGET_VISIBLE (priv->label_widget)) - { - GtkRequisition label_requisition; - - gtk_widget_size_request (priv->label_widget, &label_requisition); - - requisition->width += label_requisition.width; - requisition->height += label_requisition.height; - } - - requisition->height = MAX (expander_size + 2 * expander_spacing, requisition->height); - - if (!interior_focus) - requisition->height += 2 * focus_width + 2 * focus_pad; - - if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) - { - GtkRequisition child_requisition; - - gtk_widget_size_request (bin->child, &child_requisition); - - if (!interior_focus) - child_requisition.width += 2 * focus_width + 2 * focus_pad; - - requisition->width = MAX (requisition->width, child_requisition.width); - requisition->height += child_requisition.height + priv->spacing; - } - - requisition->width += 2 * border_width; - requisition->height += 2 * border_width + 2 * priv->spacing; -} - -static void -e_expander_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) -{ - EExpander *expander; - GtkBin *bin; - EExpanderPrivate *priv; - GtkRequisition child_requisition; - gboolean child_visible = FALSE; - gint border_width; - gint expander_size; - gint expander_spacing; - gboolean interior_focus; - gint focus_width; - gint focus_pad; - gint label_height; - - expander = E_EXPANDER (widget); - bin = GTK_BIN (widget); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - border_width = GTK_CONTAINER (widget)->border_width; - - gtk_widget_style_get (widget, - "interior-focus", &interior_focus, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - "expander-size", &expander_size, - "expander-spacing", &expander_spacing, - NULL); - - child_requisition.width = 0; - child_requisition.height = 0; - if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) - { - child_visible = TRUE; - gtk_widget_get_child_requisition (bin->child, &child_requisition); - } - - widget->allocation = *allocation; - - if (GTK_WIDGET_REALIZED (widget)) - gdk_window_move_resize (widget->window, - allocation->x + border_width, - allocation->y + border_width, - MAX (allocation->width - 2 * border_width, 0), - MAX (allocation->height - 2 * border_width, 0)); - - if (priv->label_widget && GTK_WIDGET_VISIBLE (priv->label_widget)) - { - GtkAllocation label_allocation; - GtkRequisition label_requisition; - gboolean ltr; - - gtk_widget_get_child_requisition (priv->label_widget, &label_requisition); - - ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL; - - label_allocation.x = focus_width + focus_pad; - if (ltr) - label_allocation.x += expander_size + 2 * expander_spacing; - label_allocation.y = priv->spacing + focus_width + focus_pad; - - label_allocation.width = MIN (label_requisition.width, - allocation->width - 2 * border_width - - expander_size - 2 * expander_spacing - - 2 * focus_width - 2 * focus_pad); - label_allocation.width = MAX (label_allocation.width, 1); - - label_allocation.height = MIN (label_requisition.height, - allocation->height - 2 * border_width - - 2 * priv->spacing - - 2 * focus_width - 2 * focus_pad - - child_requisition.height - - (child_visible ? priv->spacing : 0)); - label_allocation.height = MAX (label_allocation.height, 1); - - gtk_widget_size_allocate (priv->label_widget, &label_allocation); - - label_height = label_allocation.height; - } - else - { - label_height = 0; - } - - if (child_visible) - { - GtkAllocation child_allocation; - gint top_height; - - top_height = MAX (2 * expander_spacing + expander_size, - label_height + - (interior_focus ? 2 * focus_width + 2 * focus_pad : 0)); - - child_allocation.x = 0; - child_allocation.y = 2 * priv->spacing + top_height; - - if (!interior_focus) - { - child_allocation.x += focus_width + focus_pad; - child_allocation.y += focus_width + focus_pad; - } - - child_allocation.width = allocation->width - 2 * border_width - - (!interior_focus ? 2 * focus_width + 2 * focus_pad : 0); - child_allocation.width = MAX (child_allocation.width, 1); - - child_allocation.height = allocation->height - top_height - - 2 * border_width - - 3 * priv->spacing - - (!interior_focus ? 2 * focus_width + 2 * focus_pad : 0); - child_allocation.height = MAX (child_allocation.height, 1); - - gtk_widget_size_allocate (bin->child, &child_allocation); - } -} - -static void -e_expander_map (GtkWidget *widget) -{ - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (widget); - - if (priv->label_widget) - gtk_widget_map (priv->label_widget); - - GTK_WIDGET_CLASS (parent_class)->map (widget); -} - -static GdkRectangle -get_expander_bounds (EExpander *expander) -{ - GtkWidget *widget; - EExpanderPrivate *priv; - GdkRectangle bounds; - gint border_width; - gint expander_size; - gint expander_spacing; - gboolean interior_focus; - gint focus_width; - gint focus_pad; - gboolean ltr; - - widget = GTK_WIDGET (expander); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - border_width = GTK_CONTAINER (expander)->border_width; - - gtk_widget_style_get (widget, - "interior-focus", &interior_focus, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - "expander-size", &expander_size, - "expander-spacing", &expander_spacing, - NULL); - - ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL; - - - if (ltr) - bounds.x = expander_spacing; - else - bounds.x = widget->allocation.width - 2 * border_width - - expander_spacing - expander_size; - - if (priv->label_widget && GTK_WIDGET_VISIBLE (priv->label_widget)) - { - GtkAllocation label_allocation; - - label_allocation = priv->label_widget->allocation; - - if (expander_size < label_allocation.height) - bounds.y = label_allocation.y + (label_allocation.height - expander_size) / 2; - else - bounds.y = priv->spacing + expander_spacing; - } - else - { - bounds.y = priv->spacing + expander_spacing; - } - - if (!interior_focus) - { - if (ltr) - bounds.x += focus_width + focus_pad; - else - bounds.x -= focus_width + focus_pad; - bounds.y += focus_width + focus_pad; - } - - bounds.width = bounds.height = expander_size; - - return bounds; -} - -static void -e_expander_paint (EExpander *expander) -{ - GtkWidget *widget; - EExpanderPrivate *priv; - gint x, y; - GtkStateType state; - GdkRectangle clip; - - widget = GTK_WIDGET (expander); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - clip = get_expander_bounds (expander); - - x = clip.x + clip.width / 2; - y = clip.y + clip.height / 2; - - state = widget->state; - if (state != GTK_STATE_PRELIGHT) - state = GTK_STATE_NORMAL; - - gtk_paint_expander (widget->style, - widget->window, - state, - &clip, - widget, - "expander", - x, - y, - priv->expander_style); -} - -static void -e_expander_paint_focus (EExpander *expander, - GdkRectangle *area) -{ - GtkWidget *widget; - EExpanderPrivate *priv; - gint x, y, width, height; - gboolean interior_focus; - gint focus_width; - gint focus_pad; - gint expander_size; - gint expander_spacing; - gboolean ltr; - - widget = GTK_WIDGET (expander); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - gtk_widget_style_get (widget, - "interior-focus", &interior_focus, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - "expander-size", &expander_size, - "expander-spacing", &expander_spacing, - NULL); - - ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL; - - if (interior_focus) - { - if (ltr) - x = expander_spacing * 2 + expander_size; - else - x = 0; - y = priv->spacing; - - width = height = 2 * focus_pad + 2 * focus_width; - - if (priv->label_widget && GTK_WIDGET_VISIBLE (priv->label_widget)) - { - GtkAllocation label_allocation = priv->label_widget->allocation; - - width += label_allocation.width; - height += label_allocation.height; - } - } - else - { - x = y = 0; - width = widget->allocation.width - 2 * GTK_CONTAINER (widget)->border_width; - height = widget->allocation.height - 2 * GTK_CONTAINER (widget)->border_width; - } - - gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget), - area, widget, "expander", - x, y, width, height); -} - -static gboolean -e_expander_expose (GtkWidget *widget, - GdkEventExpose *event) -{ - if (GTK_WIDGET_DRAWABLE (widget)) - { - EExpander *expander = E_EXPANDER (widget); - - e_expander_paint (expander); - - if (GTK_WIDGET_HAS_FOCUS (expander)) - e_expander_paint_focus (expander, &event->area); - - GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); - } - - return FALSE; -} - -static gboolean -is_in_expander_panel (EExpander *expander, - GdkWindow *window, - gint x, - gint y) -{ - GtkWidget *widget; - GdkRectangle area; - - widget = GTK_WIDGET (expander); - - area = get_expander_bounds (expander); - - area.x = 0; - area.width = widget->allocation.width; - - if (widget->window == window) - { - if (x >= area.x && x <= (area.x + area.width) && - y >= area.y && y <= (area.y + area.height)) - return TRUE; - } - - return FALSE; -} - -static gboolean -e_expander_button_press (GtkWidget *widget, - GdkEventButton *event) -{ - EExpander *expander = E_EXPANDER (widget); - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (event->button == 1 && !priv->button_down) - { - if (is_in_expander_panel (expander, event->window, event->x, event->y)) - { - priv->button_down = TRUE; - return TRUE; - } - } - - return FALSE; -} - -static gboolean -e_expander_button_release (GtkWidget *widget, - GdkEventButton *event) -{ - EExpander *expander = E_EXPANDER (widget); - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (event->button == 1 && priv->button_down) - { - g_signal_emit_by_name (expander, "activate"); - - priv->button_down = FALSE; - return TRUE; - } - - return FALSE; -} - -static void -e_expander_maybe_prelight (EExpander *expander) -{ - GtkWidget *widget; - EExpanderPrivate *priv; - GtkStateType state = GTK_STATE_NORMAL; - - widget = GTK_WIDGET (expander); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (!priv->button_down) - { - gint x, y; - - gdk_window_get_pointer (widget->window, &x, &y, NULL); - - if (is_in_expander_panel (expander, widget->window, x, y)) - state = GTK_STATE_PRELIGHT; - } - - gtk_widget_set_state (widget, state); -} - -static gboolean -e_expander_motion_notify (GtkWidget *widget, - GdkEventMotion *event) -{ - e_expander_maybe_prelight (E_EXPANDER (widget)); - - return FALSE; -} - -static gboolean -e_expander_enter_notify (GtkWidget *widget, - GdkEventCrossing *event) -{ - e_expander_maybe_prelight (E_EXPANDER (widget)); - - return FALSE; -} - -static gboolean -e_expander_leave_notify (GtkWidget *widget, - GdkEventCrossing *event) -{ - gtk_widget_set_state (widget, GTK_STATE_NORMAL); - - return FALSE; -} - -static gboolean -focus_child_in (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); - - if (!child) - return FALSE; - - return gtk_widget_child_focus (child, direction); -} - -static gboolean -e_expander_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - EExpanderPrivate *priv; - GtkWidget *old_focus_child; - gboolean widget_is_focus; - gboolean label_can_focus; - - priv = E_EXPANDER_GET_PRIVATE (widget); - - widget_is_focus = gtk_widget_is_focus (widget); - old_focus_child = GTK_CONTAINER (widget)->focus_child; - label_can_focus = priv->label_widget && GTK_WIDGET_CAN_FOCUS (priv->label_widget); - - if (old_focus_child && old_focus_child == priv->label_widget) - { - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_LEFT: - case GTK_DIR_UP: - gtk_widget_grab_focus (widget); - return TRUE; - case GTK_DIR_DOWN: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_RIGHT: - return focus_child_in (widget, direction); - } - } - else if (old_focus_child) - { - if (gtk_widget_child_focus (old_focus_child, direction)) - return TRUE; - - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_LEFT: - case GTK_DIR_UP: - if (label_can_focus) - gtk_widget_grab_focus (priv->label_widget); - else - gtk_widget_grab_focus (widget); - return TRUE; - case GTK_DIR_DOWN: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_RIGHT: - return FALSE; - } - } - else if (widget_is_focus) - { - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_LEFT: - case GTK_DIR_UP: - return FALSE; - case GTK_DIR_DOWN: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_RIGHT: - if (label_can_focus) - { - gtk_widget_grab_focus (priv->label_widget); - return TRUE; - } - - return focus_child_in (widget, direction); - } - } - else - { - switch (direction) - { - case GTK_DIR_DOWN: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_TAB_BACKWARD: - gtk_widget_grab_focus (widget); - return TRUE; - case GTK_DIR_UP: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - if (!focus_child_in (widget, direction)) - { - gtk_widget_grab_focus (widget); - } - return TRUE; - } - } - - g_return_val_if_reached(FALSE); -} - -static void -e_expander_add (GtkContainer *container, - GtkWidget *widget) -{ - GTK_CONTAINER_CLASS (parent_class)->add (container, widget); - - g_object_set (G_OBJECT (widget), - "visible", E_EXPANDER_GET_PRIVATE (container)->expanded, - NULL); -} - -static void -e_expander_remove (GtkContainer *container, - GtkWidget *widget) -{ - EExpander *expander = E_EXPANDER (container); - - if (E_EXPANDER_GET_PRIVATE (expander)->label_widget == widget) - e_expander_set_label_widget (expander, NULL); - else - GTK_CONTAINER_CLASS (parent_class)->remove (container, widget); -} - -static void -e_expander_forall (GtkContainer *container, - gboolean include_internals, - GtkCallback callback, - gpointer callback_data) -{ - GtkBin *bin = GTK_BIN (container); - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (container); - - if (bin->child) - (* callback) (bin->child, callback_data); - - if (priv->label_widget) - (* callback) (priv->label_widget, callback_data); -} - -static void -e_expander_activate (EExpander *expander) -{ - e_expander_set_expanded (expander, - !E_EXPANDER_GET_PRIVATE (expander)->expanded); -} - -GtkWidget * -e_expander_new (const gchar *label) -{ - return g_object_new (E_TYPE_EXPANDER, "label", label, NULL); -} - -GtkWidget * -e_expander_new_with_mnemonic (const gchar *label) -{ - return g_object_new (E_TYPE_EXPANDER, - "label", label, - "use_underline", TRUE, - NULL); -} - -static gboolean -e_expander_animation_timeout (EExpander *expander) -{ - EExpanderPrivate *priv; - GdkRectangle area; - gboolean finish = FALSE; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (GTK_WIDGET_REALIZED (expander)) - { - area = get_expander_bounds (expander); - gdk_window_invalidate_rect (GTK_WIDGET (expander)->window, &area, TRUE); - } - - if (priv->expanded) - { - if (priv->expander_style == GTK_EXPANDER_COLLAPSED) - { - priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED; - } - else - { - priv->expander_style = GTK_EXPANDER_EXPANDED; - finish = TRUE; - } - } - else - { - if (priv->expander_style == GTK_EXPANDER_EXPANDED) - { - priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED; - } - else - { - priv->expander_style = GTK_EXPANDER_COLLAPSED; - finish = TRUE; - } - } - - if (finish) - { - priv->animation_timeout = 0; - g_object_set (G_OBJECT (GTK_BIN (expander)->child), - "visible", priv->expanded, - NULL); - } - - return !finish; -} - -static void -e_expander_start_animation (EExpander *expander) -{ - EExpanderPrivate *priv; - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (priv->animation_timeout) - g_source_remove (priv->animation_timeout); - - priv->animation_timeout = - g_timeout_add (50, - (GSourceFunc) e_expander_animation_timeout, - expander); -} - -void -e_expander_set_expanded (EExpander *expander, - gboolean expanded) -{ - EExpanderPrivate *priv; - - g_return_if_fail (E_IS_EXPANDER (expander)); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - expanded = expanded != FALSE; - - if (priv->expanded != expanded) - { - priv->expanded = expanded; - - if (GTK_WIDGET_VISIBLE (expander)) - e_expander_start_animation (expander); - - else if (GTK_BIN (expander)->child) - { - priv->expander_style = expanded ? GTK_EXPANDER_EXPANDED : - GTK_EXPANDER_COLLAPSED; - g_object_set (G_OBJECT (GTK_BIN (expander)->child), - "visible", priv->expanded, - NULL); - } - - gtk_widget_queue_resize (GTK_WIDGET (expander)); - - g_object_notify (G_OBJECT (expander), "expanded"); - } -} - -gboolean -e_expander_get_expanded (EExpander *expander) -{ - g_return_val_if_fail (E_IS_EXPANDER (expander), FALSE); - - return E_EXPANDER_GET_PRIVATE (expander)->expanded; -} - -void -e_expander_set_spacing (EExpander *expander, - gint spacing) -{ - EExpanderPrivate *priv; - - g_return_if_fail (E_IS_EXPANDER (expander)); - g_return_if_fail (spacing >= 0); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (priv->spacing != spacing) - { - priv->spacing = spacing; - - gtk_widget_queue_resize (GTK_WIDGET (expander)); - - g_object_notify (G_OBJECT (expander), "spacing"); - } -} - -gint -e_expander_get_spacing (EExpander *expander) -{ - g_return_val_if_fail (E_IS_EXPANDER (expander), 0); - - return E_EXPANDER_GET_PRIVATE (expander)->spacing; -} - -void -e_expander_set_label (EExpander *expander, - const gchar *label) -{ - g_return_if_fail (E_IS_EXPANDER (expander)); - - if (!label) - { - e_expander_set_label_widget (expander, NULL); - } - else - { - GtkWidget *child; - - child = gtk_label_new (label); - gtk_label_set_use_underline (GTK_LABEL (child), - E_EXPANDER_GET_PRIVATE (expander)->use_underline); - gtk_widget_show (child); - - e_expander_set_label_widget (expander, child); - } - - g_object_notify (G_OBJECT (expander), "label"); -} - -/** - * e_expander_get_label: - * @expander: a #EExpander - * - * If the expander's label widget is a #GtkLabel, return the - * text in the label widget. (The frame will have a #GtkLabel - * for the label widget if a non-%NULL argument was passed - * to e_expander_new().) - * - * Return value: the text in the label, or %NULL if there - * was no label widget or the lable widget was not - * a #GtkLabel. This string is owned by GTK+ and - * must not be modified or freed. - **/ -G_CONST_RETURN char * -e_expander_get_label (EExpander *expander) -{ - EExpanderPrivate *priv; - - g_return_val_if_fail (E_IS_EXPANDER (expander), NULL); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (priv->label_widget && GTK_IS_LABEL (priv->label_widget)) - return gtk_label_get_text (GTK_LABEL (priv->label_widget)); - else - return NULL; -} - -void -e_expander_set_use_underline (EExpander *expander, - gboolean use_underline) -{ - EExpanderPrivate *priv; - - g_return_if_fail (E_IS_EXPANDER (expander)); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - use_underline = use_underline != FALSE; - - if (priv->use_underline != use_underline) - { - priv->use_underline = use_underline; - - if (priv->label_widget && GTK_IS_LABEL (priv->label_widget)) - gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), use_underline); - - g_object_notify (G_OBJECT (expander), "use_underline"); - } -} - -gboolean -e_expander_get_use_underline (EExpander *expander) -{ - g_return_val_if_fail (E_IS_EXPANDER (expander), FALSE); - - return E_EXPANDER_GET_PRIVATE (expander)->use_underline; -} - -/** - * e_expander_set_label_widget: - * @expander: a #EExpander - * @label_widget: the new label widget - * - * Set the label widget for the expander. This is the widget - * that will appear embedded alongside the expander arrow. - **/ -void -e_expander_set_label_widget (EExpander *expander, - GtkWidget *label_widget) -{ - EExpanderPrivate *priv; - gboolean need_resize = FALSE; - - g_return_if_fail (E_IS_EXPANDER (expander)); - g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget)); - g_return_if_fail (label_widget == NULL || label_widget->parent == NULL); - - priv = E_EXPANDER_GET_PRIVATE (expander); - - if (priv->label_widget == label_widget) - return; - - if (priv->label_widget) - { - need_resize = GTK_WIDGET_VISIBLE (priv->label_widget); - gtk_widget_unparent (priv->label_widget); - } - - priv->label_widget = label_widget; - - if (label_widget) - { - priv->label_widget = label_widget; - gtk_widget_set_parent (label_widget, GTK_WIDGET (expander)); - need_resize |= GTK_WIDGET_VISIBLE (label_widget); - } - - if (GTK_WIDGET_VISIBLE (expander) && need_resize) - gtk_widget_queue_resize (GTK_WIDGET (expander)); - - g_object_notify (G_OBJECT (expander), "label_widget"); -} - -/** - * e_expander_get_label_widget: - * @expander: a #EExpander - * - * Retrieves the label widget for the frame. See - * e_expander_set_label_widget(). - * - * Return value: the label widget, or %NULL if there is none. - **/ -GtkWidget * -e_expander_get_label_widget (EExpander *expander) -{ - g_return_val_if_fail (E_IS_EXPANDER (expander), NULL); - - return E_EXPANDER_GET_PRIVATE (expander)->label_widget; -} diff --git a/widgets/misc/e-expander.h b/widgets/misc/e-expander.h deleted file mode 100644 index 6ddb68087e..0000000000 --- a/widgets/misc/e-expander.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Mark McLoughlin - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * Copyright (C) 2003 Sun Microsystems, Inc. - * - */ - -#ifndef _E_EXPANDER_H_ -#define _E_EXPANDER_H_ - -#include - -G_BEGIN_DECLS - -#define E_TYPE_EXPANDER (e_expander_get_type ()) -#define E_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_EXPANDER, EExpander)) -#define E_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_EXPANDER, EExpanderClass)) -#define E_IS_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_EXPANDER)) -#define E_IS_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_EXPANDER)) -#define E_EXPANDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_EXPANDER, EExpanderClass)) -/* ESTUFF #define E_EXPANDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_EXPANDER, EExpanderPrivate)) */ - -typedef struct _EExpander EExpander; -typedef struct _EExpanderClass EExpanderClass; - -struct _EExpander -{ - GtkBin bin; -}; - -struct _EExpanderClass -{ - GtkBinClass parent_class; - - void (* activate) (EExpander *expander); -}; - -GType e_expander_get_type (void); - -GtkWidget *e_expander_new (const gchar *label); -GtkWidget *e_expander_new_with_mnemonic (const gchar *label); - -void e_expander_set_expanded (EExpander *expander, - gboolean expanded); -gboolean e_expander_get_expanded (EExpander *expander); - -/* Spacing between the expander/label and the child */ -void e_expander_set_spacing (EExpander *expander, - gint spacing); -gint e_expander_get_spacing (EExpander *expander); - -void e_expander_set_label (EExpander *expander, - const gchar *label); -G_CONST_RETURN gchar *e_expander_get_label (EExpander *expander); - -void e_expander_set_use_underline (EExpander *expander, - gboolean use_underline); -gboolean e_expander_get_use_underline (EExpander *expander); - -void e_expander_set_label_widget (EExpander *expander, - GtkWidget *label_widget); -GtkWidget *e_expander_get_label_widget (EExpander *expander); - -G_END_DECLS - -#endif /* _E_EXPANDER_H_ */ -- cgit v1.2.3