diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | widgets/misc/ChangeLog | 8 | ||||
-rw-r--r-- | widgets/misc/Makefile.am | 7 | ||||
-rw-r--r-- | widgets/misc/e-attachment-bar.c | 934 | ||||
-rw-r--r-- | widgets/misc/e-attachment-bar.h | 86 | ||||
-rw-r--r-- | widgets/misc/e-attachment.c | 639 | ||||
-rw-r--r-- | widgets/misc/e-attachment.glade | 237 | ||||
-rw-r--r-- | widgets/misc/e-attachment.h | 98 |
9 files changed, 2014 insertions, 2 deletions
@@ -1,3 +1,8 @@ +2005-07-11 Srinivasa Ragavan <sragavan@novell.com> + + * configure.in: Added gnome-vfs-module-2.0 to E_WIDGETS_CFLAGS + for the merged attachment bar. + 2005-07-10 Shreyas Srinivasan <sshreyas@novell.com> * configure.in: Add mail-account-disable, proxy, proxy-login and diff --git a/configure.in b/configure.in index df4286e8e5..09c6ac8d13 100644 --- a/configure.in +++ b/configure.in @@ -1221,7 +1221,7 @@ EVO_SET_COMPILE_FLAGS(TZDIALOG, libecal-$EDS_PACKAGE >= $EDS_REQUIRED, $GNOME_FU AC_SUBST(TZDIALOG_CFLAGS) AC_SUBST(TZDIALOG_LIBS) -EVO_SET_COMPILE_FLAGS(E_WIDGETS, glib-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gnome-vfs-2.0 libgnomeui-2.0 libglade-2.0 libgnomecanvas-2.0 libxml-2.0 gconf-2.0 libedataserverui-$EDS_PACKAGE) +EVO_SET_COMPILE_FLAGS(E_WIDGETS, glib-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gnome-vfs-2.0 gnome-vfs-module-2.0 libgnomeui-2.0 libglade-2.0 libgnomecanvas-2.0 libxml-2.0 gconf-2.0 libedataserverui-$EDS_PACKAGE) AC_SUBST(E_WIDGETS_CFLAGS) AC_SUBST(E_WIDGETS_LIBS) diff --git a/widgets/misc/ChangeLog b/widgets/misc/ChangeLog index b259d2595d..a16b0c4265 100644 --- a/widgets/misc/ChangeLog +++ b/widgets/misc/ChangeLog @@ -1,3 +1,11 @@ +2005-07-11 Srinivasa Ragavan <sragavan@novell.com> + + * Makefile.am: Added e-attachment-bar.[ch] e-attachment.[ch] + e-attachment.glade. + * e-attachment.[ch]: Merge of em-attachment.[ch] and cal-attachment.[ch] + * e-attachment-bar.[ch]: Merge of em-attachment-bar.[ch] and + cal-attachment-bar.[ch]. + 2005-06-23 Kaushal Kumar <kakumar@novell.com> * Makefile.am: Use 'misc' for widgetsincludedir, instead of diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index 042bdf5699..89a6aceeb1 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -27,7 +27,8 @@ pilot_headers = endif glade_DATA = e-send-options.glade \ - gal-categories.glade + gal-categories.glade \ + e-attachment.glade libemiscwidgets_la_LDFLAGS = $(ICONV_LIBS) @@ -35,6 +36,8 @@ libemiscwidgets_la_LDFLAGS = $(ICONV_LIBS) widgetsinclude_HEADERS = \ $(pilot_headers) \ e-activity-handler.h \ + e-attachment.h \ + e-attachment-bar.h \ e-calendar.h \ e-calendar-item.h \ e-cell-date-edit.h \ @@ -82,6 +85,8 @@ libemiscwidgets_la_SOURCES = \ $(pilot_sources) \ e-activity-handler.c \ e-calendar.c \ + e-attachment.c \ + e-attachment-bar.c \ e-calendar-item.c \ e-cell-date-edit.c \ e-cell-percent.c \ diff --git a/widgets/misc/e-attachment-bar.c b/widgets/misc/e-attachment-bar.c new file mode 100644 index 0000000000..35aefed59d --- /dev/null +++ b/widgets/misc/e-attachment-bar.c @@ -0,0 +1,934 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Authors: Ettore Perazzoli <ettore@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * Srinivasa Ragavan <sragavan@novell.com> + * + * Copyright 1999-2005 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <gconf/gconf.h> +#include <gconf/gconf-client.h> +#include <gdk/gdkkeysyms.h> +#include <libgnomevfs/gnome-vfs-mime-handlers.h> +#include <libgnome/gnome-i18n.h> + +#include "e-attachment.h" +#include "e-attachment-bar.h" + +#include <libedataserver/e-iconv.h> + +#include <camel/camel-data-wrapper.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-stream-fs.h> +#include <camel/camel-stream-null.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter-bestenc.h> +#include <camel/camel-mime-part.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 */ + + GList *attachments; + guint num_attachments; + gchar *path; +}; + + +enum { + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + + +static void update (EAttachmentBar *bar); + + +static char * +size_to_string (gulong size) +{ + char *size_string; + + /* FIXME: The following should probably go into a separate module, as + we might have to do the same thing in other places as well. Also, + I am not sure this will be OK for all the languages. */ + + if (size < 1e3L) { + size_string = NULL; + } else { + gdouble displayed_size; + + if (size < 1e6L) { + displayed_size = (gdouble) size / 1.0e3; + size_string = g_strdup_printf (_("%.0fK"), displayed_size); + } else if (size < 1e9L) { + displayed_size = (gdouble) size / 1.0e6; + size_string = g_strdup_printf (_("%.0fM"), displayed_size); + } else { + displayed_size = (gdouble) size / 1.0e9; + size_string = g_strdup_printf (_("%.0fG"), displayed_size); + } + } + + return size_string; +} + +/* Attachment handling functions. */ + +static void +free_attachment_list (EAttachmentBar *bar) +{ + EAttachmentBarPrivate *priv; + GList *p; + + priv = bar->priv; + + for (p = priv->attachments; p != NULL; p = p->next) + g_object_unref (p->data); + + priv->attachments = NULL; +} + +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_signal_connect (attachment, "changed", + G_CALLBACK (attachment_changed_cb), + bar); + + bar->priv->attachments = g_list_append (bar->priv->attachments, + attachment); + bar->priv->num_attachments++; + + 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); + attachment = e_attachment_new (file_name, disposition, &ex); + if (attachment) { + 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); + } +} + +static void +remove_attachment (EAttachmentBar *bar, + EAttachment *attachment) +{ + g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); + g_return_if_fail (g_list_find (bar->priv->attachments, attachment) != NULL); + + bar->priv->attachments = g_list_remove (bar->priv->attachments, + attachment); + bar->priv->num_attachments--; + if (attachment->editor_gui != NULL) { + GtkWidget *dialog = glade_xml_get_widget (attachment->editor_gui, "dialog"); + g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_CLOSE); + } + + g_object_unref(attachment); + + g_signal_emit (bar, signals[CHANGED], 0); +} + + +/* Icon list contents handling. */ + +static void +update (EAttachmentBar *bar) +{ + EAttachmentBarPrivate *priv; + GnomeIconList *icon_list; + GList *p; + + 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 (p = priv->attachments; p != NULL; p = p->next) { + EAttachment *attachment; + CamelContentType *content_type; + char *size_string, *label; + GdkPixbuf *pixbuf=NULL; + const char *desc; + + attachment = p->data; + + if (!attachment->is_available_local) { + /* stock_attach would be better, but its fugly scaled up */ + pixbuf = e_icon_factory_get_icon("stock_unknown", E_ICON_SIZE_DIALOG); + if (pixbuf) { + 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 */ + pixbuf = attachment->pixbuf_cache; + if (pixbuf) { + 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) { + int ratio, width, height; + + /* Shrink pixbuf */ + pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + 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; + } + } else { + if (height > 48) { + ratio = height / 48; + height = 48; + width = width / ratio; + } + } + + attachment->pixbuf_cache = gdk_pixbuf_scale_simple + (pixbuf, + width, + height, + GDK_INTERP_BILINEAR); + 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); + } + + desc = camel_mime_part_get_description (attachment->body); + if (!desc || *desc == '\0') + desc = camel_mime_part_get_filename (attachment->body); + + if (!desc) + desc = _("attachment"); + + if (attachment->size + && (size_string = size_to_string (attachment->size))) { + label = g_strdup_printf ("%s (%s)", desc, size_string); + g_free (size_string); + } else + label = g_strdup (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); + /* stock_attach would be better, but its fugly scaled up */ + pixbuf = e_icon_factory_get_icon("stock_unknown", E_ICON_SIZE_DIALOG); + } + g_free (mime_type); + } + + if (pixbuf) { + pixbuf = gdk_pixbuf_add_alpha (pixbuf, TRUE, 255, 255, 255); + + /* 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); +} + +static void +update_remote_file (EAttachmentBar *bar, EAttachment *attachment, char *msg) +{ + EAttachmentBarPrivate *priv; + GnomeIconList *icon_list; + GnomeIconTextItem *item; + + priv = bar->priv; + 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) +{ + GnomeIconList *icon_list; + EAttachment *attachment; + GList *attachment_list, *p; + int num = 0, left, dlen; + + icon_list = GNOME_ICON_LIST (bar); + + /* Weee! I am especially proud of this piece of cheesy code: it is + truly awful. But unless one attaches a huge number of files, it + will not be as greedy as intended. FIXME of course. */ + + attachment_list = NULL; + p = gnome_icon_list_get_selection (icon_list); + dlen = g_list_length (p); + for ( ; p != NULL; p = p->next) { + num = GPOINTER_TO_INT (p->data); + attachment = E_ATTACHMENT (g_list_nth_data (bar->priv->attachments, num)); + + /* We need to check if there are duplicated index in the return list of + gnome_icon_list_get_selection() because of gnome bugzilla bug #122356. + FIXME in the future. */ + + if (g_list_find (attachment_list, attachment) == NULL) { + attachment_list = g_list_prepend (attachment_list, attachment); + } + } + + for (p = attachment_list; p != NULL; p = p->next) + remove_attachment (bar, E_ATTACHMENT (p->data)); + + g_list_free (attachment_list); + + update (bar); + + left = gnome_icon_list_get_num_icons (icon_list); + num = num - dlen + 1; + if (left > 0) + gnome_icon_list_focus_icon (icon_list, left > num ? num : left - 1); +} + +void +e_attachment_bar_edit_selected (EAttachmentBar *bar) +{ + GnomeIconList *icon_list; + GList *selection, *attach; + int num; + + icon_list = GNOME_ICON_LIST (bar); + + selection = gnome_icon_list_get_selection (icon_list); + if (selection) { + num = GPOINTER_TO_INT (selection->data); + attach = g_list_nth (bar->priv->attachments, num); + if (attach) + e_attachment_edit ((EAttachment *)attach->data, GTK_WIDGET (bar)); + } +} + +GtkWidget ** +e_attachment_bar_get_selector(EAttachmentBar *bar) +{ + return &bar->priv->attach; +} + +/* if id != -1, then use it as an index for target of the popup */ +GSList * +e_attachment_bar_get_attachment (EAttachmentBar *bar, int id) +{ + GSList *attachments = NULL; + GList *p; + EAttachment *attachment; + + /* We need to check if there are duplicated index in the return list of + gnome_icon_list_get_selection() because of gnome bugzilla bug #122356. + FIXME in the future. */ + + if (id == -1 + || (attachment = g_list_nth_data(bar->priv->attachments, id)) == NULL) { + p = gnome_icon_list_get_selection((GnomeIconList *)bar); + for ( ; p != NULL; p = p->next) { + int num = GPOINTER_TO_INT(p->data); + EAttachment *attachment = g_list_nth_data(bar->priv->attachments, num); + + if (attachment && g_slist_find(attachments, attachment) == NULL) { + g_object_ref(attachment); + attachments = g_slist_prepend(attachments, attachment); + } + } + attachments = g_slist_reverse(attachments); + } else { + g_object_ref(attachment); + attachments = g_slist_prepend(attachments, attachment); + } + + return attachments; +} + +/* Just the GSList has to be freed by the caller */ +GSList * +e_attachment_bar_get_attachment_part_list (EAttachmentBar *bar) +{ + EAttachment *attachment; + GSList *p = NULL, *part_list = NULL; + + for ( p = bar->priv->attachments; p!= NULL; p = p->next) { + attachment = p->data; + + if (attachment && attachment->is_available_local) { + part_list = g_slist_prepend(part_list, attachment->body); + } + } + return part_list; +} + +/* GtkObject methods. */ + +static void +destroy (GtkObject *object) +{ + EAttachmentBar *bar; + + bar = E_ATTACHMENT_BAR (object); + + if (bar->priv) { + free_attachment_list (bar); + + if (bar->priv->attach) + gtk_widget_destroy(bar->priv->attach); + + if (bar->priv->path) + g_free (bar->priv->path); + + g_free (bar->priv); + bar->priv = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +/* Initialization. */ + +static void +class_init (EAttachmentBarClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GnomeIconListClass *icon_list_class; + + object_class = GTK_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + icon_list_class = GNOME_ICON_LIST_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) +{ + EAttachmentBarPrivate *priv; + + priv = g_new (EAttachmentBarPrivate, 1); + + priv->attach = NULL; + priv->attachments = NULL; + priv->num_attachments = 0; + priv->path = NULL; + + bar->priv = priv; +} + + +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 width, height, icon_width, window_height; + PangoFontMetrics *metrics; + PangoContext *context; + + new = g_object_new (e_attachment_bar_get_type (), NULL); + + icon_list = GNOME_ICON_LIST (new); + + context = gtk_widget_get_pango_context ((GtkWidget *) new); + metrics = pango_context_get_metrics (context, ((GtkWidget *) new)->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; + icon_width = MAX (icon_width, width); + + gnome_icon_list_construct (icon_list, icon_width, adj, 0); + + window_height = ICON_WIDTH + ICON_SPACING + ICON_BORDER + ICON_TEXT_SPACING + height; + gtk_widget_set_size_request (GTK_WIDGET (new), icon_width * 4, window_height); + + 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")); + + 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 = e_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; + + 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) +{ + EAttachmentBarPrivate *priv; + GList *p; + + g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); + g_return_if_fail (CAMEL_IS_MULTIPART (multipart)); + + priv = bar->priv; + + for (p = priv->attachments; p != NULL; p = p->next) { + EAttachment *attachment; + + attachment = E_ATTACHMENT (p->data); + 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 (bar != NULL, 0); + g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), 0); + + return bar->priv->num_attachments; +} + + +void +e_attachment_bar_attach (EAttachmentBar *bar, + const gchar *file_name, + 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) +{ + add_common (bar, attachment); +} + +typedef struct DownloadInfo { + EAttachment *attachment; + EAttachmentBar *bar; + gchar *file_name; +}DownloadInfo; + +static int +async_progress_update_cb (GnomeVFSAsyncHandle *handle, + GnomeVFSXferProgressInfo *info, + DownloadInfo *download_info) +{ + int percent=0; + switch (info->status) { + case GNOME_VFS_XFER_PROGRESS_STATUS_OK: + { + gchar *base_path = g_path_get_basename(download_info->attachment->file_name); + if (info->file_size) { + percent = info->bytes_copied*100/info->file_size; + update_remote_file (download_info->bar, + download_info->attachment, + g_strdup_printf("%s (%d\%)", base_path, percent)); + } else { + update_remote_file (download_info->bar, + download_info->attachment, + g_strdup_printf("%s (%d\%)", base_path, percent)); + } + g_free (base_path); + + if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) { + CamelException ex; + + download_info->attachment->is_available_local = TRUE; + download_info->attachment->handle = NULL; + camel_exception_init (&ex); + e_attachment_build_remote_file (download_info->file_name, download_info->attachment, "attachment", &ex); + update(download_info->bar); + g_free (download_info->file_name); + g_free (download_info); + } + return TRUE; + break; + } + case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR: + gnome_vfs_async_cancel (handle); + g_free (download_info->file_name); + g_free (download_info); + return FALSE; + break; + + default: + break; + } + + return TRUE; +} + +static void +download_to_local_path (GnomeVFSURI *source_uri, GnomeVFSURI *target_uri, DownloadInfo *download_info) + +{ + GnomeVFSResult result; + GList *source_uri_list = NULL; + GList *target_uri_list = NULL; + + source_uri_list = g_list_prepend (source_uri_list, source_uri); + target_uri_list = g_list_prepend (target_uri_list, target_uri); + + /* Callback info */ + result = gnome_vfs_async_xfer (&download_info->attachment->handle, /* handle_return */ + source_uri_list, /* source_uri_list */ + target_uri_list, /* target_uri_list */ + GNOME_VFS_XFER_DEFAULT, /* xfer_options */ + GNOME_VFS_XFER_ERROR_MODE_ABORT, /* error_mode */ + GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE, /* overwrite_mode */ + GNOME_VFS_PRIORITY_DEFAULT, /* priority */ + async_progress_update_cb, /* progress_update_callback */ + download_info, /* update_callback_data */ + NULL, /* progress_sync_callback */ + NULL); /* sync_callback_data */ +} + + +int +e_attachment_bar_get_download_count (EAttachmentBar *bar) +{ + EAttachmentBarPrivate *priv; + GList *p; + int count=0; + + priv = bar->priv; + + for (p = priv->attachments; p != NULL; p = p->next) { + EAttachment *attachment; + + attachment = p->data; + if (!attachment->is_available_local) + count++; + } + + return count; +} + +void +e_attachment_bar_attach_remote_file (EAttachmentBar *bar, + const gchar *url) +{ + EAttachment *attachment; + CamelException ex; + gchar *tmpfile; + gchar *base; + + if (!bar->priv->path) + bar->priv->path = e_mkdtemp("attach-XXXXXX"); + base = g_path_get_basename (url); + + g_return_if_fail (E_IS_ATTACHMENT_BAR (bar)); + tmpfile = g_build_filename (bar->priv->path, base, NULL); + + g_free (base); + + camel_exception_init (&ex); + attachment = e_attachment_new_remote_file (tmpfile, "attachment", &ex); + if (attachment) { + DownloadInfo *download_info; + download_info = g_new (DownloadInfo, 1); + download_info->attachment = attachment; + download_info->bar =bar; + download_info->file_name = g_strdup (tmpfile); + add_common (bar, attachment); + download_to_local_path (gnome_vfs_uri_new(url), gnome_vfs_uri_new(tmpfile), download_info); + + } else { + e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)bar), "mail-composer:no-attach", + attachment->file_name, camel_exception_get_description(&ex), NULL); + camel_exception_clear (&ex); + } + + g_free (tmpfile); + +} + +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); +} diff --git a/widgets/misc/e-attachment-bar.h b/widgets/misc/e-attachment-bar.h new file mode 100644 index 0000000000..947376339e --- /dev/null +++ b/widgets/misc/e-attachment-bar.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-attachment-bar.h + * + * Copyright (C) 2005 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * published by the Free Software Foundation; either version 2 of the + * License as published by the Free Software Foundation. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli + * Srinivasa Ragavan + */ + +#ifndef __E_ATTACHMENT_BAR_H__ +#define __E_ATTACHMENT_BAR_H__ + +#include <libgnomeui/gnome-icon-list.h> + +#include <camel/camel-multipart.h> +#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; +typedef struct _EAttachmentBarPrivate EAttachmentBarPrivate; + +struct _EAttachmentBar { + GnomeIconList parent; + + EAttachmentBarPrivate *priv; +}; + +struct _EAttachmentBarClass { + GnomeIconListClass parent_class; + + void (* changed) (EAttachmentBar *bar); +}; + + +GtkType 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, 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 gchar *url); +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_attachment_part_list (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 new file mode 100644 index 0000000000..3e06467578 --- /dev/null +++ b/widgets/misc/e-attachment.c @@ -0,0 +1,639 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Authors: Ettore Perazzoli <ettore@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * Srinivasa Ragavan <sragavan@novell.com> + * + * Copyright 1999-2005 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/stat.h> +#include <string.h> +#include <errno.h> + +#include <camel/camel.h> +#include <gtk/gtk.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkdialog.h> +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnome/gnome-i18n.h> + +#include "e-util/e-mktemp.h" + +#include "e-attachment.h" + +enum { + CHANGED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +static GObjectClass *parent_class = NULL; + +static void +changed (EAttachment *attachment) +{ + g_signal_emit (attachment, signals[CHANGED], 0); +} + + +/* GtkObject methods. */ + +static void +finalise(GObject *object) +{ + EAttachment *attachment; + + attachment = E_ATTACHMENT (object); + + if (attachment->is_available_local) { + camel_object_unref (attachment->body); + if (attachment->pixbuf_cache != NULL) + g_object_unref (attachment->pixbuf_cache); + } else { + if (attachment->handle) + gnome_vfs_async_cancel(attachment->handle); + if (attachment->file_name) + g_free (attachment->file_name); + if (attachment->description) + g_free (attachment->description); + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/* Signals. */ + +static void +real_changed (EAttachment *attachment) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); +} + + +static void +class_init (EAttachmentClass *klass) +{ + GObjectClass *object_class; + + object_class = (GObjectClass*) klass; + parent_class = g_type_class_ref (G_TYPE_OBJECT); + + object_class->finalize = finalise; + klass->changed = real_changed; + + 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); +} + +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->description = NULL; + attachment->disposition = FALSE; + attachment->sign = CAMEL_CIPHER_VALIDITY_SIGN_NONE; + attachment->encrypt = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE; +} + +GType +e_attachment_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof (EAttachmentClass), + NULL, + NULL, + (GClassInitFunc) class_init, + NULL, + NULL, + sizeof (EAttachment), + 0, + (GInstanceInitFunc) init, + }; + + type = g_type_register_static (G_TYPE_OBJECT, "EAttachment", &info, 0); + } + + return type; +} + +static char * +attachment_guess_mime_type (const char *file_name) +{ + GnomeVFSFileInfo *info; + GnomeVFSResult result; + char *type = NULL; + + info = gnome_vfs_file_info_new (); + result = gnome_vfs_get_file_info (file_name, info, + GNOME_VFS_FILE_INFO_GET_MIME_TYPE | + GNOME_VFS_FILE_INFO_FORCE_SLOW_MIME_TYPE | + GNOME_VFS_FILE_INFO_FOLLOW_LINKS); + if (result == GNOME_VFS_OK) + type = g_strdup (gnome_vfs_file_info_get_mime_type (info)); + + gnome_vfs_file_info_unref (info); + + return type; +} + + +/** + * 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) +{ + EAttachment *new; + CamelMimePart *part; + CamelDataWrapper *wrapper; + CamelStream *stream; + struct stat statbuf; + char *mime_type; + char *filename; + + g_return_val_if_fail (file_name != NULL, NULL); + + if (stat (file_name, &statbuf) < 0) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot attach file %s: %s"), + file_name, g_strerror (errno)); + return 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); + return NULL; + } + + stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0); + if (!stream) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot attach file %s: %s"), + file_name, g_strerror (errno)); + return NULL; + } + + mime_type = attachment_guess_mime_type (file_name); + if (mime_type) { + 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); + + part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); + camel_object_unref (wrapper); + + camel_mime_part_set_disposition (part, disposition); + filename = g_path_get_basename (file_name); + camel_mime_part_set_filename (part, filename); + g_free (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 + + 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->handle = NULL; + new->is_available_local = TRUE; + + return new; +} + + +EAttachment * +e_attachment_new_remote_file (const char *file_name, + const char *disposition, + CamelException *ex) +{ + EAttachment *new; + + g_return_val_if_fail (file_name != NULL, NULL); + + new = g_object_new (E_TYPE_ATTACHMENT, NULL); + new->editor_gui = NULL; + new->body = NULL; + new->size = 0; + new->guessed_type = FALSE; + new->handle = NULL; + new->is_available_local = FALSE; + new->file_name = g_path_get_basename(file_name); + + return new; +} + +void +e_attachment_build_remote_file (const char *file_name, + EAttachment *attachment, + const char *disposition, + CamelException *ex) +{ + CamelMimePart *part; + CamelDataWrapper *wrapper; + CamelStream *stream; + struct stat statbuf; + char *mime_type; + char *filename; + + g_return_if_fail (file_name != NULL); + + if (stat (file_name, &statbuf) < 0) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot attach file %s: %s"), + file_name, g_strerror (errno)); + 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; + } + + stream = camel_stream_fs_new_with_name (file_name, O_RDONLY, 0); + if (!stream) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot attach file %s: %s"), + file_name, g_strerror (errno)); + return; + } + + mime_type = attachment_guess_mime_type (file_name); + if (mime_type) { + 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); + + part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); + camel_object_unref (wrapper); + + if (attachment->disposition) + camel_mime_part_set_disposition (part, "inline"); + else + camel_mime_part_set_disposition (part, "attachment"); + + if (!attachment->file_name) + filename = g_path_get_basename (file_name); + else + filename = g_path_get_basename (attachment->file_name); + + camel_mime_part_set_filename (part, filename); + g_free (filename); + + if (attachment->description) { + camel_mime_part_set_description (part, attachment->description); + g_free (attachment->description); + attachment->description = NULL; + } + + attachment->editor_gui = NULL; + attachment->body = part; + attachment->size = statbuf.st_size; + attachment->guessed_type = TRUE; + if (attachment->file_name) { + g_free (attachment->file_name); + attachment->file_name = NULL; + } +} + + +/** + * 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) +{ + EAttachment *new; + CamelMimePart *mime_part; + CamelStream *stream; + + g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); + + stream = camel_stream_mem_new (); + if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (part), stream) == -1) { + camel_object_unref (stream); + return NULL; + } + + camel_stream_reset (stream); + mime_part = camel_mime_part_new (); + + if (camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mime_part), stream) == -1) { + camel_object_unref (mime_part); + camel_object_unref (stream); + return NULL; + } + + camel_object_unref (stream); + + new = g_object_new (E_TYPE_ATTACHMENT, NULL); + new->editor_gui = NULL; + new->body = mime_part; + new->guessed_type = FALSE; + new->is_available_local = TRUE; + new->size = 0; + + return new; +} + + +/* The attachment property dialog. */ + +typedef struct { + GtkWidget *dialog; + GtkEntry *file_name_entry; + GtkEntry *description_entry; + GtkEntry *mime_type_entry; + GtkToggleButton *disposition_checkbox; + EAttachment *attachment; +} DialogData; + +static void +destroy_dialog_data (DialogData *data) +{ + g_free (data); +} + +/* + * 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. + */ + +static void +set_entry (GladeXML *xml, const char *widget_name, const char *value) +{ + GtkEntry *entry; + + 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 : ""); +} + +static void +connect_widget (GladeXML *gui, const char *name, const char *signal_name, + GCallback func, gpointer data) +{ + GtkWidget *widget; + + widget = glade_xml_get_widget (gui, name); + g_signal_connect (widget, signal_name, func, data); +} + +static void +close_cb (GtkWidget *widget, gpointer data) +{ + EAttachment *attachment; + DialogData *dialog_data; + + dialog_data = (DialogData *) data; + attachment = dialog_data->attachment; + + gtk_widget_destroy (dialog_data->dialog); + g_object_unref (attachment->editor_gui); + attachment->editor_gui = NULL; + + g_object_unref (attachment); + + destroy_dialog_data (dialog_data); +} + +static void +ok_cb (GtkWidget *widget, gpointer 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); + } else { + if (attachment->file_name) + 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 { + if (attachment->description) + g_free (attachment->description); + attachment->description = g_strdup (str); + } + + 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 (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); + } + + changed (attachment); + close_cb (widget, data); +} + +static void +response_cb (GtkWidget *widget, gint response, gpointer data) +{ + if (response == GTK_RESPONSE_OK) + ok_cb (widget, data); + else + close_cb (widget, data); +} + +void +e_attachment_edit (EAttachment *attachment, GtkWidget *parent) +{ + CamelContentType *content_type; + const char *disposition; + DialogData *dialog_data; + GladeXML *editor_gui; + char *type; + + g_return_if_fail (attachment != NULL); + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if (attachment->editor_gui != NULL) { + GtkWidget *window; + + window = glade_xml_get_widget (attachment->editor_gui, + "dialog"); + gdk_window_show (window->window); + return; + } + + editor_gui = glade_xml_new (EVOLUTION_GLADEDIR "/e-attachment.glade", + NULL, NULL); + if (editor_gui == NULL) { + g_warning ("Cannot load `e-attachment.glade'"); + 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); + g_object_ref (attachment); + 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) { + 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); + type = attachment_guess_mime_type (attachment->file_name); + if (type) { + set_entry (editor_gui, "mime_type_entry", type); + g_free (type); + } else { + set_entry (editor_gui, "mime_type_entry", ""); + } + + gtk_toggle_button_set_active (dialog_data->disposition_checkbox, attachment->disposition); + + } + + connect_widget (editor_gui, "dialog", "response", (GCallback)response_cb, dialog_data); +#warning "signal connect while alive" + /* make sure that when the parent gets hidden/closed that our windows also close */ + parent = gtk_widget_get_toplevel (parent); + gtk_signal_connect_while_alive (GTK_OBJECT (parent), "destroy", (GCallback)close_cb, dialog_data, + GTK_OBJECT (dialog_data->dialog)); + gtk_signal_connect_while_alive (GTK_OBJECT (parent), "hide", (GCallback)close_cb, dialog_data, + GTK_OBJECT (dialog_data->dialog)); +} diff --git a/widgets/misc/e-attachment.glade b/widgets/misc/e-attachment.glade new file mode 100644 index 0000000000..dc228eef70 --- /dev/null +++ b/widgets/misc/e-attachment.glade @@ -0,0 +1,237 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkDialog" id="dialog"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="title" translatable="yes">Attachment Properties</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="has_separator">True</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="close_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="ok_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkEntry" id="description_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char" translatable="yes">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="mime_type_entry"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char" translatable="yes">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="mime_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">MIME type:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="description_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Description:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="filename_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">File name:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="file_name_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char" translatable="yes">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkCheckButton" id="disposition_checkbox"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Suggest automatic display of attachment</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/widgets/misc/e-attachment.h b/widgets/misc/e-attachment.h new file mode 100644 index 0000000000..1a0ef7d167 --- /dev/null +++ b/widgets/misc/e-attachment.h @@ -0,0 +1,98 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-attachment.h + * + * Copyright (C) 2005 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * published by the Free Software Foundation; either version 2 of the + * License as published by the Free Software Foundation. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli + * Srinivasa Ragavan + */ + +#ifndef __E_ATTACHMENT_H__ +#define __E_ATTACHMENT_H__ + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glade/glade-xml.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-exception.h> +#include <camel/camel-cipher-context.h> +#include <libgnomevfs/gnome-vfs.h> + +#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; + +struct _EAttachment { + GObject parent; + + GladeXML *editor_gui; + + CamelMimePart *body; + gboolean guessed_type; + gulong size; + + GdkPixbuf *pixbuf_cache; + + GnomeVFSAsyncHandle *handle; + gboolean is_available_local; + char *file_name; + char *description; + gboolean disposition; + int index; + + /* Status of signed/encrypted attachments */ + camel_cipher_validity_sign_t sign; + camel_cipher_validity_encrypt_t encrypt; +}; + +struct _EAttachmentClass { + GObjectClass parent_class; + + void (*changed) (EAttachment *attachment); +}; + +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 (const char *file_name, + const char *disposition, + 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__ */ |