1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <tny-gtk-text-buffer-stream.h>
35 #include <glib/gi18n.h>
36 #include <gtkhtml/gtkhtml.h>
37 #include <gtkhtml/gtkhtml-stream.h>
39 #include <tny-simple-list.h>
41 #include <modest-tny-msg.h>
42 #include <modest-text-utils.h>
43 #include "modest-msg-view.h"
44 #include "modest-tny-stream-gtkhtml.h"
45 #include <modest-mail-header-view.h>
48 /* 'private'/'protected' functions */
49 static void modest_msg_view_class_init (ModestMsgViewClass *klass);
50 static void modest_msg_view_init (ModestMsgView *obj);
51 static void modest_msg_view_finalize (GObject *obj);
53 static void on_recpt_activated (ModestMailHeaderView *header_view, const gchar *address, ModestMsgView *msg_view);
54 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
55 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream,
56 ModestMsgView *msg_view);
57 static gboolean on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
59 #define ATT_PREFIX "att:"
65 ATTACHMENT_CLICKED_SIGNAL,
66 RECPT_ACTIVATED_SIGNAL,
70 typedef struct _ModestMsgViewPrivate ModestMsgViewPrivate;
71 struct _ModestMsgViewPrivate {
73 GtkWidget *mail_header_view;
76 gulong sig1, sig2, sig3;
78 #define MODEST_MSG_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
79 MODEST_TYPE_MSG_VIEW, \
80 ModestMsgViewPrivate))
82 static GtkContainerClass *parent_class = NULL;
84 /* uncomment the following if you have defined any signals */
85 static guint signals[LAST_SIGNAL] = {0};
88 modest_msg_view_get_type (void)
90 static GType my_type = 0;
92 static const GTypeInfo my_info = {
93 sizeof(ModestMsgViewClass),
95 NULL, /* base finalize */
96 (GClassInitFunc) modest_msg_view_class_init,
97 NULL, /* class finalize */
98 NULL, /* class data */
99 sizeof(ModestMsgView),
101 (GInstanceInitFunc) modest_msg_view_init,
104 my_type = g_type_register_static (GTK_TYPE_VBOX,
112 modest_msg_view_class_init (ModestMsgViewClass *klass)
114 GObjectClass *gobject_class;
115 gobject_class = (GObjectClass*) klass;
117 parent_class = g_type_class_peek_parent (klass);
118 gobject_class->finalize = modest_msg_view_finalize;
120 g_type_class_add_private (gobject_class, sizeof(ModestMsgViewPrivate));
123 signals[LINK_CLICKED_SIGNAL] =
124 g_signal_new ("link_clicked",
125 G_TYPE_FROM_CLASS (gobject_class),
127 G_STRUCT_OFFSET(ModestMsgViewClass, link_clicked),
129 g_cclosure_marshal_VOID__STRING,
130 G_TYPE_NONE, 1, G_TYPE_STRING);
132 signals[ATTACHMENT_CLICKED_SIGNAL] =
133 g_signal_new ("attachment_clicked",
134 G_TYPE_FROM_CLASS (gobject_class),
136 G_STRUCT_OFFSET(ModestMsgViewClass, attachment_clicked),
138 g_cclosure_marshal_VOID__POINTER,
139 G_TYPE_NONE, 1, G_TYPE_INT);
141 signals[LINK_HOVER_SIGNAL] =
142 g_signal_new ("link_hover",
143 G_TYPE_FROM_CLASS (gobject_class),
145 G_STRUCT_OFFSET(ModestMsgViewClass, link_hover),
147 g_cclosure_marshal_VOID__STRING,
148 G_TYPE_NONE, 1, G_TYPE_STRING);
150 signals[RECPT_ACTIVATED_SIGNAL] =
151 g_signal_new ("recpt_activated",
152 G_TYPE_FROM_CLASS (gobject_class),
154 G_STRUCT_OFFSET(ModestMsgViewClass, recpt_activated),
156 g_cclosure_marshal_VOID__STRING,
157 G_TYPE_NONE, 1, G_TYPE_STRING);
161 modest_msg_view_init (ModestMsgView *obj)
163 ModestMsgViewPrivate *priv;
165 priv = MODEST_MSG_VIEW_GET_PRIVATE(obj);
168 priv->gtkhtml = gtk_html_new();
169 priv->mail_header_view = GTK_WIDGET(modest_mail_header_view_new ());
170 gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
172 gtk_html_set_editable (GTK_HTML(priv->gtkhtml), FALSE);
173 gtk_html_allow_selection (GTK_HTML(priv->gtkhtml), TRUE);
174 gtk_html_set_caret_mode (GTK_HTML(priv->gtkhtml), FALSE);
175 gtk_html_set_blocking (GTK_HTML(priv->gtkhtml), FALSE);
176 gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
178 priv->sig1 = g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
179 G_CALLBACK(on_link_clicked), obj);
180 priv->sig2 = g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
181 G_CALLBACK(on_url_requested), obj);
182 priv->sig3 = g_signal_connect (G_OBJECT(priv->gtkhtml), "on_url",
183 G_CALLBACK(on_link_hover), obj);
185 g_signal_connect (G_OBJECT (priv->mail_header_view), "recpt-activated",
186 G_CALLBACK (on_recpt_activated), obj);
191 modest_msg_view_finalize (GObject *obj)
193 ModestMsgViewPrivate *priv;
194 priv = MODEST_MSG_VIEW_GET_PRIVATE (obj);
197 g_object_unref (G_OBJECT(priv->msg));
201 /* we cannot disconnect sigs, because priv->gtkhtml is
204 priv->gtkhtml = NULL;
206 G_OBJECT_CLASS(parent_class)->finalize (obj);
211 modest_msg_view_new (TnyMsg *msg)
215 ModestMsgViewPrivate *priv;
216 GtkWidget *scrolled_window;
218 obj = G_OBJECT(g_object_new(MODEST_TYPE_MSG_VIEW, NULL));
219 self = MODEST_MSG_VIEW(obj);
220 priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
222 gtk_box_set_spacing (GTK_BOX (self), 0);
223 gtk_box_set_homogeneous (GTK_BOX (self), FALSE);
225 if (priv->mail_header_view)
226 gtk_box_pack_start (GTK_BOX(self), priv->mail_header_view, FALSE, FALSE, 0);
229 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
230 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
231 GTK_POLICY_AUTOMATIC,
232 GTK_POLICY_AUTOMATIC);
233 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scrolled_window),
235 gtk_container_add (GTK_CONTAINER (scrolled_window), priv->gtkhtml);
236 gtk_box_pack_start (GTK_BOX(self), scrolled_window, TRUE, TRUE, 0);
239 modest_msg_view_set_message (self, msg);
241 return GTK_WIDGET(self);
245 on_recpt_activated (ModestMailHeaderView *header_view,
246 const gchar *address,
247 ModestMsgView * view)
249 g_signal_emit (G_OBJECT (view), signals[RECPT_ACTIVATED_SIGNAL], 0, address);
253 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
257 g_return_val_if_fail (msg_view, FALSE);
259 /* is it an attachment? */
260 if (g_str_has_prefix(uri, ATT_PREFIX)) {
262 index = atoi (uri + strlen(ATT_PREFIX));
265 /* index is 1-based, so 0 indicates an error */
266 g_printerr ("modest: invalid attachment id: %s\n", uri);
270 g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL],
275 g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL],
284 on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
286 if (uri && g_str_has_prefix (uri, ATT_PREFIX))
289 g_signal_emit (G_OBJECT(msg_view), signals[LINK_HOVER_SIGNAL],
298 find_cid_image (TnyMsg *msg, const gchar *cid)
300 TnyMimePart *part = NULL;
304 g_return_val_if_fail (msg, NULL);
305 g_return_val_if_fail (cid, NULL);
307 parts = TNY_LIST (tny_simple_list_new());
309 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
310 iter = tny_list_create_iterator (parts);
312 while (!tny_iterator_is_done(iter)) {
313 const gchar *part_cid;
314 part = TNY_MIME_PART(tny_iterator_get_current(iter));
315 part_cid = tny_mime_part_get_content_id (part);
317 if (part_cid && strcmp (cid, part_cid) == 0)
320 g_object_unref (G_OBJECT(part));
323 tny_iterator_next (iter);
326 g_object_unref (G_OBJECT(iter));
327 g_object_unref (G_OBJECT(parts));
334 on_url_requested (GtkWidget *widget, const gchar *uri,
335 GtkHTMLStream *stream, ModestMsgView *msg_view)
337 ModestMsgViewPrivate *priv;
338 priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
340 if (g_str_has_prefix (uri, "cid:")) {
341 /* +4 ==> skip "cid:" */
342 TnyMimePart *part = find_cid_image (priv->msg, uri + 4);
344 g_printerr ("modest: '%s' not found\n", uri + 4);
345 gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
347 TnyStream *tny_stream =
348 TNY_STREAM(modest_tny_stream_gtkhtml_new(stream));
349 tny_mime_part_decode_to_stream ((TnyMimePart*)part,
351 gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
353 g_object_unref (G_OBJECT(tny_stream));
354 g_object_unref (G_OBJECT(part));
362 /* render the attachments as hyperlinks in html */
364 attachments_as_html (ModestMsgView *self, TnyMsg *msg)
366 ModestMsgViewPrivate *priv;
376 priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
378 parts = TNY_LIST(tny_simple_list_new());
379 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
380 iter = tny_list_create_iterator (parts);
382 appendix= g_string_new ("");
384 while (!tny_iterator_is_done(iter)) {
387 ++index; /* attachment numbers are 1-based */
388 part = TNY_MIME_PART(tny_iterator_get_current (iter));
390 if (tny_mime_part_is_attachment (part)) {
392 const gchar *filename = tny_mime_part_get_filename(part);
394 filename = _("attachment");
396 g_string_append_printf (appendix, "<a href=\"%s%d\">%s</a> \n",
397 ATT_PREFIX, index, filename);
400 g_object_unref (G_OBJECT(part));
401 tny_iterator_next (iter);
403 g_object_unref (G_OBJECT(iter));
404 g_object_unref (G_OBJECT(parts));
406 if (appendix->len == 0)
407 return g_string_free (appendix, TRUE);
409 html = g_strdup_printf ("<strong>%s:</strong> %s\n<hr>",
410 _("Attachments"), appendix->str);
411 g_string_free (appendix, TRUE);
420 set_html_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
422 gchar *html_attachments;
423 GtkHTMLStream *gtkhtml_stream;
424 TnyStream *tny_stream;
425 ModestMsgViewPrivate *priv;
427 g_return_val_if_fail (self, FALSE);
428 g_return_val_if_fail (tny_body, FALSE);
430 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
432 gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml));
434 tny_stream = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
435 tny_stream_reset (tny_stream);
437 html_attachments = attachments_as_html(self, msg);
438 if (html_attachments) {
439 tny_stream_write (tny_stream, html_attachments, strlen(html_attachments));
440 tny_stream_reset (tny_stream);
441 g_free (html_attachments);
444 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, tny_stream);
445 g_object_unref (G_OBJECT(tny_stream));
447 gtk_html_stream_destroy (gtkhtml_stream);
453 /* FIXME: this is a hack --> we use the tny_text_buffer_stream to
454 * get the message text, then write to gtkhtml 'by hand' */
456 set_text_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
459 GtkTextIter begin, end;
460 TnyStream* txt_stream, *tny_stream;
461 GtkHTMLStream *gtkhtml_stream;
462 gchar *txt, *html_attachments;
463 ModestMsgViewPrivate *priv;
465 g_return_val_if_fail (self, FALSE);
466 g_return_val_if_fail (tny_body, FALSE);
468 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
470 buf = gtk_text_buffer_new (NULL);
471 txt_stream = TNY_STREAM(tny_gtk_text_buffer_stream_new (buf));
473 tny_stream_reset (txt_stream);
475 gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml));
476 tny_stream = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
478 html_attachments = attachments_as_html(self, msg);
479 if (html_attachments) {
480 tny_stream_write (tny_stream, html_attachments,
481 strlen(html_attachments));
482 tny_stream_reset (tny_stream);
483 g_free (html_attachments);
487 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, txt_stream);
488 tny_stream_reset (txt_stream);
490 gtk_text_buffer_get_bounds (buf, &begin, &end);
491 txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
493 gchar *html = modest_text_utils_convert_to_html (txt);
494 tny_stream_write (tny_stream, html, strlen(html));
495 tny_stream_reset (tny_stream);
500 g_object_unref (G_OBJECT(tny_stream));
501 g_object_unref (G_OBJECT(txt_stream));
502 g_object_unref (G_OBJECT(buf));
504 gtk_html_stream_destroy (gtkhtml_stream);
511 set_empty_message (ModestMsgView *self)
513 ModestMsgViewPrivate *priv;
515 g_return_val_if_fail (self, FALSE);
516 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
518 gtk_html_load_from_string (GTK_HTML(priv->gtkhtml),
526 modest_msg_view_set_message (ModestMsgView *self, TnyMsg *msg)
529 ModestMsgViewPrivate *priv;
532 g_return_if_fail (self);
534 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
535 gtk_widget_set_no_show_all (priv->mail_header_view, FALSE);
537 if (msg != priv->msg) {
539 g_object_unref (G_OBJECT(priv->msg));
541 g_object_ref (G_OBJECT(msg));
546 tny_header_view_clear (TNY_HEADER_VIEW (priv->mail_header_view));
547 gtk_widget_hide_all (priv->mail_header_view);
548 gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
549 set_empty_message (self);
553 header = tny_msg_get_header (msg);
554 tny_header_view_set_header (TNY_HEADER_VIEW (priv->mail_header_view), header);
555 g_object_unref (header);
557 body = modest_tny_msg_find_body_part (msg,TRUE);
559 if (tny_mime_part_content_type_is (body, "text/html"))
560 set_html_message (self, body, msg);
562 set_text_message (self, body, msg);
564 set_empty_message (self);
566 gtk_widget_show_all (priv->mail_header_view);
567 gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
572 modest_msg_view_get_message (ModestMsgView *self)
574 g_return_val_if_fail (self, NULL);
576 return MODEST_MSG_VIEW_GET_PRIVATE(self)->msg;