Merged older cs.po file with newest pot file.
[gliv/czech_localization.git] / src / files_list.c
blob929489829d911b40deb6f671ff3bc833fe2ea058
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 * The files list *
23 ******************/
25 #include <sys/stat.h> /* stat() */
26 #include <stdlib.h> /* qsort() */
27 #include <stdio.h> /* remove(), perror(), stdin, getdelim() */
28 #include <sys/types.h> /* size_t */
30 #include "gliv.h"
31 #include "files_list.h"
32 #include "options.h"
33 #include "loading.h"
34 #include "str_utils.h"
35 #include "foreach_file.h"
36 #include "next_image.h"
37 #include "messages.h"
38 #include "windows.h"
39 #include "collection.h"
40 #include "formats.h"
41 #include "timestamp.h"
42 #include "pathset.h"
43 #include "strnatcmp.h"
45 #ifndef HAVE_GETDELIM
46 #include "../lib/getdelim.h"
47 #endif
49 extern options_struct *options;
50 extern GlivImage *current_image;
52 static GList *files_list = NULL;
53 static gint list_length = 0;
54 static GList *list_end = NULL;
55 static DECLARE_TIMESTAMP(timestamp); /* Last modification. */
56 static GList *obsolete_nodes = NULL; /* Nodes to delete when possible */
58 gint get_list_length(void)
60 return list_length;
63 GList *get_list_head(void)
65 return files_list;
68 GList *get_list_end(void)
70 return list_end;
73 /*** Additions. ***/
75 static gboolean is_loadable(const gchar * filename)
77 loader_t loader;
79 if (options->force)
80 return TRUE;
82 loader = get_loader(filename);
83 return loader == LOADER_PIXBUF || loader == LOADER_DECOMP_PIXBUF;
86 /* Returns the number of files added. */
87 static gint add_file_to_list(const gchar * filename)
89 if (is_loadable(filename)) {
90 list_end = g_list_append(list_end, clean_filename(filename));
91 list_length++;
93 if (list_length == 1)
94 files_list = list_end;
95 else
96 list_end = list_end->next;
98 touch(&timestamp);
99 return 1;
102 return 0;
105 struct files_list_state {
106 GList *files_list;
107 gint list_length;
108 GList *list_end;
111 static struct files_list_state *save_files_list_state(void)
113 struct files_list_state *prev = g_new(struct files_list_state, 1);
115 prev->files_list = files_list;
116 prev->list_length = list_length;
117 prev->list_end = list_end;
119 files_list = NULL;
120 list_length = 0;
121 list_end = NULL;
123 return prev;
126 static void merge_files_list_state(struct files_list_state *prev,
127 gboolean after_current)
129 if (prev->list_length == 0) {
130 /* The previous list was empty => nothing to merge. */
131 goto end_free;
134 if (files_list == NULL) {
135 /* No files were added. */
136 files_list = prev->files_list;
137 list_length = prev->list_length;
138 list_end = prev->list_end;
139 goto end_free;
142 if (after_current && current_image != NULL &&
143 current_image->node->next != NULL) {
144 /* Insert after the current image. */
146 /* Merge the insertion end. */
147 list_end->next = current_image->node->next;
148 list_end->next->prev = list_end;
150 /* Merge the insertion beginning. */
151 current_image->node->next = files_list;
152 files_list->prev = current_image->node;
154 files_list = prev->files_list;
155 } else {
156 /* Insert at the end. */
157 if (prev->list_end != NULL)
158 prev->list_end->next = files_list;
160 files_list->prev = prev->list_end;
161 files_list = prev->files_list;
164 list_length += prev->list_length;
166 end_free:
168 g_free(prev);
171 static void reorder_list(gboolean shuffle);
173 static gint cmp_filename(gconstpointer a, gconstpointer b)
175 return !g_str_equal(a, b);
178 /* Returns the number of files added. */
179 static gint add_dir(const gchar * dirname, const gchar * first_file)
181 gint nb_inserted = 0;
182 struct files_list_state *prev = save_files_list_state();
183 GList *place_first = NULL;
185 if (options->recursive) {
186 /* Traverse recursively the directory. */
187 nb_inserted += foreach_file(dirname, add_file_to_list);
188 } else {
189 /* Add every file in the directory. */
190 GDir *dir;
191 GError *err = NULL;
192 const gchar *dir_entry;
194 dir = g_dir_open(dirname, 0, &err);
195 if (dir == NULL) {
196 g_printerr("%s\n", err->message);
197 g_error_free(err);
198 return 0;
201 while ((dir_entry = g_dir_read_name(dir)) != NULL) {
202 gchar *full_path = g_build_filename(dirname, dir_entry, NULL);
204 if (!g_file_test(full_path, G_FILE_TEST_IS_DIR))
205 /* We have a file. */
206 nb_inserted += add_file_to_list(full_path);
208 g_free(full_path);
211 g_dir_close(dir);
214 reorder_list(FALSE);
216 if (first_file != NULL)
217 place_first = g_list_find_custom(files_list, first_file, cmp_filename);
219 if (place_first != NULL && place_first->prev != NULL) {
220 place_first->prev->next = place_first->next;
222 if (place_first->next != NULL)
223 place_first->next->prev = place_first->prev;
225 place_first->prev = NULL;
226 place_first->next = files_list;
228 files_list->prev = place_first;
230 files_list = place_first;
233 merge_files_list_state(prev, FALSE);
234 return nb_inserted;
237 /* Returns the number of files added. */
238 static gint add_to_list(const gchar * name, gboolean add_all)
240 gint nb_inserted = 0;
241 struct stat st;
243 if (stat(name, &st) < 0) {
244 perror(name);
245 return 0;
248 if (S_ISDIR(st.st_mode)) {
249 nb_inserted += add_dir(name, NULL);
250 } else {
251 loader_t loader = get_loader(name);
253 if (loader == LOADER_DOT_GLIV || loader == LOADER_DECOMP_DOT_GLIV)
254 /* A .gliv collection. */
255 nb_inserted += load_dot_gliv(name);
257 else if (add_all) {
258 gchar *dirname = g_path_get_dirname(name);
259 gchar *clean = clean_filename(name);
261 nb_inserted += add_dir(dirname, clean);
262 g_free(dirname);
263 g_free(clean);
264 } else {
265 nb_inserted += add_file_to_list(name);
269 return nb_inserted;
272 /*** Deletion ***/
274 void remove_from_list(GList * node)
276 if (node == list_end)
277 list_end = node->prev;
279 if (current_image && current_image->node == node)
280 current_image->node = NULL;
282 unload(node);
283 g_free(node->data);
284 files_list = g_list_delete_link(files_list, node);
285 list_length--;
286 touch(&timestamp);
289 /*** Sorting ***/
291 /* To shuffle the list, we simply sort with a random compare func. */
292 static gint random_compar(gconstpointer unused1, gconstpointer unused2)
294 return g_random_int_range(-1, 3);
297 /* We want children to be after their parent, or the alphabetical order. */
298 G_GNUC_PURE static gint filename_compar(gconstpointer a, gconstpointer b)
300 const gchar *ptr1, *ptr2;
301 gboolean ptr1_has_dir = FALSE, ptr2_has_dir = FALSE;
302 gint prefix_length;
304 ptr1 = (const gchar *) a;
305 ptr2 = (const gchar *) b;
307 if (ptr1[0] != ptr2[0])
308 /* Comparing an absolute filename, and a relative one. */
309 return ptr1[0] == '/' ? -1 : 1;
311 prefix_length = common_prefix_length(ptr1, ptr2);
312 ptr1 += prefix_length;
313 ptr2 += prefix_length;
315 if (*ptr1 == *ptr2)
316 /* The filenames were equal. */
317 return 0;
319 /* Go back to the first different dir. */
320 for (;;) {
321 ptr1--;
322 ptr2--;
324 if (*ptr1 == '/') {
325 if (*ptr2 == '/')
326 break;
328 ptr1_has_dir = TRUE;
329 } else if (*ptr2 == '/') {
330 ptr2_has_dir = TRUE;
334 /* Skip the common '/'. */
335 ptr1++;
336 ptr2++;
338 if (ptr1_has_dir == ptr2_has_dir)
340 * Either the files are in the same directory,
341 * or they are not parent.
343 return strnatcmp(ptr1, ptr2);
345 /* One of the directory is parent of the other one. */
346 return ptr1_has_dir ? -1 : 1;
349 static void reorder_list(gboolean shuffle)
351 GCompareFunc compare_func;
353 compare_func = shuffle ? random_compar : filename_compar;
354 files_list = g_list_sort(files_list, compare_func);
355 list_end = g_list_last(list_end);
358 /* Called by the menu. */
359 gboolean reorder_files(gboolean shuffle)
361 if (files_list == NULL)
362 return FALSE;
364 reorder_list(shuffle);
366 after_reorder();
367 touch(&timestamp);
368 return FALSE;
371 G_GNUC_PURE static gint compar(gconstpointer a, gconstpointer b)
373 return filename_compar(*((const gchar **) a), *((const gchar **) b));
377 * Used to build the images menus.
379 gchar **get_sorted_files_array(void)
381 gchar **array, **array_ptr;
382 GList *list_ptr;
384 if (list_length == 0)
385 return NULL;
387 array_ptr = array = g_new(gchar *, list_length + 1);
389 /* Fill the array. */
390 for (list_ptr = files_list; list_ptr != NULL; list_ptr = list_ptr->next) {
391 *array_ptr = list_ptr->data;
392 array_ptr++;
395 *array_ptr = NULL;
396 qsort(array, list_length, sizeof(gchar *), compar);
398 return array;
401 /*** Initialization ***/
403 static void list_initialized(gboolean sort, gboolean shuffle)
405 if (sort || shuffle)
406 reorder_list(shuffle);
408 touch(&timestamp);
411 gint init_from_null_filenames(gboolean sort, gboolean shuffle, gboolean add_all)
413 gchar *filename = NULL;
414 size_t len = 0;
415 gint nb_inserted = 0;
417 while (!feof(stdin) && getdelim(&filename, &len, '\0', stdin) > 0)
418 nb_inserted += add_to_list(filename, add_all);
420 list_initialized(sort, shuffle);
421 g_free(filename);
423 return nb_inserted;
426 gint init_list(gchar ** names, gint nb, gboolean sort, gboolean shuffle,
427 gboolean add_all)
429 gint nb_inserted;
431 nb_inserted = insert_after_current(names, nb, FALSE, add_all);
432 list_initialized(sort, shuffle);
434 return nb_inserted;
437 /*** Misc. operations. ***/
439 /* Returns the number of files inserted. */
440 gint insert_after_current(gchar ** names, gint nb, gboolean just_file,
441 gboolean add_all)
443 gint nb_inserted = 0;
444 struct files_list_state *prev = save_files_list_state();
445 struct pathset *paths = pathset_new();
447 for (; nb != 0; names++, nb--) {
448 if (just_file)
449 nb_inserted += add_file_to_list(*names);
451 else if (!pathset_add(paths, *names))
452 /* Already added */
453 continue;
455 else if (add_all && !g_file_test(*names, G_FILE_TEST_IS_DIR)) {
456 gchar *dirname = g_path_get_dirname(*names);
458 if (pathset_add(paths, dirname))
459 nb_inserted += add_to_list(*names, TRUE);
461 g_free(dirname);
463 } else
464 nb_inserted += add_to_list(*names, FALSE);
467 pathset_free(paths);
468 list_initialized(FALSE, FALSE);
469 merge_files_list_state(prev, TRUE);
470 return nb_inserted;
473 static gboolean show_remove_dialog(const gchar * msg)
475 GtkMessageDialog *dialog;
476 gint res;
478 dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL,
479 GTK_DIALOG_MODAL,
480 GTK_MESSAGE_QUESTION,
481 GTK_BUTTONS_OK_CANCEL,
482 "%s", msg));
484 res = run_modal_dialog(GTK_DIALOG(dialog));
485 gtk_widget_destroy(GTK_WIDGET(dialog));
487 return res == GTK_RESPONSE_ACCEPT || res == GTK_RESPONSE_OK ||
488 res == GTK_RESPONSE_YES;
491 gboolean confirm_remove_current(void)
493 gchar *filename, *msg;
495 if (current_image == NULL || current_image->node == NULL)
496 return FALSE;
498 filename = current_image->node->data;
499 msg = g_strdup_printf(_("Do you really want to delete this file?\n%s\n"),
500 filename_to_utf8(filename));
502 if (show_remove_dialog(msg)) {
503 add_obsolete_node(current_image->node);
505 if (remove(filename) < 0)
506 perror(filename);
509 g_free(msg);
510 return FALSE;
513 timestamp_t get_list_timestamp(void)
515 return timestamp;
518 const gchar *get_nth_filename(gint n)
520 GList *ptr;
522 if (n < 0)
523 return _("directory/file");
525 if (n <= get_list_length() / 2)
526 for (ptr = get_list_head(); n != 0; n--, ptr = ptr->next);
527 else {
528 n = get_list_length() - n - 1;
529 for (ptr = get_list_end(); n != 0; n--, ptr = ptr->prev);
532 return ptr->data;
535 gint get_image_number(GlivImage * im)
537 if (im->number < 0) {
538 GList *ptr;
539 gint im_nr = 0;
541 if (im->node == NULL)
542 return im->number;
544 for (ptr = get_list_head(); ptr != NULL; ptr = ptr->next) {
545 if (ptr == im->node) {
546 im->number = im_nr;
547 break;
550 im_nr++;
554 return im->number;
557 GList *find_node_by_name(const gchar * name)
559 GList *ptr;
561 for (ptr = get_list_head(); ptr != NULL; ptr = ptr->next)
562 if (ptr->data == name)
563 return ptr;
565 return NULL;
568 void add_obsolete_node(GList * node)
570 obsolete_nodes = g_list_prepend(obsolete_nodes, node);
573 gboolean remove_obsolete_nodes(void)
575 GList *ptr, *next;
576 gboolean destroyed = FALSE;
578 for (ptr = obsolete_nodes; ptr != NULL; ptr = next) {
579 next = ptr->next;
580 if (ptr->data != current_image->node) {
581 remove_from_list(ptr->data);
582 obsolete_nodes = g_list_delete_link(obsolete_nodes, ptr);
583 destroyed = TRUE;
587 if (destroyed)
588 touch(&timestamp);
590 return destroyed;