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-actions.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)
287 tny_iterator_next (iter);
291 g_object_ref (G_OBJECT(part));
293 g_object_unref (G_OBJECT(iter));
294 g_object_unref (G_OBJECT(parts));
301 on_url_requested (GtkWidget *widget, const gchar *uri,
302 GtkHTMLStream *stream,
303 ModestMsgView *msg_view)
305 ModestMsgViewPrivate *priv;
306 priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
308 if (g_str_has_prefix (uri, "cid:")) {
309 /* +4 ==> skip "cid:" */
310 TnyMimePart *part = find_cid_image (priv->msg, uri + 4);
312 g_printerr ("modest: '%s' not found\n", uri + 4);
313 gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
315 TnyStream *tny_stream =
316 TNY_STREAM(modest_tny_stream_gtkhtml_new(stream));
317 tny_mime_part_decode_to_stream ((TnyMimePart*)part,
319 gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
321 g_object_unref (G_OBJECT(tny_stream));
322 g_object_unref (G_OBJECT(part));
330 /* render the attachments as hyperlinks in html */
332 attachments_as_html (ModestMsgView *self, TnyMsg *msg)
334 ModestMsgViewPrivate *priv;
344 priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
346 parts = TNY_LIST(tny_simple_list_new());
347 tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
348 iter = tny_list_create_iterator (parts);
350 appendix= g_string_new ("");
352 while (!tny_iterator_is_done(iter)) {
355 ++index; /* attachment numbers are 1-based */
357 part = TNY_MIME_PART(tny_iterator_get_current (iter));
359 if (tny_mime_part_is_attachment (part)) {
361 const gchar *filename = tny_mime_part_get_filename(part);
363 filename = _("attachment");
365 g_string_append_printf (appendix, "<a href=\"%s%d\">%s</a> \n",
366 ATT_PREFIX, index, filename);
368 tny_iterator_next (iter);
370 g_object_unref (G_OBJECT(iter));
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 TnyStream *gtkhtml_stream;
390 ModestMsgViewPrivate *priv;
392 g_return_val_if_fail (self, FALSE);
393 g_return_val_if_fail (tny_body, FALSE);
395 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
398 TNY_STREAM(modest_tny_stream_gtkhtml_new
399 (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
401 tny_stream_reset (gtkhtml_stream);
403 html_attachments = attachments_as_html(self, msg);
404 if (html_attachments) {
405 tny_stream_write (gtkhtml_stream, html_attachments,
406 strlen(html_attachments));
407 tny_stream_reset (gtkhtml_stream);
408 g_free (html_attachments);
412 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body,
415 g_object_unref (G_OBJECT(gtkhtml_stream));
421 /* FIXME: this is a hack --> we use the tny_text_buffer_stream to
422 * get the message text, then write to gtkhtml 'by hand' */
424 set_text_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
427 GtkTextIter begin, end;
428 TnyStream* txt_stream, *gtkhtml_stream;
429 gchar *txt, *html_attachments;
430 ModestMsgViewPrivate *priv;
432 g_return_val_if_fail (self, FALSE);
433 g_return_val_if_fail (tny_body, FALSE);
435 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
437 buf = gtk_text_buffer_new (NULL);
438 txt_stream = TNY_STREAM(tny_gtk_text_buffer_stream_new (buf));
440 tny_stream_reset (txt_stream);
443 TNY_STREAM(modest_tny_stream_gtkhtml_new
444 (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
446 html_attachments = attachments_as_html(self, msg);
447 if (html_attachments) {
448 tny_stream_write (gtkhtml_stream, html_attachments,
449 strlen(html_attachments));
450 tny_stream_reset (gtkhtml_stream);
451 g_free (html_attachments);
455 tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body,
457 tny_stream_reset (txt_stream);
459 gtk_text_buffer_get_bounds (buf, &begin, &end);
460 txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
462 gchar *html = modest_text_utils_convert_to_html (txt);
463 tny_stream_write (gtkhtml_stream, html, strlen(html));
464 tny_stream_reset (gtkhtml_stream);
469 g_object_unref (G_OBJECT(gtkhtml_stream));
470 g_object_unref (G_OBJECT(txt_stream));
471 g_object_unref (G_OBJECT(buf));
478 set_empty_message (ModestMsgView *self)
480 ModestMsgViewPrivate *priv;
482 g_return_val_if_fail (self, FALSE);
483 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
485 gtk_html_load_from_string (GTK_HTML(priv->gtkhtml),
493 modest_msg_view_set_message (ModestMsgView *self, TnyMsg *msg)
496 ModestMsgViewPrivate *priv;
498 g_return_if_fail (self);
500 priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
503 if (msg != priv->msg) {
505 g_object_unref (G_OBJECT(priv->msg));
507 g_object_ref (G_OBJECT(msg));
512 set_empty_message (self);
516 body = modest_tny_msg_actions_find_body_part (msg, TRUE);
518 if (tny_mime_part_content_type_is (body, "text/html"))
519 set_html_message (self, body, msg);
521 set_text_message (self, body, msg);
524 set_empty_message (self);