Merged older cs.po file with newest pot file.
[gliv/czech_localization.git] / src / next_image.c
blobc9e9f9f97e0be253c3291894b54d5014bdc739b2
1 /*
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() */
32 #include "gliv.h"
33 #include "next_image.h"
34 #include "options.h"
35 #include "gliv-image.h"
36 #include "rendering.h"
37 #include "loading.h"
38 #include "files_list.h"
39 #include "textures.h"
40 #include "messages.h"
41 #include "menus.h"
42 #include "callbacks.h"
43 #include "transition.h"
44 #include "windows.h"
45 #include "tree_browser.h"
46 #include "images_menus.h"
48 extern rt_struct *rt;
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);
67 *im_ptr = NULL;
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 ***/
93 typedef enum {
94 NOTICE_FIRST,
95 NOTICE_LAST,
96 NOTICE_NOTHING
97 } notice_t;
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;
110 if (need_refresh)
111 refresh(REFRESH_IMAGE);
113 notice_timer = 0;
114 return FALSE;
117 static void image_notice(gboolean last_image)
119 notice_t new_notice;
120 gboolean need_refresh;
122 if (options->notice_time <= 0)
123 return;
125 new_notice = last_image ? NOTICE_LAST : NOTICE_FIRST;
126 need_refresh = new_notice != notice;
127 notice = new_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);
135 if (need_refresh)
136 refresh(REFRESH_IMAGE);
139 const gchar *get_image_notice(void)
141 switch (notice) {
142 case NOTICE_FIRST:
143 return _("First image");
145 case NOTICE_LAST:
146 return _("Last image");
148 default:
149 return NULL;
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) {
164 gint i;
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();
173 process_events();
174 gdk_flush();
178 if (make_transition)
179 transition(im);
181 if (current_image != NULL) {
182 prioritize_textures(current_image, FALSE);
183 g_object_unref(current_image);
186 current_image = im;
187 g_object_ref(im);
188 render();
190 if (im->node->next == NULL)
191 image_notice(TRUE);
193 else if (im->node->prev == NULL && first_image == FALSE)
194 image_notice(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)
206 if (node == NULL)
207 return (dir == 1) ? get_list_head() : get_list_end();
209 if (dir == 1)
210 return node->next;
212 /* dir == -1 */
213 return node->prev;
216 static void show_errors(GSList * errors)
218 GtkDialog *dialog;
219 GtkTextView *text;
220 GtkTextBuffer *buffer;
221 GtkScrolledWindow *scroll;
223 /* Create the widgets */
225 dialog =
226 GTK_DIALOG(gtk_dialog_new_with_buttons(_("Loading errors"),
227 get_current_window(),
228 GTK_DIALOG_DESTROY_WITH_PARENT,
229 GTK_STOCK_OK,
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);
242 while (errors) {
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;
261 GList *next;
262 GError *error;
263 GSList *failed = NULL;
264 gint nb_errors = 0;
266 while ((next = next_node(*node, dir)) != NULL) {
267 gchar *filename = next->data;
269 error = NULL;
270 im = load_file(filename, &error);
271 if (im == NULL) {
272 gchar *msg;
274 /* Delete bad images from the list. */
275 remove_from_list(next);
277 if (++nb_errors < 1024) {
278 msg = error ?
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);
284 } else {
285 *node = next;
286 if (error != NULL && error->message != NULL)
287 g_printerr("%s\n", error->message);
288 break;
292 if (nb_errors == 1) {
293 gchar *filename = failed->data;
294 DIALOG_MSG("%s", filename);
295 } else if (nb_errors > 1) {
296 /* Many errors */
297 GSList *ptr;
299 failed = g_slist_reverse(failed);
300 show_errors(failed);
301 for (ptr = failed; ptr != NULL; ptr = ptr->next)
302 g_free(ptr->data);
304 g_slist_free(failed);
307 return im;
310 static GlivImage *load_in_direction(gint dir)
312 GlivImage *im = NULL;
313 GList *node;
314 gint id;
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);
330 return next_image;
334 if (current_image == NULL) {
335 /* First time. */
336 node = NULL;
337 id = 0;
338 } else {
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. */
347 node = NULL;
348 id = (dir == 1) ? 0 : (get_list_length() - 1);
349 im = load_next_node(&node, dir);
352 if (im != NULL) {
353 im->node = node;
354 im->number = id;
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);
362 return im;
365 /* Called when switching images. */
366 static void destroy_unused(GlivImage ** backup)
368 if (*backup != NULL) {
369 g_object_unref(*backup);
370 *backup = NULL;
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)
382 GlivImage **backup;
383 GlivImage **next;
384 GlivImage *im;
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;
393 return;
396 if (currently_loading() || current_image == NULL)
397 return;
399 for (;;) {
400 if (dir == -1) {
401 backup = &next_image;
402 next = &previous_image;
403 } else if (dir == 1) {
404 backup = &previous_image;
405 next = &next_image;
406 } else
407 return;
409 in_load_direction = TRUE;
410 next_direction = 0;
412 /* Be sure there is a next image. */
413 if (*next == NULL) {
414 *next = load_in_direction(dir);
415 if (*next == NULL) {
416 /* There is no next image to load. */
417 image_notice(dir == 1);
418 stop_slide_show();
419 goto out;
423 destroy_unused(backup);
424 im = *next;
425 *next = NULL;
427 render_next_image(im);
428 g_object_unref(im);
430 if (options->one_image) {
431 g_object_unref(*backup);
432 *backup = NULL;
433 } else
434 *next = load_in_direction(dir);
436 slide_show_direction = dir;
437 out:
438 in_load_direction = FALSE;
439 dir = next_direction;
443 /* Load a randomly selected image from the list. */
444 void load_random_image()
446 gint nr;
447 const gchar *filename;
449 nr = random() % get_list_length();
450 filename = get_nth_filename(nr);
451 menu_load(filename);
454 /* Load the first image from the list. */
455 void load_1st_image()
457 const gchar *filename;
459 filename = get_nth_filename(0);
460 menu_load(filename);
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);
469 menu_load(filename);
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
476 * not change.
478 static void open_next_image(gboolean keep_next)
480 GlivImage *next;
481 GlivImage *future_image;
483 if (currently_loading())
484 return;
486 if (keep_next) {
487 forget_image(&previous_image);
488 } else
489 unload_images();
491 /* load_in_direction() would be too smart when looping with 2 images */
492 future_image = next_image;
493 next_image = NULL;
495 next = load_in_direction(1);
496 if (next == NULL)
497 return;
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)
521 start_slide_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();
537 return;
540 first_image = load_in_direction(1);
541 if (first_image == NULL)
542 DIALOG_MSG(_("No image found"));
543 else
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);
559 * Reloading.
560 * Used for example, when an option is changed such as dithering or mipmap.
563 void reload_current_image(void)
565 GlivImage *new;
567 if (currently_loading() || current_image == NULL ||
568 current_image->node == NULL)
569 return;
571 new = load_file(current_image->node->data, NULL);
572 if (new == NULL)
573 return;
575 new->node = current_image->node;
576 new->number = current_image->number;
578 g_object_unref(current_image);
579 current_image = new;
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())
589 return;
591 forget_image(&previous_image);
592 forget_image(&next_image);
593 reload_current_image();
596 struct image_stat {
597 off_t size;
598 time_t ctime;
599 gboolean present;
602 struct image_stat3 {
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)
610 st->present = FALSE;
612 if (im != NULL && im->node != NULL) {
613 struct stat real_stat;
615 if (stat(im->node->data, &real_stat) < 0) {
616 if (!quiet)
617 perror(im->node->data);
618 return;
621 st->size = real_stat.st_size;
622 st->ctime = real_stat.st_ctime;
623 st->present = TRUE;
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);
635 return stat_data;
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);
649 if (DIFFERENT(next))
650 forget_image(&next_image);
652 if (now->current.present && DIFFERENT(current))
653 reload_current_image();
655 g_free(before);
656 g_free(now);
660 /*** Menu ***/
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;
666 GList *next_node;
668 if (dir == 1) {
669 prev = &previous_image;
670 next = &next_image;
672 next_node = current_image->node->next;
673 if (next_node != NULL)
674 next_node = next_node->next;
675 } else {
676 /* dir == -1 */
677 prev = &next_image;
678 next = &previous_image;
680 next_node = current_image->node->prev;
681 if (next_node != NULL)
682 next_node = next_node->prev;
685 if (*next == NULL)
686 return FALSE;
688 /* Check if the requested image is just following the current one. */
689 if (filename == (*next)->node->data) {
690 load_direction(dir);
691 return TRUE;
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) {
699 GlivImage *new;
701 if (*prev != NULL)
702 g_object_unref(*prev);
703 *prev = *next;
704 *next = NULL;
706 new = load_file(filename, NULL);
707 if (new == NULL) {
708 remove_from_list(next_node);
709 return TRUE;
712 new->node = next_node;
714 render_next_image(new);
715 g_object_unref(new);
716 return TRUE;
719 return FALSE;
722 gboolean menu_load(const gchar * filename)
724 GlivImage *im;
725 static gboolean loading = FALSE;
727 if (loading || currently_loading()) {
728 /* We are not reentrant. */
729 return FALSE;
732 loading = TRUE;
734 if (current_image->node != NULL &&
735 (filename == current_image->node->data ||
736 check_direction(filename, -1) || check_direction(filename, 1))) {
737 loading = FALSE;
738 return FALSE;
741 im = load_file(filename, NULL);
742 if (im == NULL) {
743 loading = FALSE;
744 return FALSE;
747 im->node = find_node_by_name(filename);
748 unload_images();
750 render_next_image(im);
751 loading = FALSE;
752 g_object_unref(im);
753 return FALSE;
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);
766 next_image = NULL;
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);
777 /*** Slide show ***/
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();
803 return FALSE;
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,
812 NULL);
815 void start_slide_show(void)
817 stop_slide_show();
818 slide_show_direction = 1;
820 if (options->duration < 0 || current_image == NULL)
821 return;
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())
835 stop_slide_show();
836 else
837 start_slide_show();
839 return FALSE;
842 void new_images(gint nb_inserted)
844 if (nb_inserted == 0)
845 return;
847 open_next_image(nb_inserted == 1);
848 do_later(G_PRIORITY_LOW, cond_rebuild_menus);