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