1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
2  *
3  * Copyright (C) 2005-2006 William Jon McCann <mccann@jhu.edu>
4  * Copyright (C) 2012-2021 MATE Developers
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  *
21  */
22 
23 #include "config.h"
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <string.h>
29 
30 #include <glib.h>
31 #include <gtk/gtk.h>
32 
33 #include "gs-theme-engine.h"
34 #include "gste-slideshow.h"
35 
36 static void     gste_slideshow_finalize   (GObject            *object);
37 
38 struct GSTESlideshowPrivate
39 {
40 	/* Image at full opacity */
41 	cairo_pattern_t *pat1;
42 	/* Image at partial opacity */
43 	cairo_pattern_t *pat2;
44 	/* Alpha of pat2 */
45 	gdouble          alpha2;
46 	/* edges of pat2 */
47 	int              pat2top;
48 	int              pat2bottom;
49 	int              pat2left;
50 	int              pat2right;
51 
52 	/* backbuffer that we do all the alpha drawing into (no round
53 	 * trips to the X server when the server doesn't support drawing
54 	 * pixmaps with alpha?) */
55 	cairo_surface_t *surf;
56 
57 	gint64           fade_ticks;
58 
59 	GThread         *load_thread;
60 	GAsyncQueue     *op_q;
61 	GAsyncQueue     *results_q;
62 
63 	guint           results_pull_id;
64 	guint           update_image_id;
65 
66 	GSList         *filename_list;
67 	char           *images_location;
68 	gboolean        sort_images;
69 	int             window_width;
70 	int             window_height;
71 	PangoColor     *background_color;
72 	gboolean        no_stretch_hint;
73 
74 	guint           timeout_id;
75 
76 	GTimer         *timer;
77 	gboolean        fade_disabled;
78 };
79 
80 enum
81 {
82     PROP_0,
83     PROP_IMAGES_LOCATION,
84     PROP_SORT_IMAGES,
85     PROP_SOLID_BACKGROUND,
86     PROP_NO_STRETCH_HINT
87 };
88 
89 static GObjectClass *parent_class = NULL;
90 
91 G_DEFINE_TYPE_WITH_PRIVATE (GSTESlideshow, gste_slideshow, GS_TYPE_THEME_ENGINE)
92 
93 #define N_FADE_TICKS 10
94 #define MINIMUM_FPS 3.0
95 #define DEFAULT_IMAGES_LOCATION DATADIR "/pixmaps/backgrounds"
96 #define IMAGE_LOAD_TIMEOUT 10000
97 
98 typedef struct _Op
99 {
100 	char          *location;
101 	GSTESlideshow *slideshow;
102 } Op;
103 
104 typedef struct _OpResult
105 {
106 	GdkPixbuf *pixbuf;
107 } OpResult;
108 
109 static gboolean
push_load_image_func(GSTESlideshow * show)110 push_load_image_func (GSTESlideshow *show)
111 {
112 	Op *op;
113 
114 	gs_theme_engine_profile_msg ("Starting a new image load");
115 
116 	op = g_new (Op, 1);
117 
118 	op->location = g_strdup (show->priv->images_location);
119 	op->slideshow = g_object_ref (show);
120 
121 	g_async_queue_push (show->priv->op_q, op);
122 
123 	show->priv->update_image_id = 0;
124 
125 	return FALSE;
126 }
127 
128 static void
start_new_load(GSTESlideshow * show,guint timeout)129 start_new_load (GSTESlideshow *show,
130                 guint          timeout)
131 {
132 	gs_theme_engine_profile_msg ("Scheduling a new image load");
133 
134 	/* queue a new load */
135 	if (show->priv->update_image_id <= 0)
136 	{
137 		show->priv->update_image_id = g_timeout_add_full (G_PRIORITY_LOW, timeout,
138 		                              (GSourceFunc)push_load_image_func,
139 		                              show, NULL);
140 	}
141 }
142 
143 static void
start_fade(GSTESlideshow * show,GdkPixbuf * pixbuf)144 start_fade (GSTESlideshow *show,
145             GdkPixbuf     *pixbuf)
146 {
147 	int      pw;
148 	int      ph;
149 	int      x;
150 	int      y;
151 	cairo_t *cr;
152 	int      window_width;
153 	int      window_height;
154 
155 	gs_theme_engine_profile_start ("start");
156 
157 	window_width = show->priv->window_width;
158 	window_height = show->priv->window_height;
159 
160 	if (show->priv->pat2 != NULL)
161 	{
162 		cairo_pattern_destroy (show->priv->pat2);
163 	}
164 
165 	pw = gdk_pixbuf_get_width (pixbuf);
166 	ph = gdk_pixbuf_get_height (pixbuf);
167 	x = (window_width - pw) / 2;
168 	y = (window_height - ph) / 2;
169 
170 	if (gdk_pixbuf_get_has_alpha (pixbuf) && show->priv->background_color)
171 	{
172 		GdkPixbuf *colored;
173 		guint32    color;
174 
175 		color = (show->priv->background_color->red << 16)
176 		        + (show->priv->background_color->green / 256 << 8)
177 		        + show->priv->background_color->blue / 256;
178 		colored = gdk_pixbuf_composite_color_simple (pixbuf,
179 		          pw, ph,
180 		          GDK_INTERP_BILINEAR,
181 		          255,
182 		          256,
183 		          color,
184 		          color);
185 
186 		gdk_pixbuf_copy_area (colored, 0, 0,
187 		                      gdk_pixbuf_get_width (colored),
188 		                      gdk_pixbuf_get_height (colored),
189 		                      pixbuf, 0, 0);
190 
191 		g_object_unref(colored);
192 	}
193 
194 	cr = cairo_create (show->priv->surf);
195 
196 	/* XXX Handle out of memory? */
197 	gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
198 	show->priv->pat2 = cairo_pattern_reference (cairo_get_source (cr));
199 	show->priv->pat2top = y;
200 	show->priv->pat2bottom = y + ph;
201 	show->priv->pat2left = x;
202 	show->priv->pat2right = x + pw;
203 
204 	cairo_destroy (cr);
205 
206 	show->priv->fade_ticks = 0;
207 	g_timer_start (show->priv->timer);
208 
209 	gs_theme_engine_profile_end ("end");
210 }
211 
212 static void
finish_fade(GSTESlideshow * show)213 finish_fade (GSTESlideshow *show)
214 {
215 	gs_theme_engine_profile_start ("start");
216 
217 	if (show->priv->pat1 != NULL)
218 	{
219 		cairo_pattern_destroy (show->priv->pat1);
220 	}
221 
222 	show->priv->pat1 = show->priv->pat2;
223 	show->priv->pat2 = NULL;
224 
225 	start_new_load (show, IMAGE_LOAD_TIMEOUT);
226 
227 	gs_theme_engine_profile_end ("end");
228 }
229 
230 static void
update_display(GSTESlideshow * show)231 update_display (GSTESlideshow *show)
232 {
233 	int      window_width;
234 	int      window_height;
235 	cairo_t *cr;
236 
237 	gs_theme_engine_profile_start ("start");
238 
239 	cr = cairo_create (show->priv->surf);
240 
241 	gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
242 	                                 &window_width,
243 	                                 &window_height);
244 
245 	if (show->priv->pat2 != NULL)
246 	{
247 		/* fade out areas not covered by the new image */
248 		/* top */
249 		cairo_rectangle (cr, 0, 0, window_width, show->priv->pat2top);
250 		if (show->priv->background_color)
251 		{
252 			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
253 			                       show->priv->background_color->green / 65535.0,
254 			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
255 		}
256 		else
257 		{
258 			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
259 		}
260 		cairo_fill (cr);
261 		/* left (excluding what's covered by top and bottom) */
262 		cairo_rectangle (cr, 0, show->priv->pat2top,
263 		                 show->priv->pat2left,
264 		                 show->priv->pat2bottom - show->priv->pat2top);
265 		if (show->priv->background_color)
266 		{
267 			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
268 			                       show->priv->background_color->green / 65535.0,
269 			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
270 		}
271 		else
272 		{
273 			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
274 		}
275 		cairo_fill (cr);
276 		/* bottom */
277 		cairo_rectangle (cr, 0, show->priv->pat2bottom, window_width,
278 		                 window_height - show->priv->pat2bottom);
279 		if (show->priv->background_color)
280 		{
281 			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
282 			                       show->priv->background_color->green / 65535.0,
283 			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
284 		}
285 		else
286 		{
287 			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
288 		}
289 		cairo_fill (cr);
290 		/* right (excluding what's covered by top and bottom) */
291 		cairo_rectangle (cr, show->priv->pat2right,
292 		                 show->priv->pat2top,
293 		                 window_width - show->priv->pat2right,
294 		                 show->priv->pat2bottom - show->priv->pat2top);
295 		if (show->priv->background_color)
296 		{
297 			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
298 			                       show->priv->background_color->green / 65535.0,
299 			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
300 		}
301 		else
302 		{
303 			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
304 		}
305 		cairo_fill (cr);
306 
307 		gs_theme_engine_profile_start ("paint pattern to surface");
308 		cairo_set_source (cr, show->priv->pat2);
309 
310 		cairo_paint_with_alpha (cr, show->priv->alpha2);
311 		gs_theme_engine_profile_end ("paint pattern to surface");
312 	}
313 	else
314 	{
315 		if (show->priv->pat1 != NULL)
316 		{
317 			cairo_set_source (cr, show->priv->pat1);
318 			cairo_paint (cr);
319 		}
320 	}
321 
322 	cairo_destroy (cr);
323 
324 	gtk_widget_queue_draw (GTK_WIDGET (show));
325 }
326 
327 static gboolean
draw_iter(GSTESlideshow * show)328 draw_iter (GSTESlideshow *show)
329 {
330 	double old_opacity;
331 	double new_opacity;
332 
333 	if (show->priv->pat2 != NULL)
334 	{
335 		gdouble fps;
336 		gdouble elapsed;
337 
338 		if (show->priv->fade_disabled)
339 		{
340 			show->priv->alpha2 = 1.0;
341 			update_display (show);
342 			finish_fade (show);
343 			return TRUE;
344 		}
345 
346 		/* we are in a fade */
347 		show->priv->fade_ticks++;
348 
349 		/*
350 		 * We have currently drawn pat2 with old_opacity, and we
351 		 * want to set alpha2 so that drawing pat2 at alpha2
352 		 * yields it drawn with new_opacity
353 		 *
354 		 * Solving
355 		 *   new_opacity = 1 - (1 - alpha2) * (1 - old_opacity)
356 		 * yields
357 		 *   alpha2 = 1 - (1 - new_opacity) / (1 - old_opacity)
358 		 *
359 		 * XXX This assumes that cairo doesn't correct alpha for
360 		 * the color profile.  However, any error is guaranteed
361 		 * to be cleaned up by the last iteration, where alpha2
362 		 * becomes 1 because new_opacity is 1.
363 		 */
364 		old_opacity = (double) (show->priv->fade_ticks - 1) /
365 		              (double) N_FADE_TICKS;
366 		new_opacity = (double) show->priv->fade_ticks /
367 		              (double) N_FADE_TICKS;
368 		show->priv->alpha2 = 1.0 - (1.0 - new_opacity) /
369 		                     (1.0 - old_opacity);
370 
371 		update_display (show);
372 
373 		elapsed = g_timer_elapsed (show->priv->timer, NULL);
374 		fps = (gdouble)show->priv->fade_ticks / elapsed;
375 		if (fps < MINIMUM_FPS)
376 		{
377 			g_warning ("Getting less than %.2f frames per second, disabling fade", MINIMUM_FPS);
378 			show->priv->fade_ticks = N_FADE_TICKS - 1;
379 			show->priv->fade_disabled = TRUE;
380 		}
381 
382 		if (show->priv->fade_ticks >= N_FADE_TICKS)
383 		{
384 			finish_fade (show);
385 		}
386 	}
387 
388 	return TRUE;
389 }
390 
391 static void
process_new_pixbuf(GSTESlideshow * show,GdkPixbuf * pixbuf)392 process_new_pixbuf (GSTESlideshow *show,
393                     GdkPixbuf     *pixbuf)
394 {
395 	gs_theme_engine_profile_msg ("Processing a new image");
396 
397 	if (pixbuf != NULL)
398 	{
399 		start_fade (show, pixbuf);
400 	}
401 	else
402 	{
403 		start_new_load (show, 10);
404 	}
405 }
406 
407 static void
op_result_free(OpResult * result)408 op_result_free (OpResult *result)
409 {
410 	if (result == NULL)
411 	{
412 		return;
413 	}
414 
415 	if (result->pixbuf != NULL)
416 	{
417 		g_object_unref (result->pixbuf);
418 	}
419 
420 	g_free (result);
421 }
422 
423 static gboolean
results_pull_func(GSTESlideshow * show)424 results_pull_func (GSTESlideshow *show)
425 {
426 	OpResult *result;
427 
428 	g_async_queue_lock (show->priv->results_q);
429 
430 	result = g_async_queue_try_pop_unlocked (show->priv->results_q);
431 	g_assert (result);
432 
433 	while (result != NULL)
434 	{
435 		process_new_pixbuf (show, result->pixbuf);
436 		op_result_free (result);
437 
438 		result = g_async_queue_try_pop_unlocked (show->priv->results_q);
439 	}
440 
441 	show->priv->results_pull_id = 0;
442 
443 	g_async_queue_unlock (show->priv->results_q);
444 
445 	return FALSE;
446 }
447 
448 static GdkPixbuf *
scale_pixbuf(GdkPixbuf * pixbuf,int max_width,int max_height,gboolean no_stretch_hint)449 scale_pixbuf (GdkPixbuf *pixbuf,
450               int        max_width,
451               int        max_height,
452               gboolean   no_stretch_hint)
453 {
454 	int        pw;
455 	int        ph;
456 	float      scale_factor_x = 1.0;
457 	float      scale_factor_y = 1.0;
458 	float      scale_factor = 1.0;
459 
460 	pw = gdk_pixbuf_get_width (pixbuf);
461 	ph = gdk_pixbuf_get_height (pixbuf);
462 
463 	/* If the image is less than 256 wide or high then it
464 	   is probably a thumbnail and we should ignore it */
465 	if (pw < 256 || ph < 256)
466 	{
467 		return NULL;
468 	}
469 
470 	/* Determine which dimension requires the smallest scale. */
471 	scale_factor_x = (float) max_width / (float) pw;
472 	scale_factor_y = (float) max_height / (float) ph;
473 
474 	if (scale_factor_x > scale_factor_y)
475 	{
476 		scale_factor = scale_factor_y;
477 	}
478 	else
479 	{
480 		scale_factor = scale_factor_x;
481 	}
482 
483 	/* always scale down, allow to disable scaling up */
484 	if (scale_factor < 1.0 || !no_stretch_hint)
485 	{
486 		int scale_x;
487 		int scale_y;
488 
489 		scale_x = (int) (pw * scale_factor);
490 		scale_y = (int) (ph * scale_factor);
491 		return gdk_pixbuf_scale_simple (pixbuf,
492 		                                scale_x,
493 		                                scale_y,
494 		                                GDK_INTERP_BILINEAR);
495 	}
496 	else
497 	{
498 		return g_object_ref (pixbuf);
499 	}
500 }
501 
502 static void
add_files_to_list(GSList ** list,const char * base)503 add_files_to_list (GSList    **list,
504                    const char *base)
505 {
506 	GDir       *d;
507 	const char *d_name;
508 
509 	d = g_dir_open (base, 0, NULL);
510 	if (d == NULL)
511 	{
512 		g_warning ("Could not open directory: %s", base);
513 		return;
514 	}
515 
516 	while ((d_name = g_dir_read_name (d)) != NULL)
517 	{
518 		char *path;
519 
520 		/* skip hidden files */
521 		if (d_name[0] == '.')
522 		{
523 			continue;
524 		}
525 
526 		path = g_build_filename (base, d_name, NULL);
527 		if (g_file_test (path, G_FILE_TEST_IS_DIR))
528 		{
529 			add_files_to_list (list, path);
530 			g_free (path);
531 		}
532 		else
533 		{
534 			*list = g_slist_prepend (*list, path);
535 		}
536 	}
537 
538 	g_dir_close (d);
539 }
540 
541 static GSList *
build_filename_list_local_dir(const char * base)542 build_filename_list_local_dir (const char *base)
543 {
544 	GSList *list = NULL;
545 
546 	add_files_to_list (&list, base);
547 
548 	return list;
549 }
550 
551 static int
gste_strcmp_compare_func(gconstpointer string_a,gconstpointer string_b)552 gste_strcmp_compare_func (gconstpointer string_a, gconstpointer string_b)
553 {
554 	return strcmp (string_a == NULL ? "" : string_a,
555 	               string_b == NULL ? "" : string_b);
556 }
557 
558 
559 static GdkPixbuf *
get_pixbuf_from_local_dir(GSTESlideshow * show,const char * location)560 get_pixbuf_from_local_dir (GSTESlideshow *show,
561                            const char    *location)
562 {
563 	GdkPixbuf *pixbuf, *transformed_pixbuf;
564 	char      *filename;
565 	int        i;
566 	GSList    *l;
567 
568 	/* rebuild the cache */
569 	if (show->priv->filename_list == NULL)
570 	{
571 		show->priv->filename_list = build_filename_list_local_dir (location);
572 	}
573 
574 	if (show->priv->filename_list == NULL)
575 	{
576 		return NULL;
577 	}
578 	else
579 	{
580 		if (show->priv->sort_images)
581 		{
582 			show->priv->filename_list = g_slist_sort (show->priv->filename_list, gste_strcmp_compare_func);
583 		}
584 	}
585 
586 	/* get a random filename if needed */
587 	if (! show->priv->sort_images)
588 	{
589 		i = g_random_int_range (0, g_slist_length (show->priv->filename_list));
590 		l = g_slist_nth (show->priv->filename_list, i);
591 	}
592 	else
593 	{
594 		l = show->priv->filename_list;
595 	}
596 	filename = l->data;
597 
598 	pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
599 
600 	if (pixbuf != NULL)
601 	{
602 		transformed_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
603 		g_object_unref (pixbuf);
604 	}
605 	else
606 	{
607 		transformed_pixbuf = NULL;
608 	}
609 
610 	g_free (filename);
611 	show->priv->filename_list = g_slist_delete_link (show->priv->filename_list, l);
612 
613 	return transformed_pixbuf;
614 }
615 
616 static GdkPixbuf *
get_pixbuf_from_location(GSTESlideshow * show,const char * location)617 get_pixbuf_from_location (GSTESlideshow *show,
618                           const char    *location)
619 {
620 	GdkPixbuf *pixbuf = NULL;
621 	gboolean   is_dir;
622 
623 	if (location == NULL)
624 	{
625 		return NULL;
626 	}
627 
628 	is_dir = g_file_test (location, G_FILE_TEST_IS_DIR);
629 
630 	if (is_dir)
631 	{
632 		pixbuf = get_pixbuf_from_local_dir (show, location);
633 	}
634 
635 	return pixbuf;
636 }
637 
638 static GdkPixbuf *
get_pixbuf(GSTESlideshow * show,const char * location,int width,int height)639 get_pixbuf (GSTESlideshow *show,
640             const char    *location,
641             int            width,
642             int            height)
643 {
644 	GdkPixbuf *pixbuf;
645 	GdkPixbuf *scaled = NULL;
646 
647 	if (location == NULL)
648 	{
649 		return NULL;
650 	}
651 
652 	pixbuf = get_pixbuf_from_location (show, location);
653 
654 	if (pixbuf != NULL)
655 	{
656 		scaled = scale_pixbuf (pixbuf, width, height, show->priv->no_stretch_hint);
657 		g_object_unref (pixbuf);
658 	}
659 
660 	return scaled;
661 }
662 
663 static void
op_load_image(GSTESlideshow * show,const char * location)664 op_load_image (GSTESlideshow *show,
665                const char    *location)
666 {
667 	OpResult *op_result;
668 	int       window_width;
669 	int       window_height;
670 
671 	window_width = show->priv->window_width;
672 	window_height = show->priv->window_height;
673 
674 	op_result = g_new0 (OpResult, 1);
675 
676 	op_result->pixbuf = get_pixbuf (show,
677 	                                location,
678 	                                window_width,
679 	                                window_height);
680 
681 	g_async_queue_lock (show->priv->results_q);
682 	g_async_queue_push_unlocked (show->priv->results_q, op_result);
683 
684 	if (show->priv->results_pull_id == 0)
685 	{
686 		show->priv->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
687 		                              (GSourceFunc)results_pull_func,
688 		                              show, NULL);
689 	}
690 
691 	g_async_queue_unlock (show->priv->results_q);
692 }
693 
694 static gpointer
load_threadfunc(GAsyncQueue * op_q)695 load_threadfunc (GAsyncQueue *op_q)
696 {
697 	Op *op;
698 
699 	op = g_async_queue_pop (op_q);
700 	while (op)
701 	{
702 		op_load_image (op->slideshow,
703 		               op->location);
704 
705 		if (op->slideshow != NULL)
706 		{
707 			g_object_unref (op->slideshow);
708 		}
709 		g_free (op->location);
710 		g_free (op);
711 
712 		op = g_async_queue_pop (op_q);
713 	}
714 
715 	return NULL;
716 }
717 
718 void
gste_slideshow_set_images_location(GSTESlideshow * show,const char * location)719 gste_slideshow_set_images_location (GSTESlideshow *show,
720                                     const char    *location)
721 {
722 	g_return_if_fail (GSTE_IS_SLIDESHOW (show));
723 
724 	g_free (show->priv->images_location);
725 	show->priv->images_location = g_strdup (location);
726 }
727 
728 
729 void
gste_slideshow_set_sort_images(GSTESlideshow * show,gboolean sort_images)730 gste_slideshow_set_sort_images (GSTESlideshow *show,
731                                 gboolean       sort_images)
732 {
733 	g_return_if_fail (GSTE_IS_SLIDESHOW (show));
734 
735 	show->priv->sort_images = sort_images;
736 }
737 
738 void
gste_slideshow_set_no_stretch_hint(GSTESlideshow * show,gboolean no_stretch_hint)739 gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
740                                     gboolean       no_stretch_hint)
741 {
742 	g_return_if_fail (GSTE_IS_SLIDESHOW (show));
743 
744 	show->priv->no_stretch_hint = no_stretch_hint;
745 }
746 
747 void
gste_slideshow_set_background_color(GSTESlideshow * show,const char * background_color)748 gste_slideshow_set_background_color (GSTESlideshow *show,
749                                      const char    *background_color)
750 {
751 	g_return_if_fail (GSTE_IS_SLIDESHOW (show));
752 
753 	if (show->priv->background_color != NULL)
754 	{
755 		g_slice_free (PangoColor, show->priv->background_color);
756 		show->priv->background_color = NULL;
757 	}
758 
759 	if (background_color != NULL)
760 	{
761 		show->priv->background_color = g_slice_new (PangoColor);
762 
763 		if (pango_color_parse (show->priv->background_color, background_color) == FALSE)
764 		{
765 			g_slice_free (PangoColor, show->priv->background_color);
766 			show->priv->background_color = NULL;
767 		}
768 	}
769 }
770 
771 static void
gste_slideshow_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)772 gste_slideshow_set_property (GObject            *object,
773                              guint               prop_id,
774                              const GValue       *value,
775                              GParamSpec         *pspec)
776 {
777 	GSTESlideshow *self;
778 
779 	self = GSTE_SLIDESHOW (object);
780 
781 	switch (prop_id)
782 	{
783 	case PROP_IMAGES_LOCATION:
784 		gste_slideshow_set_images_location (self, g_value_get_string (value));
785 		break;
786 	case PROP_SORT_IMAGES:
787 		gste_slideshow_set_sort_images (self, g_value_get_boolean (value));
788 		break;
789 	case PROP_SOLID_BACKGROUND:
790 		gste_slideshow_set_background_color (self, g_value_get_string (value));
791 		break;
792 	case PROP_NO_STRETCH_HINT:
793 		gste_slideshow_set_no_stretch_hint (self, g_value_get_boolean (value));
794 		break;
795 	default:
796 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
797 		break;
798 	}
799 }
800 
801 static void
gste_slideshow_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)802 gste_slideshow_get_property (GObject            *object,
803                              guint               prop_id,
804                              GValue             *value,
805                              GParamSpec         *pspec)
806 {
807 	GSTESlideshow *self;
808 
809 	self = GSTE_SLIDESHOW (object);
810 
811 	switch (prop_id)
812 	{
813 	case PROP_IMAGES_LOCATION:
814 		g_value_set_string (value, self->priv->images_location);
815 		break;
816 	case PROP_SORT_IMAGES:
817 		g_value_set_boolean (value, self->priv->sort_images);
818 		break;
819 	case PROP_SOLID_BACKGROUND:
820 	{
821 		char *color = NULL;
822 		color = pango_color_to_string (self->priv->background_color);
823 		g_value_set_string (value, color);
824 		g_free (color);
825 		break;
826 	}
827 	case PROP_NO_STRETCH_HINT:
828 		g_value_set_boolean (value, self->priv->no_stretch_hint);
829 		break;
830 	default:
831 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
832 		break;
833 	}
834 }
835 
836 static void
gste_slideshow_real_show(GtkWidget * widget)837 gste_slideshow_real_show (GtkWidget *widget)
838 {
839 	GSTESlideshow *show = GSTE_SLIDESHOW (widget);
840 	int            delay;
841 
842 	if (GTK_WIDGET_CLASS (parent_class)->show)
843 	{
844 		GTK_WIDGET_CLASS (parent_class)->show (widget);
845 	}
846 
847 	start_new_load (show, 10);
848 
849 	delay = 25;
850 	show->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, show);
851 
852 	if (show->priv->timer != NULL)
853 	{
854 		g_timer_destroy (show->priv->timer);
855 	}
856 	show->priv->timer = g_timer_new ();
857 }
858 
859 static gboolean
gste_slideshow_real_draw(GtkWidget * widget,cairo_t * cr)860 gste_slideshow_real_draw (GtkWidget *widget,
861                           cairo_t   *cr)
862 {
863 	GSTESlideshow *show = GSTE_SLIDESHOW (widget);
864 
865 	if (GTK_WIDGET_CLASS (parent_class)->draw) {
866 		GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
867 	}
868 
869 	cairo_set_source_surface (cr, show->priv->surf, 0, 0);
870 
871 	gs_theme_engine_profile_start ("paint surface to window");
872 	cairo_paint (cr);
873 	gs_theme_engine_profile_end ("paint surface to window");
874 
875 	return TRUE;
876 }
877 
878 static gboolean
gste_slideshow_real_configure(GtkWidget * widget,GdkEventConfigure * event)879 gste_slideshow_real_configure (GtkWidget         *widget,
880                                GdkEventConfigure *event)
881 {
882 	GSTESlideshow *show = GSTE_SLIDESHOW (widget);
883 	gboolean       handled = FALSE;
884 	cairo_t       *cr;
885 
886 	/* resize */
887 	gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
888 	                                 &show->priv->window_width,
889 	                                 &show->priv->window_height);
890 
891 	gs_theme_engine_profile_msg ("Resize to x:%d y:%d",
892 	                             show->priv->window_width,
893 	                             show->priv->window_height);
894 
895 	if (show->priv->surf != NULL)
896 	{
897 		cairo_surface_destroy (show->priv->surf);
898 	}
899 
900 	cr = gdk_cairo_create (gtk_widget_get_window (widget));
901 	show->priv->surf = cairo_surface_create_similar (cairo_get_target (cr),
902 	                   CAIRO_CONTENT_COLOR,
903 	                   show->priv->window_width,
904 	                   show->priv->window_height);
905 	cairo_destroy (cr);
906 
907 	/* schedule a redraw */
908 	gtk_widget_queue_draw (widget);
909 
910 	if (GTK_WIDGET_CLASS (parent_class)->configure_event)
911 	{
912 		handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
913 	}
914 
915 	return handled;
916 }
917 
918 static void
gste_slideshow_class_init(GSTESlideshowClass * klass)919 gste_slideshow_class_init (GSTESlideshowClass *klass)
920 {
921 	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
922 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
923 
924 	parent_class = g_type_class_peek_parent (klass);
925 
926 	object_class->finalize = gste_slideshow_finalize;
927 	object_class->get_property = gste_slideshow_get_property;
928 	object_class->set_property = gste_slideshow_set_property;
929 
930 	widget_class->show = gste_slideshow_real_show;
931 	widget_class->draw = gste_slideshow_real_draw;
932 	widget_class->configure_event = gste_slideshow_real_configure;
933 
934 	g_object_class_install_property (object_class,
935 	                                 PROP_IMAGES_LOCATION,
936 	                                 g_param_spec_string ("images-location",
937 	                                         NULL,
938 	                                         NULL,
939 	                                         NULL,
940 	                                         G_PARAM_READWRITE));
941 	g_object_class_install_property (object_class,
942 	                                 PROP_SORT_IMAGES,
943 	                                 g_param_spec_boolean ("sort-images",
944 	                                         NULL,
945 	                                         NULL,
946 	                                         FALSE,
947 	                                         G_PARAM_READWRITE));
948 	g_object_class_install_property (object_class,
949 	                                 PROP_SOLID_BACKGROUND,
950 	                                 g_param_spec_string ("background-color",
951 	                                         NULL,
952 	                                         NULL,
953 	                                         NULL,
954 	                                         G_PARAM_READWRITE));
955 	g_object_class_install_property (object_class,
956 	                                 PROP_NO_STRETCH_HINT,
957 	                                 g_param_spec_boolean ("no-stretch",
958 	                                         NULL,
959 	                                         NULL,
960 	                                         FALSE,
961 	                                         G_PARAM_READWRITE));
962 }
963 
964 static void
set_visual(GtkWidget * widget)965 set_visual (GtkWidget *widget)
966 {
967 	GdkScreen *screen;
968 	GdkVisual *visual;
969 
970 	screen = gtk_widget_get_screen (widget);
971 	visual = gdk_screen_get_rgba_visual (screen);
972 	if (visual == NULL)
973 	{
974 		visual = gdk_screen_get_system_visual (screen);
975 	}
976 
977 	gtk_widget_set_visual (widget, visual);
978 }
979 
980 static void
gste_slideshow_init(GSTESlideshow * show)981 gste_slideshow_init (GSTESlideshow *show)
982 {
983 	show->priv = gste_slideshow_get_instance_private (show);
984 
985 	show->priv->images_location = g_strdup (DEFAULT_IMAGES_LOCATION);
986 
987 	show->priv->op_q = g_async_queue_new ();
988 	show->priv->results_q = g_async_queue_new ();
989 
990 	g_thread_new ("loadthread", (GThreadFunc)load_threadfunc, show->priv->op_q);
991 
992 	set_visual (GTK_WIDGET (show));
993 }
994 
995 static void
gste_slideshow_finalize(GObject * object)996 gste_slideshow_finalize (GObject *object)
997 {
998 	GSTESlideshow *show;
999 	gpointer       result;
1000 
1001 	g_return_if_fail (object != NULL);
1002 	g_return_if_fail (GSTE_IS_SLIDESHOW (object));
1003 
1004 	show = GSTE_SLIDESHOW (object);
1005 
1006 	g_return_if_fail (show->priv != NULL);
1007 
1008 	if (show->priv->surf)
1009 	{
1010 		cairo_surface_destroy (show->priv->surf);
1011 	}
1012 
1013 	if (show->priv->timeout_id > 0)
1014 	{
1015 		g_source_remove (show->priv->timeout_id);
1016 		show->priv->timeout_id = 0;
1017 	}
1018 
1019 	if (show->priv->results_pull_id > 0)
1020 	{
1021 		g_source_remove (show->priv->results_pull_id);
1022 		show->priv->results_pull_id = 0;
1023 	}
1024 
1025 	if (show->priv->results_q != NULL)
1026 	{
1027 		result = g_async_queue_try_pop (show->priv->results_q);
1028 
1029 		while (result)
1030 		{
1031 			result = g_async_queue_try_pop (show->priv->results_q);
1032 		}
1033 		g_async_queue_unref (show->priv->results_q);
1034 	}
1035 
1036 	g_free (show->priv->images_location);
1037 	show->priv->images_location = NULL;
1038 
1039 	if (show->priv->background_color)
1040 	{
1041 		g_slice_free (PangoColor, show->priv->background_color);
1042 		show->priv->background_color = NULL;
1043 	}
1044 
1045 	if (show->priv->timer != NULL)
1046 	{
1047 		g_timer_destroy (show->priv->timer);
1048 	}
1049 
1050 	G_OBJECT_CLASS (parent_class)->finalize (object);
1051 }
1052