2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@yahoo.fr>
21 /****************************
22 * Which image to load next *
23 ****************************/
25 #include <sys/types.h> /* off_t */
26 #include <sys/stat.h> /* struct stat, stat() */
27 #include <time.h> /* time_t */
28 #include <stdio.h> /* perror() */
29 #include <string.h> /* memcmp() */
30 #include <stdlib.h> /* random() */
33 #include "next_image.h"
35 #include "gliv-image.h"
36 #include "rendering.h"
38 #include "files_list.h"
42 #include "callbacks.h"
43 #include "transition.h"
45 #include "tree_browser.h"
46 #include "images_menus.h"
49 extern options_struct
*options
;
50 extern GlivImage
*current_image
;
53 * We can have previous_image == next_image.
54 * Thanks to ref counting, we don't care.
56 static GlivImage
*previous_image
= NULL
;
57 static GlivImage
*next_image
= NULL
;
59 static GtkMenuItem
*slide_show_menu
;
60 static guint slide_show_timer
= 0;
61 static gint slide_show_direction
= 1;
63 static void forget_image(GlivImage
** im_ptr
)
65 if (*im_ptr
!= NULL
) {
66 g_object_unref(*im_ptr
);
72 * Once the images list has been reordered we must take care
73 * of the current image position and destroy the previous and
74 * next images since they changed.
76 void after_reorder(void)
78 if (previous_image
!= NULL
&&
79 current_image
->node
->prev
!= previous_image
->node
) {
81 forget_image(&previous_image
);
84 if (next_image
!= NULL
&& current_image
->node
->next
!= next_image
->node
) {
85 forget_image(&next_image
);
88 update_current_image_status(TRUE
);
91 /*** Last image notice ***/
99 static notice_t notice
= NOTICE_NOTHING
;
100 static guint notice_timer
= 0;
102 /* Called by the timer. */
103 static gboolean
hide_image_notice(void)
105 gboolean need_refresh
;
107 need_refresh
= notice
!= NOTICE_NOTHING
;
108 notice
= NOTICE_NOTHING
;
111 refresh(REFRESH_IMAGE
);
117 static void image_notice(gboolean last_image
)
120 gboolean need_refresh
;
122 if (options
->notice_time
<= 0)
125 new_notice
= last_image
? NOTICE_LAST
: NOTICE_FIRST
;
126 need_refresh
= new_notice
!= notice
;
129 if (notice_timer
!= 0)
130 g_source_remove(notice_timer
);
132 notice_timer
= g_timeout_add(options
->notice_time
,
133 (GSourceFunc
) hide_image_notice
, NULL
);
136 refresh(REFRESH_IMAGE
);
139 const gchar
*get_image_notice(void)
143 return _("First image");
146 return _("Last image");
153 /*** Switching images ***/
155 static void slide_show_advance(void);
157 static void render_next_image(GlivImage
* im
)
159 gboolean first_image
= current_image
== NULL
;
160 gboolean make_transition
= options
->transitions
&&
161 options
->trans_time
> 0 && current_image
!= NULL
&& im
!= NULL
;
163 if (options
->fullscreen
== FALSE
) {
165 goto_window(im
, FALSE
);
167 * This seems to work to make sure the window is actually resized and
168 * the corresponding events have been handled.
170 for (i
= 0; i
< 2; i
++) {
171 gtk_widget_queue_draw(GTK_WIDGET(get_current_window()));
172 gdk_window_process_all_updates();
181 if (current_image
!= NULL
) {
182 prioritize_textures(current_image
, FALSE
);
183 g_object_unref(current_image
);
190 if (im
->node
->next
== NULL
)
193 else if (im
->node
->prev
== NULL
&& first_image
== FALSE
)
196 if (slide_show_started())
197 slide_show_advance();
199 highlight_current_image();
202 /*** Next/previous image ***/
204 static GList
*next_node(GList
* node
, gint dir
)
207 return (dir
== 1) ? get_list_head() : get_list_end();
216 static void show_errors(GSList
* errors
)
220 GtkTextBuffer
*buffer
;
221 GtkScrolledWindow
*scroll
;
223 /* Create the widgets */
226 GTK_DIALOG(gtk_dialog_new_with_buttons(_("Loading errors"),
227 get_current_window(),
228 GTK_DIALOG_DESTROY_WITH_PARENT
,
230 GTK_RESPONSE_NONE
, NULL
));
232 text
= GTK_TEXT_VIEW(gtk_text_view_new());
233 gtk_text_view_set_wrap_mode(text
, GTK_WRAP_WORD_CHAR
);
234 gtk_text_view_set_editable(text
, FALSE
);
235 gtk_text_view_set_cursor_visible(text
, FALSE
);
237 buffer
= gtk_text_view_get_buffer(text
);
238 gtk_text_buffer_insert_at_cursor(buffer
,
239 _("The following errors occurred"
240 " while loading the next image:\n"), -1);
243 gtk_text_buffer_insert_at_cursor(buffer
, errors
->data
, -1);
244 gtk_text_buffer_insert_at_cursor(buffer
, "\n", -1);
245 errors
= errors
->next
;
248 scroll
= GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL
, NULL
));
249 gtk_container_add(GTK_CONTAINER(scroll
), GTK_WIDGET(text
));
251 gtk_container_add(GTK_CONTAINER(dialog
->vbox
), GTK_WIDGET(scroll
));
252 gtk_widget_show_all(GTK_WIDGET(scroll
));
253 gtk_window_set_default_size(GTK_WINDOW(dialog
), 600, 400);
254 run_modal_dialog(dialog
);
255 gtk_widget_destroy(GTK_WIDGET(dialog
));
258 static GlivImage
*load_next_node(GList
** node
, gint dir
)
260 GlivImage
*im
= NULL
;
263 GSList
*failed
= NULL
;
266 while ((next
= next_node(*node
, dir
)) != NULL
) {
267 gchar
*filename
= next
->data
;
270 im
= load_file(filename
, &error
);
274 /* Delete bad images from the list. */
275 remove_from_list(next
);
277 if (++nb_errors
< 1024) {
279 g_strdup(error
->message
) :
280 g_strdup_printf(_("Cannot load %s"), filename
);
281 failed
= g_slist_prepend(failed
, msg
);
283 g_clear_error(&error
);
286 if (error
!= NULL
&& error
->message
!= NULL
)
287 g_printerr("%s\n", error
->message
);
292 if (nb_errors
== 1) {
293 gchar
*filename
= failed
->data
;
294 DIALOG_MSG("%s", filename
);
295 } else if (nb_errors
> 1) {
299 failed
= g_slist_reverse(failed
);
301 for (ptr
= failed
; ptr
!= NULL
; ptr
= ptr
->next
)
304 g_slist_free(failed
);
310 static GlivImage
*load_in_direction(gint dir
)
312 GlivImage
*im
= NULL
;
315 timestamp_t before_load
= get_list_timestamp();
317 if (get_list_length() == 2 && options
->loop
) {
319 * When there are only two images, and we are looping,
320 * next_image and previous_image are equal.
323 if (previous_image
!= NULL
) {
324 g_object_ref(previous_image
);
325 return previous_image
;
328 if (next_image
!= NULL
) {
329 g_object_ref(next_image
);
334 if (current_image
== NULL
) {
339 node
= current_image
->node
;
340 id
= get_image_number(current_image
) + dir
;
343 im
= load_next_node(&node
, dir
);
345 if (im
== NULL
&& options
->loop
&& get_list_length() > 1) {
346 /* Loop at the end. */
348 id
= (dir
== 1) ? 0 : (get_list_length() - 1);
349 im
= load_next_node(&node
, dir
);
355 } else if (get_list_head() == NULL
&& current_image
)
356 /* No more images, not even the current one */
357 current_image
->node
= NULL
;
359 if (before_load
!= get_list_timestamp())
360 update_current_image_status(TRUE
);
365 /* Called when switching images. */
366 static void destroy_unused(GlivImage
** backup
)
368 if (*backup
!= NULL
) {
369 g_object_unref(*backup
);
373 *backup
= current_image
;
374 g_object_ref(*backup
);
377 static void stop_slide_show(void);
379 /* Return FALSE if there are no more images. */
380 void load_direction(gint dir
)
386 /* We are not reentrant */
387 static gboolean in_load_direction
= FALSE
;
388 static gint next_direction
= 0;
390 if (in_load_direction
) {
391 /* We delay the reentrance */
392 next_direction
= dir
;
396 if (currently_loading() || current_image
== NULL
)
401 backup
= &next_image
;
402 next
= &previous_image
;
403 } else if (dir
== 1) {
404 backup
= &previous_image
;
409 in_load_direction
= TRUE
;
412 /* Be sure there is a next image. */
414 *next
= load_in_direction(dir
);
416 /* There is no next image to load. */
417 image_notice(dir
== 1);
423 destroy_unused(backup
);
427 render_next_image(im
);
430 if (options
->one_image
) {
431 g_object_unref(*backup
);
434 *next
= load_in_direction(dir
);
436 slide_show_direction
= dir
;
438 in_load_direction
= FALSE
;
439 dir
= next_direction
;
443 /* Load a randomly selected image from the list. */
444 void load_random_image()
447 const gchar
*filename
;
449 nr
= random() % get_list_length();
450 filename
= get_nth_filename(nr
);
454 /* Load the first image from the list. */
455 void load_1st_image()
457 const gchar
*filename
;
459 filename
= get_nth_filename(0);
463 /* Load the last image in the list. */
464 void load_last_image()
466 const gchar
*filename
;
468 filename
= get_nth_filename(get_list_length() - 1);
473 * After many images are opened we want to load
474 * the next image but not the preloaded one.
475 * Maybe the image after the one we will load does
478 static void open_next_image(gboolean keep_next
)
481 GlivImage
*future_image
;
483 if (currently_loading())
487 forget_image(&previous_image
);
491 /* load_in_direction() would be too smart when looping with 2 images */
492 future_image
= next_image
;
495 next
= load_in_direction(1);
499 if (current_image
!= NULL
) {
500 previous_image
= current_image
;
501 g_object_ref(previous_image
);
504 next_image
= future_image
;
506 render_next_image(next
);
507 g_object_unref(next
);
509 if (options
->one_image
== FALSE
&& next_image
== NULL
)
510 next_image
= load_in_direction(1);
513 /*** First/second image ***/
515 static void load_second_image(void)
517 if (next_image
== NULL
&& options
->one_image
== FALSE
)
518 next_image
= load_in_direction(1);
520 if (options
->start_show
)
523 if (options
->build_menus
)
524 do_later(G_PRIORITY_LOW
, (GSourceDummyMarshal
) rebuild_images_menus
);
527 void load_first_image(void)
529 GlivImage
*first_image
;
531 install_segv_handler();
533 if (get_list_head() == NULL
) {
534 if (options
->build_menus
)
535 /* Initialize the menus' timestamps for cond_rebuild to work */
536 rebuild_images_menus();
540 first_image
= load_in_direction(1);
541 if (first_image
== NULL
)
542 DIALOG_MSG(_("No image found"));
544 render_next_image(first_image
);
546 if (first_image
!= NULL
) {
547 if (options
->fullscreen
== FALSE
&& options
->resize_win
== FALSE
)
548 goto_window(first_image
, TRUE
);
550 g_object_unref(first_image
);
552 } else if (options
->fullscreen
== FALSE
)
553 goto_window(NULL
, TRUE
);
555 do_later(G_PRIORITY_LOW
, load_second_image
);
560 * Used for example, when an option is changed such as dithering or mipmap.
563 void reload_current_image(void)
567 if (currently_loading() || current_image
== NULL
||
568 current_image
->node
== NULL
)
571 new = load_file(current_image
->node
->data
, NULL
);
575 new->node
= current_image
->node
;
576 new->number
= current_image
->number
;
578 g_object_unref(current_image
);
581 prioritize_textures(current_image
, TRUE
);
582 refresh(REFRESH_NOW
);
583 update_current_image_status(FALSE
);
586 void reload_images(void)
588 if (currently_loading())
591 forget_image(&previous_image
);
592 forget_image(&next_image
);
593 reload_current_image();
603 struct image_stat previous
;
604 struct image_stat current
;
605 struct image_stat next
;
608 static void stat_image(GlivImage
* im
, struct image_stat
*st
, gboolean quiet
)
612 if (im
!= NULL
&& im
->node
!= NULL
) {
613 struct stat real_stat
;
615 if (stat(im
->node
->data
, &real_stat
) < 0) {
617 perror(im
->node
->data
);
621 st
->size
= real_stat
.st_size
;
622 st
->ctime
= real_stat
.st_ctime
;
627 gpointer
stat_loaded_files(gboolean quiet
)
629 struct image_stat3
*stat_data
= g_new(struct image_stat3
, 1);
631 stat_image(previous_image
, &stat_data
->previous
, quiet
);
632 stat_image(current_image
, &stat_data
->current
, quiet
);
633 stat_image(next_image
, &stat_data
->next
, quiet
);
638 #define DIFFERENT(name) \
639 memcmp(&before->name, &now->name, sizeof(struct image_stat))
641 void reload_changed_files(gpointer
* stat_data
)
643 struct image_stat3
*before
= (struct image_stat3
*) stat_data
;
644 struct image_stat3
*now
= stat_loaded_files(TRUE
);
646 if (DIFFERENT(previous
))
647 forget_image(&previous_image
);
650 forget_image(&next_image
);
652 if (now
->current
.present
&& DIFFERENT(current
))
653 reload_current_image();
662 /* Check if we can optimize the loading asked by the images menu. */
663 static gboolean
check_direction(const gchar
* filename
, gint dir
)
665 GlivImage
**prev
, **next
;
669 prev
= &previous_image
;
672 next_node
= current_image
->node
->next
;
673 if (next_node
!= NULL
)
674 next_node
= next_node
->next
;
678 next
= &previous_image
;
680 next_node
= current_image
->node
->prev
;
681 if (next_node
!= NULL
)
682 next_node
= next_node
->prev
;
688 /* Check if the requested image is just following the current one. */
689 if (filename
== (*next
)->node
->data
) {
695 * Check if the requested image is just before the previous one,
696 * or just after the next one.
698 if (next_node
!= NULL
&& filename
== next_node
->data
) {
702 g_object_unref(*prev
);
706 new = load_file(filename
, NULL
);
708 remove_from_list(next_node
);
712 new->node
= next_node
;
714 render_next_image(new);
722 gboolean
menu_load(const gchar
* filename
)
725 static gboolean loading
= FALSE
;
727 if (loading
|| currently_loading()) {
728 /* We are not reentrant. */
734 if (current_image
->node
!= NULL
&&
735 (filename
== current_image
->node
->data
||
736 check_direction(filename
, -1) || check_direction(filename
, 1))) {
741 im
= load_file(filename
, NULL
);
747 im
->node
= find_node_by_name(filename
);
750 render_next_image(im
);
756 /* Called when destroying a file. */
757 void unload(GList
* node
)
759 if (previous_image
!= NULL
&& previous_image
->node
== node
) {
760 g_object_unref(previous_image
);
761 previous_image
= NULL
;
764 if (next_image
!= NULL
&& next_image
->node
== node
) {
765 g_object_unref(next_image
);
770 /* Called when we want only one image in memory. */
771 void unload_images(void)
773 forget_image(&previous_image
);
774 forget_image(&next_image
);
779 static void slide_show_remove_timer(void)
781 if (slide_show_timer
!= 0) {
782 g_source_remove(slide_show_timer
);
783 slide_show_timer
= 0;
787 void set_slide_show_menu(GtkMenuItem
* item
)
789 slide_show_menu
= item
;
792 static void stop_slide_show(void)
794 slide_show_remove_timer();
795 set_menu_label(slide_show_menu
, _("Start the slide show"), TRUE
);
798 static gboolean
slide_show_next(void)
800 load_direction(slide_show_direction
);
801 if (slide_show_started())
802 slide_show_advance();
806 static void slide_show_advance(void)
808 slide_show_remove_timer();
809 slide_show_timer
= g_timeout_add_full(G_PRIORITY_LOW
,
810 options
->duration
* 1000,
811 (GSourceFunc
) slide_show_next
, NULL
,
815 void start_slide_show(void)
818 slide_show_direction
= 1;
820 if (options
->duration
< 0 || current_image
== NULL
)
823 slide_show_advance();
824 set_menu_label(slide_show_menu
, _("Stop the slide show"), TRUE
);
827 gboolean
slide_show_started(void)
829 return slide_show_timer
!= 0;
832 gboolean
toggle_slide_show(void)
834 if (slide_show_started())
842 void new_images(gint nb_inserted
)
844 if (nb_inserted
== 0)
847 open_next_image(nb_inserted
== 1);
848 do_later(G_PRIORITY_LOW
, cond_rebuild_menus
);