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"
47 /* 'private'/'protected' functions */
48 static void modest_msg_view_class_init (ModestMsgViewClass *klass);
49 static void modest_msg_view_init (ModestMsgView *obj);
50 static void modest_msg_view_finalize (GObject *obj);
52 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
53 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream,
54 ModestMsgView *msg_view);
55 static gboolean on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
57 #define ATT_PREFIX "att:"
63 ATTACHMENT_CLICKED_SIGNAL,
67 typedef struct _ModestMsgViewPrivate ModestMsgViewPrivate;
68 struct _ModestMsgViewPrivate {
73 gulong sig1, sig2, sig3;
75 #define MODEST_MSG_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
76 MODEST_TYPE_MSG_VIEW, \
77 ModestMsgViewPrivate))
79 static GtkContainerClass *parent_class = NULL;
81 /* uncomment the following if you have defined any signals */
82 static guint signals[LAST_SIGNAL] = {0};
85 modest_msg_view_get_type (void)
87 static GType my_type = 0;
89 static const GTypeInfo my_info = {
90 sizeof(ModestMsgViewClass),
92 NULL, /* base finalize */
93 (GClassInitFunc) modest_msg_view_class_init,
94 NULL, /* class finalize */
95 NULL, /* class data */
96 sizeof(ModestMsgView),
98 (GInstanceInitFunc) modest_msg_view_init,
101 my_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
109 modest_msg_view_class_init (ModestMsgViewClass *klass)
111 GObjectClass *gobject_class;
112 gobject_class = (GObjectClass*) klass;
114 parent_class = g_type_class_peek_parent (klass);
115 gobject_class->finalize = modest_msg_view_finalize;
117 g_type_class_add_private (gobject_class, sizeof(ModestMsgViewPrivate));
120 signals[LINK_CLICKED_SIGNAL] =
121 g_signal_new ("link_clicked",
122 G_TYPE_FROM_CLASS (gobject_class),
124 G_STRUCT_OFFSET(ModestMsgViewClass, link_clicked),
126 g_cclosure_marshal_VOID__STRING,
127 G_TYPE_NONE, 1, G_TYPE_STRING);
129 signals[ATTACHMENT_CLICKED_SIGNAL] =
130 g_signal_new ("attachment_clicked",
131 G_TYPE_FROM_CLASS (gobject_class),
133 G_STRUCT_OFFSET(ModestMsgViewClass, attachment_clicked),
135 g_cclosure_marshal_VOID__POINTER,
136 G_TYPE_NONE, 1, G_TYPE_INT);
138 signals[LINK_HOVER_SIGNAL] =
139 g_signal_new ("link_hover",
140 G_TYPE_FROM_CLASS (gobject_class),
142 G_STRUCT_OFFSET(ModestMsgViewClass, link_hover),
144 g_cclosure_marshal_VOID__STRING,
145 G_TYPE_NONE, 1, G_TYPE_STRING);
149 modest_msg_view_init (ModestMsgView *obj)
151 ModestMsgViewPrivate *priv;
153 priv = MODEST_MSG_VIEW_GET_PRIVATE(obj);
156 priv->gtkhtml = gtk_html_new();
158 gtk_html_set_editable (GTK_HTML(priv->gtkhtml), FALSE);
159 gtk_html_allow_selection (GTK_HTML(priv->gtkhtml), TRUE);
160 gtk_html_set_caret_mode (GTK_HTML(priv->gtkhtml), FALSE);
161 gtk_html_set_blocking (GTK_HTML(priv->gtkhtml), FALSE);
162 gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
164 priv->sig1 = g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
165 G_CALLBACK(on_link_clicked), obj);
166 priv->sig2 = g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
167 G_CALLBACK(on_url_requested), obj);
168 priv->sig3 = g_signal_connect (G_OBJECT(priv->gtkhtml), "on_url",
169 G_CALLBACK(on_link_hover), obj);
174 modest_msg_view_finalize (GObject *obj)
176 ModestMsgViewPrivate *priv;
177 priv = MODEST_MSG_VIEW_GET_PRIVATE (obj);
180 g_object_unref (G_OBJECT(priv->msg));
184 /* we cannot disconnect sigs, because priv->gtkhtml is
187 priv->gtkhtml = NULL;
189 G_OBJECT_CLASS(parent_class)->finalize (obj);
194 modest_msg_view_new (TnyMsg *msg)
198 ModestMsgViewPrivate *priv;
200 obj = G_OBJECT(g_object_new(MODEST_TYPE_MSG_VIEW, NULL));
201 self = MODEST_MSG_VIEW(obj);
202 priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
204 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
205 GTK_POLICY_AUTOMATIC,
206 GTK_POLICY_AUTOMATIC);
209 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);
212 modest_msg_view_set_message (self, msg);
214 return GTK_WIDGET(self);
219 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
223 g_return_val_if_fail (msg_view, FALSE);
225 /* is it an attachment? */
226 if (g_str_has_prefix(uri, ATT_PREFIX)) {
228 index = atoi (uri + strlen(ATT_PREFIX));
231 /* index is 1-based, so 0 indicates an error */
232 g_printerr ("modest: invalid attachment id: %s\n", uri);
236 g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL],
241 g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL],
250 on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
252 if (uri && g_str_has_prefix (uri, ATT_PREFIX))
255 g_signal_emit (G_OBJECT(msg_view), signals[LINK_HOVER_SIGNAL],
264 find_cid_image (TnyMsg *msg, const gchar *cid)
266 TnyMimePart *part = NULL;
270 g_return_val_if_fail (msg, NULL);
271 g_return_val_if_fail (cid, NULL);
273 parts = TNY_LIST (tny_simple_list_new());
275 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
276 iter = tny_list_create_iterator (parts);
278 while (!tny_iterator_is_done(iter)) {
279 const gchar *part_cid;
280 part = TNY_MIME_PART(tny_iterator_get_current(iter));
281 part_cid = tny_mime_part_get_content_id (part);
283 if (part_cid && strcmp (cid, part_cid) == 0)
286 g_object_unref (G_OBJECT(part));
289 tny_iterator_next (iter);
292 g_object_unref (G_OBJECT(iter));
293 g_object_unref (G_OBJECT(parts));
300 on_url_requested (GtkWidget *widget, const gchar *uri,
301 GtkHTMLStream *stream, ModestMsgView *msg_view)
303 ModestMsgViewPrivate *priv;
304 priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
306 if (g_str_has_prefix (uri, "cid:")) {
307 /* +4 ==> skip "cid:" */
308 TnyMimePart *part = find_cid_image (priv->msg, uri + 4);
310 g_printerr ("modest: '%s' not found\n", uri + 4);
311 gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
313 TnyStream *tny_stream =
314 TNY_STREAM(modest_tny_stream_gtkhtml_new(stream));
315 tny_mime_part_decode_to_stream ((TnyMimePart*)part,
317 gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
319 g_object_unref (G_OBJECT(tny_stream));
320 g_object_unref (G_OBJECT(part));
328 /* render the attachments as hyperlinks in html */
330 attachments_as_html (ModestMsgView *self, TnyMsg *msg)
332 ModestMsgViewPrivate *priv;
342 priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
344 parts = TNY_LIST(tny_simple_list_new());
345 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
346 iter = tny_list_create_iterator (parts);
348 appendix= g_string_new ("");
350 while (!tny_iterator_is_done(iter)) {
353 ++index; /* attachment numbers are 1-based */
354 part = TNY_MIME_PART(tny_iterator_get_current (iter));
356 if (tny_mime_part_is_attachment (part)) {
358 const gchar *filename = tny_mime_part_get_filename(part);
360 filename = _("attachment");
362 g_string_append_printf (appendix, "<a href=\"%s%d\">%s</a> \n",
363 ATT_PREFIX, index, filename);
366 g_object_unref (G_OBJECT(part));
367 tny_iterator_next (iter);
369 g_object_unref (G_OBJECT(iter));
370 g_object_unref (G_OBJECT(parts));
372 if (appendix->len == 0)
373 return g_string_free (appendix, TRUE);
375 html = g_strdup_printf ("<strong>%s:</strong> %s\n<hr>",
376 _("Attachments"), appendix->str);
377 g_string_free (appendix, TRUE);
386 set_html_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
388 gchar *html_attachments;
389 GtkHTMLStream *gtkhtml_stream;
390 TnyStream *tny_stream;
391 ModestMsgViewPrivate *priv;
393 g_return_val_if_fail (self, FALSE);
394 g_return_val_if_fail (tny_body, FALSE);
396 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
398 gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml));
400 tny_stream = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
401 tny_stream_reset (tny_stream);
403 html_attachments = attachments_as_html(self, msg);
404 if (html_attachments) {
405 tny_stream_write (tny_stream, html_attachments, strlen(html_attachments));
406 tny_stream_reset (tny_stream);
407 g_free (html_attachments);
410 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, tny_stream);
411 g_object_unref (G_OBJECT(tny_stream));
413 gtk_html_stream_destroy (gtkhtml_stream);
419 /* FIXME: this is a hack --> we use the tny_text_buffer_stream to
420 * get the message text, then write to gtkhtml 'by hand' */
422 set_text_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
425 GtkTextIter begin, end;
426 TnyStream* txt_stream, *tny_stream;
427 GtkHTMLStream *gtkhtml_stream;
428 gchar *txt, *html_attachments;
429 ModestMsgViewPrivate *priv;
431 g_return_val_if_fail (self, FALSE);
432 g_return_val_if_fail (tny_body, FALSE);
434 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
436 buf = gtk_text_buffer_new (NULL);
437 txt_stream = TNY_STREAM(tny_gtk_text_buffer_stream_new (buf));
439 tny_stream_reset (txt_stream);
441 gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml));
442 tny_stream = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
444 html_attachments = attachments_as_html(self, msg);
445 if (html_attachments) {
446 tny_stream_write (tny_stream, html_attachments,
447 strlen(html_attachments));
448 tny_stream_reset (tny_stream);
449 g_free (html_attachments);
453 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, txt_stream);
454 tny_stream_reset (txt_stream);
456 gtk_text_buffer_get_bounds (buf, &begin, &end);
457 txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
459 gchar *html = modest_text_utils_convert_to_html (txt);
460 tny_stream_write (tny_stream, html, strlen(html));
461 tny_stream_reset (tny_stream);
466 g_object_unref (G_OBJECT(tny_stream));
467 g_object_unref (G_OBJECT(txt_stream));
468 g_object_unref (G_OBJECT(buf));
470 gtk_html_stream_destroy (gtkhtml_stream);
477 set_empty_message (ModestMsgView *self)
479 ModestMsgViewPrivate *priv;
481 g_return_val_if_fail (self, FALSE);
482 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
484 gtk_html_load_from_string (GTK_HTML(priv->gtkhtml),
492 modest_msg_view_set_message (ModestMsgView *self, TnyMsg *msg)
495 ModestMsgViewPrivate *priv;
497 g_return_if_fail (self);
499 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
501 if (msg != priv->msg) {
503 g_object_unref (G_OBJECT(priv->msg));
505 g_object_ref (G_OBJECT(msg));
510 set_empty_message (self);
514 body = modest_tny_msg_find_body_part (msg, TRUE);
516 if (tny_mime_part_content_type_is (body, "text/html"))
517 set_html_message (self, body, msg);
519 set_text_message (self, body, msg);
522 set_empty_message (self);