1 /*
2 ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
3 **
4 ** This program is free software; you can redistribute it and/or modify it
5 ** under the terms of the GNU General Public License as published by the
6 ** Free Software Foundation; either version 3, or (at your option) any
7 ** later version.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ** GNU General Public License for more details.
13 **
14 ** You should have received a copy of the GNU General Public License
15 ** along with this program; if not, write to the Free Software Foundation,
16 ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 **
18 */
19
20 #include "mu-msg-body-view.hh"
21 #include <mu-msg-part.hh>
22
23 using namespace Mu;
24
25 enum _ViewMode {
26 VIEW_MODE_MSG,
27 VIEW_MODE_SOURCE,
28 VIEW_MODE_NOTE,
29
30 VIEW_MODE_NONE
31 };
32 typedef enum _ViewMode ViewMode;
33
34 /* 'private'/'protected' functions */
35 static void mu_msg_body_view_class_init (MuMsgBodyViewClass *klass);
36 static void mu_msg_body_view_init (MuMsgBodyView *obj);
37 static void mu_msg_body_view_finalize (GObject *obj);
38
39 /* list my signals */
40 enum {
41 ACTION_REQUESTED,
42 /* MY_SIGNAL_2, */
43 LAST_SIGNAL
44 };
45
46
47 struct _MuMsgBodyViewPrivate {
48 WebKitSettings *_settings;
49 MuMsg *_msg;
50 ViewMode _view_mode;
51 };
52
53 #define MU_MSG_BODY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
54 MU_TYPE_MSG_BODY_VIEW, \
55 MuMsgBodyViewPrivate))
56 /* globals */
57 static WebKitWebViewClass *parent_class = NULL;
58
59 static guint signals[LAST_SIGNAL] = {0};
60
61 G_DEFINE_TYPE (MuMsgBodyView, mu_msg_body_view, WEBKIT_TYPE_WEB_VIEW);
62
63 static void
set_message(MuMsgBodyView * self,MuMsg * msg)64 set_message (MuMsgBodyView *self, MuMsg *msg)
65 {
66 if (self->_priv->_msg == msg)
67 return; /* nothing to todo */
68
69 if (self->_priv->_msg) {
70 mu_msg_unref (self->_priv->_msg);
71 self->_priv->_msg = NULL;
72 }
73
74 if (msg)
75 self->_priv->_msg = mu_msg_ref (msg);
76 }
77
78 static void
mu_msg_body_view_class_init(MuMsgBodyViewClass * klass)79 mu_msg_body_view_class_init (MuMsgBodyViewClass *klass)
80 {
81 GObjectClass *gobject_class;
82 gobject_class = (GObjectClass*) klass;
83
84 parent_class = (WebKitWebViewClass*)g_type_class_peek_parent (klass);
85 gobject_class->finalize = mu_msg_body_view_finalize;
86
87 g_type_class_add_private (gobject_class, sizeof(MuMsgBodyViewPrivate));
88
89 signals[ACTION_REQUESTED] =
90 g_signal_new ("action-requested",
91 G_TYPE_FROM_CLASS (gobject_class),
92 G_SIGNAL_RUN_FIRST,
93 G_STRUCT_OFFSET (MuMsgBodyViewClass,
94 action_requested),
95 NULL, NULL,
96 g_cclosure_marshal_VOID__STRING,
97 G_TYPE_NONE, 1, G_TYPE_STRING);
98 }
99
100 static char*
save_file_for_cid(MuMsg * msg,const char * cid)101 save_file_for_cid (MuMsg *msg, const char* cid)
102 {
103 gint idx;
104 gchar *filepath;
105 gboolean rv;
106 GError *err;
107
108 g_return_val_if_fail (msg, NULL);
109 g_return_val_if_fail (cid, NULL);
110
111 idx = mu_msg_find_index_for_cid (msg, MU_MSG_OPTION_NONE, cid);
112 if (idx < 0) {
113 g_warning ("%s: cannot find %s", __func__, cid);
114 return NULL;
115 }
116
117 filepath = mu_msg_part_get_cache_path (msg, MU_MSG_OPTION_NONE, idx, NULL);
118 if (!filepath) {
119 g_warning ("%s: cannot create filepath", filepath);
120 return NULL;
121 }
122
123 err = NULL;
124 rv = mu_msg_part_save (msg, MU_MSG_OPTION_USE_EXISTING,
125 filepath, idx, &err);
126 if (!rv) {
127 g_warning ("%s: failed to save %s: %s", __func__, filepath,
128 err&&err->message?err->message:"error");
129 g_clear_error (&err);
130 g_free (filepath);
131 filepath = NULL;
132 }
133
134 return filepath;
135 }
136
137 static void
on_resource_load_started(MuMsgBodyView * self,WebKitWebResource * resource,WebKitURIRequest * request,gpointer data)138 on_resource_load_started (MuMsgBodyView *self, WebKitWebResource *resource,
139 WebKitURIRequest *request, gpointer data)
140 {
141 const char* uri;
142 MuMsg *msg;
143
144 msg = self->_priv->_msg;
145 uri = webkit_uri_request_get_uri (request);
146
147 /* g_warning ("%s: %s", __func__, uri); */
148
149 if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) {
150 gchar *filepath;
151 filepath = save_file_for_cid (msg, uri);
152 if (filepath) {
153 gchar *fileuri;
154 fileuri = g_strdup_printf ("file://%s", filepath);
155 webkit_uri_request_set_uri (request, fileuri);
156 g_free (fileuri);
157 g_free (filepath);
158 }
159 }
160 }
161
162
163 static void
on_menu_item_activate(GtkMenuItem * item,MuMsgBodyView * self)164 on_menu_item_activate (GtkMenuItem *item, MuMsgBodyView *self)
165 {
166 g_signal_emit (G_OBJECT(self),
167 signals[ACTION_REQUESTED], 0,
168 g_object_get_data (G_OBJECT(item), "action"));
169 }
170
171 static void
popup_menu(MuMsgBodyView * self,guint button,guint32 activate_time)172 popup_menu (MuMsgBodyView *self, guint button, guint32 activate_time)
173 {
174 GtkWidget *menu;
175 int i;
176 struct {
177 const char* title;
178 const char* action;
179 ViewMode mode;
180 } actions[] = {
181 { "View source...", "view-source", VIEW_MODE_MSG },
182 { "View message...", "view-message", VIEW_MODE_SOURCE },
183 };
184
185 menu = gtk_menu_new ();
186
187 for (i = 0; i != G_N_ELEMENTS(actions); ++i) {
188 GtkWidget *item;
189
190 if (self->_priv->_view_mode != actions[i].mode)
191 continue;
192
193 item = gtk_menu_item_new_with_label(actions[i].title);
194 g_object_set_data (G_OBJECT(item), "action", (gpointer)actions[i].action);
195 g_signal_connect (item, "activate", G_CALLBACK(on_menu_item_activate),
196 self);
197 gtk_menu_attach (GTK_MENU(menu), item, 0, 1, i, i+1);
198 gtk_widget_show (item);
199 }
200 gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, 0);
201 }
202
203
204 static gboolean
on_button_press_event(MuMsgBodyView * self,GdkEventButton * event,gpointer data)205 on_button_press_event (MuMsgBodyView *self, GdkEventButton *event, gpointer data)
206 {
207 /* ignore all but the first (typically, left) mouse button */
208 switch (event->button) {
209 case 1: return FALSE; /* propagate, let widget handle it */
210 case 3:
211 /* no popup menus for notes */
212 if (self->_priv->_view_mode != VIEW_MODE_NOTE)
213 popup_menu (self, event->button, event->time);
214 break;
215 default: return TRUE; /* ignore */
216 }
217
218 return (event->button > 1) ? TRUE : FALSE;
219 }
220
221
222 static void
mu_msg_body_view_init(MuMsgBodyView * obj)223 mu_msg_body_view_init (MuMsgBodyView *obj)
224 {
225 obj->_priv = MU_MSG_BODY_VIEW_GET_PRIVATE(obj);
226
227 obj->_priv->_msg = NULL;
228 obj->_priv->_view_mode = VIEW_MODE_NONE;
229
230 obj->_priv->_settings = webkit_settings_new ();
231 g_object_set (G_OBJECT(obj->_priv->_settings),
232 "enable-javascript", FALSE,
233 "auto-load-images", TRUE,
234 "enable-plugins", FALSE,
235 NULL);
236
237 webkit_web_view_set_settings (WEBKIT_WEB_VIEW(obj), obj->_priv->_settings);
238
239 /* to support cid: */
240 g_signal_connect (obj, "resource-load-started",
241 G_CALLBACK (on_resource_load_started), NULL);
242 g_signal_connect (obj, "button-press-event",
243 G_CALLBACK(on_button_press_event), NULL);
244 }
245
246 static void
mu_msg_body_view_finalize(GObject * obj)247 mu_msg_body_view_finalize (GObject *obj)
248 {
249 MuMsgBodyViewPrivate *priv;
250
251 priv = MU_MSG_BODY_VIEW_GET_PRIVATE(obj);
252 if (priv && priv->_settings)
253 g_object_unref (priv->_settings);
254
255 set_message (MU_MSG_BODY_VIEW(obj), NULL);
256
257 G_OBJECT_CLASS(parent_class)->finalize (obj);
258 }
259
260 GtkWidget*
mu_msg_body_view_new(void)261 mu_msg_body_view_new (void)
262 {
263 return GTK_WIDGET(g_object_new(MU_TYPE_MSG_BODY_VIEW, NULL));
264 }
265
266
267 static void
set_html(MuMsgBodyView * self,const char * html)268 set_html (MuMsgBodyView *self, const char* html)
269 {
270 g_return_if_fail (MU_IS_MSG_BODY_VIEW(self));
271
272 webkit_web_view_load_html (WEBKIT_WEB_VIEW(self),
273 html ? html : "",
274 NULL);
275 }
276
277 static void
set_text(MuMsgBodyView * self,const char * txt)278 set_text (MuMsgBodyView *self, const char* txt)
279 {
280 g_return_if_fail (MU_IS_MSG_BODY_VIEW(self));
281
282 webkit_web_view_load_plain_text (WEBKIT_WEB_VIEW(self), txt ? txt : "");
283 }
284
285 void
mu_msg_body_view_set_message(MuMsgBodyView * self,MuMsg * msg)286 mu_msg_body_view_set_message (MuMsgBodyView *self, MuMsg *msg)
287 {
288 const char* data;
289
290 g_return_if_fail (self);
291
292 set_message (self, msg);
293
294 data = msg ? mu_msg_get_body_html (msg, MU_MSG_OPTION_NONE) : "";
295 if (data)
296 set_html (self, data);
297 else
298 set_text (self,
299 mu_msg_get_body_text (msg, MU_MSG_OPTION_NONE));
300
301 self->_priv->_view_mode = VIEW_MODE_MSG;
302 }
303
304
305 void
mu_msg_body_view_set_message_source(MuMsgBodyView * self,MuMsg * msg)306 mu_msg_body_view_set_message_source (MuMsgBodyView *self, MuMsg *msg)
307 {
308 const gchar *path;
309 gchar *data;
310
311 g_return_if_fail (MU_IS_MSG_BODY_VIEW(self));
312 g_return_if_fail (msg);
313
314 set_message (self, NULL);
315
316 path = msg ? mu_msg_get_path (msg) : NULL;
317
318 if (path && g_file_get_contents (path, &data, NULL, NULL)) {
319 set_text (self, data);
320 g_free (data);
321 } else
322 set_text (self, "");
323
324 self->_priv->_view_mode = VIEW_MODE_SOURCE;
325 }
326
327
328
329 void
mu_msg_body_view_set_note(MuMsgBodyView * self,const gchar * html)330 mu_msg_body_view_set_note (MuMsgBodyView *self, const gchar *html)
331 {
332 g_return_if_fail (self);
333 g_return_if_fail (html);
334
335 set_message (self, NULL);
336
337 set_html (self, html);
338
339 self->_priv->_view_mode = VIEW_MODE_NOTE;
340 }
341