1 /*
2  * e-stock-request.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include <gtk/gtk.h>
24 #include <libsoup/soup.h>
25 
26 #include <libedataserver/libedataserver.h>
27 
28 #include "e-misc-utils.h"
29 #include "e-stock-request.h"
30 
31 struct _EStockRequestPrivate {
32 	gint scale_factor;
33 };
34 
35 enum {
36 	PROP_0,
37 	PROP_SCALE_FACTOR
38 };
39 
40 static void e_stock_request_content_request_init (EContentRequestInterface *iface);
41 
G_DEFINE_TYPE_WITH_CODE(EStockRequest,e_stock_request,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (E_TYPE_CONTENT_REQUEST,e_stock_request_content_request_init))42 G_DEFINE_TYPE_WITH_CODE (EStockRequest, e_stock_request, G_TYPE_OBJECT,
43 	G_IMPLEMENT_INTERFACE (E_TYPE_CONTENT_REQUEST, e_stock_request_content_request_init))
44 
45 static gboolean
46 e_stock_request_can_process_uri (EContentRequest *request,
47 				 const gchar *uri)
48 {
49 	g_return_val_if_fail (E_IS_STOCK_REQUEST (request), FALSE);
50 	g_return_val_if_fail (uri != NULL, FALSE);
51 
52 	return g_ascii_strncasecmp (uri, "gtk-stock:", 10) == 0;
53 }
54 
55 typedef struct _StockIdleData
56 {
57 	EContentRequest *request;
58 	const gchar *uri;
59 	GObject *requester;
60 	GInputStream **out_stream;
61 	gint64 *out_stream_length;
62 	gchar **out_mime_type;
63 	GCancellable *cancellable;
64 	GError **error;
65 
66 	gboolean success;
67 	EFlag *flag;
68 } StockIdleData;
69 
70 static gboolean
process_stock_request_idle_cb(gpointer user_data)71 process_stock_request_idle_cb (gpointer user_data)
72 {
73 	StockIdleData *sid = user_data;
74 	SoupURI *suri;
75 	GHashTable *query = NULL;
76 	GtkStyleContext *context;
77 	GtkWidgetPath *path;
78 	GtkIconSet *icon_set;
79 	gssize size = GTK_ICON_SIZE_BUTTON;
80 	gchar *buffer = NULL, *mime_type = NULL;
81 	gsize buff_len = 0;
82 	GError *local_error = NULL;
83 
84 	g_return_val_if_fail (sid != NULL, FALSE);
85 	g_return_val_if_fail (E_IS_STOCK_REQUEST (sid->request), FALSE);
86 	g_return_val_if_fail (sid->uri != NULL, FALSE);
87 	g_return_val_if_fail (sid->flag != NULL, FALSE);
88 
89 	if (g_cancellable_set_error_if_cancelled (sid->cancellable, sid->error)) {
90 		sid->success = FALSE;
91 		e_flag_set (sid->flag);
92 
93 		return FALSE;
94 	}
95 
96 	suri = soup_uri_new (sid->uri);
97 	g_return_val_if_fail (suri != NULL, FALSE);
98 
99 	if (suri->query != NULL)
100 		query = soup_form_decode (suri->query);
101 
102 	if (query != NULL) {
103 		const gchar *value;
104 
105 		value = g_hash_table_lookup (query, "size");
106 		if (value)
107 			size = atoi (value);
108 
109 		g_hash_table_destroy (query);
110 	}
111 
112 	/* Try style context first */
113 	context = gtk_style_context_new ();
114 	path = gtk_widget_path_new ();
115 	gtk_widget_path_append_type (path, GTK_TYPE_WINDOW);
116 	gtk_widget_path_append_type (path, GTK_TYPE_BUTTON);
117 	gtk_style_context_set_path (context, path);
118 	gtk_widget_path_free (path);
119 
120 	icon_set = gtk_style_context_lookup_icon_set (context, suri->host);
121 	if (icon_set != NULL) {
122 		GdkPixbuf *pixbuf;
123 
124 		pixbuf = gtk_icon_set_render_icon_pixbuf (
125 			icon_set, context, size);
126 		gdk_pixbuf_save_to_buffer (
127 			pixbuf, &buffer, &buff_len,
128 			"png", &local_error, NULL);
129 		g_object_unref (pixbuf);
130 
131 	/* Fallback to icon theme */
132 	} else {
133 		GtkIconTheme *icon_theme;
134 		GtkIconInfo *icon_info;
135 		const gchar *filename;
136 		gint icon_width, icon_height, scale_factor;
137 
138 		scale_factor = e_stock_request_get_scale_factor (E_STOCK_REQUEST (sid->request));
139 
140 		if (scale_factor < 1)
141 			scale_factor = 1;
142 
143 		if (!gtk_icon_size_lookup (size, &icon_width, &icon_height)) {
144 			icon_width = size;
145 			icon_height = size;
146 		}
147 
148 		size = MAX (icon_width, icon_height) * scale_factor;
149 
150 		icon_theme = gtk_icon_theme_get_default ();
151 
152 		icon_info = gtk_icon_theme_lookup_icon (
153 			icon_theme, suri->host, size,
154 			GTK_ICON_LOOKUP_USE_BUILTIN);
155 
156 		/* Some icons can be missing in the theme */
157 		if (icon_info) {
158 			filename = gtk_icon_info_get_filename (icon_info);
159 			if (filename != NULL) {
160 				if (!g_file_get_contents (
161 					filename, &buffer, &buff_len, &local_error)) {
162 					buffer = NULL;
163 					buff_len = 0;
164 				}
165 				mime_type = g_content_type_guess (filename, NULL, 0, NULL);
166 			} else {
167 				GdkPixbuf *pixbuf;
168 
169 				pixbuf = gtk_icon_info_get_builtin_pixbuf (icon_info);
170 				if (pixbuf != NULL) {
171 					gdk_pixbuf_save_to_buffer (
172 						pixbuf, &buffer, &buff_len,
173 						"png", &local_error, NULL);
174 					g_object_unref (pixbuf);
175 				}
176 			}
177 
178 			gtk_icon_info_free (icon_info);
179 		} else if (g_strcmp0 (suri->host, "x-evolution-arrow-down") == 0) {
180 			GdkPixbuf *pixbuf;
181 			GdkRGBA rgba;
182 			guchar *data;
183 			gint stride;
184 			cairo_surface_t *surface;
185 			cairo_t *cr;
186 
187 			stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, size);
188 			buff_len = stride * size;
189 			data = g_malloc0 (buff_len);
190 			surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, size, size, stride);
191 
192 			cr = cairo_create (surface);
193 
194 			if (gtk_style_context_lookup_color (context, "color", &rgba))
195 				gdk_cairo_set_source_rgba (cr, &rgba);
196 			else
197 				cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
198 
199 			gtk_render_background (context, cr, 0, 0, size, size);
200 			gtk_render_arrow (context, cr, G_PI, 0, 0, size);
201 
202 			cairo_destroy (cr);
203 
204 			cairo_surface_flush (surface);
205 
206 			pixbuf = gdk_pixbuf_new_from_data (data, GDK_COLORSPACE_RGB, TRUE, 8, size, size, stride, NULL, NULL);
207 			gdk_pixbuf_save_to_buffer (
208 				pixbuf, &buffer, &buff_len,
209 				"png", &local_error, NULL);
210 			g_object_unref (pixbuf);
211 
212 			cairo_surface_destroy (surface);
213 			g_free (data);
214 		}
215 	}
216 
217 	/* Sanity check */
218 	g_warn_if_fail (
219 		((buffer != NULL) && (local_error == NULL)) ||
220 		((buffer == NULL) && (local_error != NULL)));
221 
222 	if (!mime_type)
223 		mime_type = g_strdup ("image/png");
224 
225 	if (buffer != NULL) {
226 		*sid->out_stream = g_memory_input_stream_new_from_data (buffer, buff_len, g_free);;
227 		*sid->out_stream_length = buff_len;
228 		*sid->out_mime_type = mime_type;
229 
230 		sid->success = TRUE;
231 	} else {
232 		g_free (mime_type);
233 
234 		if (local_error)
235 			g_propagate_error (sid->error, local_error);
236 
237 		sid->success = FALSE;
238 	}
239 
240 	soup_uri_free (suri);
241 	g_object_unref (context);
242 
243 	e_flag_set (sid->flag);
244 
245 	return FALSE;
246 }
247 
248 static gboolean
e_stock_request_process_sync(EContentRequest * request,const gchar * uri,GObject * requester,GInputStream ** out_stream,gint64 * out_stream_length,gchar ** out_mime_type,GCancellable * cancellable,GError ** error)249 e_stock_request_process_sync (EContentRequest *request,
250 			      const gchar *uri,
251 			      GObject *requester,
252 			      GInputStream **out_stream,
253 			      gint64 *out_stream_length,
254 			      gchar **out_mime_type,
255 			      GCancellable *cancellable,
256 			      GError **error)
257 {
258 	StockIdleData sid;
259 
260 	g_return_val_if_fail (E_IS_STOCK_REQUEST (request), FALSE);
261 	g_return_val_if_fail (uri != NULL, FALSE);
262 
263 	sid.request = request;
264 	sid.uri = uri;
265 	sid.requester = requester;
266 	sid.out_stream = out_stream;
267 	sid.out_stream_length = out_stream_length;
268 	sid.out_mime_type = out_mime_type;
269 	sid.cancellable = cancellable;
270 	sid.error = error;
271 	sid.flag = e_flag_new ();
272 	sid.success = FALSE;
273 
274 	if (e_util_is_main_thread (NULL)) {
275 		process_stock_request_idle_cb (&sid);
276 	} else {
277 		/* Need to run this operation in an idle callback rather
278 		 * than a worker thread, since we're making all kinds of
279 		 * GdkPixbuf/GTK+ calls. */
280 		g_idle_add_full (
281 			G_PRIORITY_HIGH_IDLE,
282 			process_stock_request_idle_cb,
283 			&sid, NULL);
284 
285 		e_flag_wait (sid.flag);
286 	}
287 
288 	e_flag_free (sid.flag);
289 
290 	return sid.success;
291 }
292 
293 static void
e_stock_request_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)294 e_stock_request_set_property (GObject *object,
295 			      guint property_id,
296 			      const GValue *value,
297 			      GParamSpec *pspec)
298 {
299 	switch (property_id) {
300 		case PROP_SCALE_FACTOR:
301 			e_stock_request_set_scale_factor (
302 				E_STOCK_REQUEST (object),
303 				g_value_get_int (value));
304 			return;
305 	}
306 
307 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
308 }
309 
310 static void
e_stock_request_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)311 e_stock_request_get_property (GObject *object,
312 			      guint property_id,
313 			      GValue *value,
314 			      GParamSpec *pspec)
315 {
316 	switch (property_id) {
317 		case PROP_SCALE_FACTOR:
318 			g_value_set_int (
319 				value,
320 				e_stock_request_get_scale_factor (
321 				E_STOCK_REQUEST (object)));
322 			return;
323 	}
324 
325 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
326 }
327 
328 static void
e_stock_request_content_request_init(EContentRequestInterface * iface)329 e_stock_request_content_request_init (EContentRequestInterface *iface)
330 {
331 	iface->can_process_uri = e_stock_request_can_process_uri;
332 	iface->process_sync = e_stock_request_process_sync;
333 }
334 
335 static void
e_stock_request_class_init(EStockRequestClass * class)336 e_stock_request_class_init (EStockRequestClass *class)
337 {
338 	GObjectClass *object_class;
339 
340 	g_type_class_add_private (class, sizeof (EStockRequestPrivate));
341 
342 	object_class = G_OBJECT_CLASS (class);
343 	object_class->set_property = e_stock_request_set_property;
344 	object_class->get_property = e_stock_request_get_property;
345 
346 	g_object_class_install_property (
347 		object_class,
348 		PROP_SCALE_FACTOR,
349 		g_param_spec_int (
350 			"scale-factor",
351 			"Scale Factor",
352 			NULL,
353 			G_MININT, G_MAXINT, 0,
354 			G_PARAM_READWRITE |
355 			G_PARAM_STATIC_STRINGS));
356 }
357 
358 static void
e_stock_request_init(EStockRequest * request)359 e_stock_request_init (EStockRequest *request)
360 {
361 	request->priv = G_TYPE_INSTANCE_GET_PRIVATE (request, E_TYPE_STOCK_REQUEST, EStockRequestPrivate);
362 	request->priv->scale_factor = 0;
363 }
364 
365 EContentRequest *
e_stock_request_new(void)366 e_stock_request_new (void)
367 {
368 	return g_object_new (E_TYPE_STOCK_REQUEST, NULL);
369 }
370 
371 gint
e_stock_request_get_scale_factor(EStockRequest * stock_request)372 e_stock_request_get_scale_factor (EStockRequest *stock_request)
373 {
374 	g_return_val_if_fail (E_IS_STOCK_REQUEST (stock_request), 0);
375 
376 	return stock_request->priv->scale_factor;
377 }
378 
379 void
e_stock_request_set_scale_factor(EStockRequest * stock_request,gint scale_factor)380 e_stock_request_set_scale_factor (EStockRequest *stock_request,
381 				  gint scale_factor)
382 {
383 	g_return_if_fail (E_IS_STOCK_REQUEST (stock_request));
384 
385 	if (stock_request->priv->scale_factor == scale_factor)
386 		return;
387 
388 	stock_request->priv->scale_factor = scale_factor;
389 
390 	g_object_notify (G_OBJECT (stock_request), "scale-factor");
391 }
392