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