Tweaked the serialization progress dialog.
[gliv.git] / src / files_list.c
blobfe8f357bd071b41e615dda33be521d275f08d216
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 <string.h> /* strcmp(), strchr() */
26 #include <sys/stat.h> /* stat() */
27 #include <dirent.h> /* DIR, opendir(), readdir(), closedir() */
28 #include <stdlib.h> /* qsort() */
29 #include <stdio.h> /* remove(), perror(), stdin, getdelim() */
30 #include <sys/types.h> /* size_t */
32 #include "gliv.h"
33 #include "files_list.h"
34 #include "options.h"
35 #include "loading.h"
36 #include "str_utils.h"
37 #include "foreach_file.h"
38 #include "next_image.h"
39 #include "messages.h"
40 #include "windows.h"
41 #include "cursors.h"
42 #include "collection.h"
43 #include "formats.h"
44 #include "timestamp.h"
46 #ifndef HAVE_GETDELIM
47 #include "../lib/getdelim.h"
48 #endif
50 extern options_struct *options;
51 extern gliv_image *current_image;
53 static GList *files_list = NULL;
54 static gint list_length = 0;
55 static GList *list_end = NULL;
56 static DECLARE_TIMESTAMP(timestamp); /* Last modification. */
58 /* The node to delete when no longer displayed. */
59 static GList *to_destroy_node = NULL;
60 static gint to_destroy_number;
62 gint get_list_length(void)
64 return list_length;
67 GList *get_list_head(void)
69 return files_list;
72 GList *get_list_end(void)
74 if (list_end == NULL) {
75 /* Find the end, since we lost it. */
77 if (current_image == NULL)
78 list_end = g_list_last(files_list);
79 else
80 /* We know the end is after current_image->node. */
81 list_end = g_list_last(current_image->node);
84 return list_end;
87 /* Used when we insert in the head. */
88 static void reverse(void)
90 list_end = files_list;
91 files_list = g_list_reverse(files_list);
94 /*** Additions. ***/
96 static gboolean is_loadable(const gchar * filename)
98 loader_t loader;
100 if (options->force)
101 return TRUE;
103 loader = get_loader(filename);
104 return loader == LOADER_PIXBUF || loader == LOADER_DECOMP_PIXBUF;
107 /* Returns the number of files added. */
108 static gint add_file_to_list(const gchar * filename)
110 if (is_loadable(filename)) {
111 files_list = g_list_prepend(files_list, clean_filename(filename));
112 list_length++;
114 if (list_length == 1)
115 list_end = files_list;
117 return 1;
120 return 0;
123 /* Returns the number of files added. */
124 static gint add_to_list(const gchar * name, gboolean recursive)
126 gint nb_inserted = 0;
127 struct stat st;
128 struct dirent *dir_ent;
129 DIR *dir;
130 gchar *full_path;
132 if (stat(name, &st) < 0) {
133 perror(name);
134 return 0;
137 if (S_ISDIR(st.st_mode)) {
138 if (recursive)
139 /* Traverse recursively the directory. */
140 return foreach_file(name, add_file_to_list);
142 /* Add every file in the directory. */
143 dir = opendir(name);
144 if (dir == NULL) {
145 perror(name);
146 return 0;
149 for (dir_ent = readdir(dir); dir_ent != NULL; dir_ent = readdir(dir)) {
150 full_path = g_build_filename(name, dir_ent->d_name, NULL);
152 stat(full_path, &st);
154 if (S_ISDIR(st.st_mode) == FALSE)
155 /* We have a file. */
156 nb_inserted += add_file_to_list(full_path);
158 g_free(full_path);
161 closedir(dir);
162 } else {
163 loader_t loader = get_loader(name);
164 if (loader == LOADER_DOT_GLIV || loader == LOADER_DECOMP_DOT_GLIV)
165 /* A .gliv collection. */
166 nb_inserted = load_dot_gliv(name, TRUE);
167 else
168 /* Add the file. */
169 nb_inserted = add_file_to_list(name);
172 return nb_inserted;
175 /*** Deletion ***/
177 void remove_from_list(GList * node)
179 if (node == list_end)
180 list_end = node->prev;
182 g_free(node->data);
183 files_list = g_list_delete_link(files_list, node);
184 list_length--;
185 touch(&timestamp);
188 /*** Sorting ***/
190 /* To shuffle the list, we simply sort with a random compare func. */
191 static gint random_compar(gconstpointer unused1, gconstpointer unused2)
193 return g_random_int_range(-1, 3);
196 /* We want children to be after their parent, or the alphabetical order. */
197 G_GNUC_PURE static gint filename_compar(gconstpointer a, gconstpointer b)
199 const gchar *file1, *file2, *ptr1, *ptr2;
200 gboolean ptr1_has_dir, ptr2_has_dir;
202 ptr1 = file1 = (const gchar *) a;
203 ptr2 = file2 = (const gchar *) b;
205 if (file1[0] != file2[0])
206 /* Comparing an absolute filename, and a relative one. */
207 return file1[0] == '/' ? -1 : 1;
209 /* Skip identical characters in the paths. */
210 while (*ptr1 != '\0' && *ptr1 == *ptr2) {
211 ptr1++;
212 ptr2++;
215 if (*ptr1 == *ptr2)
216 /* The filenames were equal. */
217 return 0;
219 /* Go back to the first different dir. */
220 while (*ptr1 != '/' || *ptr2 != '/') {
221 ptr1--;
222 ptr2--;
225 /* Skip the common '/'. */
226 ptr1++;
227 ptr2++;
229 ptr1_has_dir = (strchr(ptr1, '/') != NULL);
230 ptr2_has_dir = (strchr(ptr2, '/') != NULL);
232 if (ptr1_has_dir == ptr2_has_dir)
234 * Either the files are in the same directory,
235 * or they are not parent.
237 return strcmp(ptr1, ptr2);
239 /* One of the directory is parent of the other one. */
240 return ptr1_has_dir ? -1 : 1;
243 static void reorder_list(gboolean shuffle)
245 GCompareFunc compare_func;
247 compare_func = shuffle ? random_compar : filename_compar;
248 files_list = g_list_sort(files_list, compare_func);
249 list_end = NULL;
252 /* Called by the menu. */
253 void reorder_files(gboolean shuffle)
255 GList *node, *current_node;
256 gint new_id = 0;
258 if (files_list == NULL)
259 return;
261 current_node = current_image->node;
263 reorder_list(shuffle);
265 /* Search the new id for current_image. */
266 for (node = files_list; node != NULL; node = node->next) {
267 if (current_node == node)
268 break;
269 new_id++;
272 after_reorder(new_id);
275 G_GNUC_PURE static gint reverse_compar(gconstpointer a, gconstpointer b)
277 return -filename_compar(*((const gchar **) a), *((const gchar **) b));
281 * Used to build the images menus.
282 * The logic would be to sort the array, and then build the menus
283 * with calls to gtk_menu_shell_append().
284 * But as the latter appends to a list by searching the end each time,
285 * we reverse sort the array and then insert in the list head.
287 gchar **get_sorted_files_array(void)
289 gchar **array, **array_ptr;
290 GList *list_ptr;
292 if (list_length == 0)
293 return NULL;
295 array_ptr = array = g_new(gchar *, list_length + 1);
297 /* Fill the array. */
298 for (list_ptr = files_list; list_ptr != NULL; list_ptr = list_ptr->next) {
299 *array_ptr = list_ptr->data;
300 array_ptr++;
303 *array_ptr = NULL;
304 qsort(array, list_length, sizeof(gchar *), reverse_compar);
306 return array;
309 /*** Initialization ***/
311 static void list_initialized(gboolean recursive,
312 gboolean shuffle, gboolean sort)
314 if (sort || shuffle)
315 reorder_list(shuffle);
316 else
317 /* We inserted in the head. */
318 reverse();
320 touch(&timestamp);
323 void init_list(gchar ** names, gint num, gboolean recursive,
324 gboolean shuffle, gboolean sort)
326 while (num) {
327 add_to_list(*names, recursive);
328 names++;
329 num--;
332 if (files_list == NULL) {
333 DIALOG_MSG(_("No image found"));
334 return;
337 list_initialized(recursive, shuffle, sort);
340 void read_null_filenames(gboolean recursive, gboolean shuffle, gboolean sort)
342 gchar *filename = NULL;
343 size_t len = 0;
345 while (!feof(stdin) && getdelim(&filename, &len, '\0', stdin) > 0)
346 add_to_list(filename, recursive);
348 list_initialized(recursive, shuffle, sort);
352 /*** Misc. operations. ***/
354 /* To avoid a gcc warning. */
355 G_GNUC_PURE static gint strcmp_compar(gconstpointer a, gconstpointer b)
357 return strcmp(a, b);
360 /* Move backward a node to the head, or to the next image. */
361 void place_next(const gchar * filename)
363 GList *node;
365 /* Where to start the search. */
366 node = (current_image == NULL) ? files_list : current_image->node->next;
368 /* The search. */
369 node = g_list_find_custom(node, filename, strcmp_compar);
371 if (node == list_end)
372 list_end = list_end->prev;
374 files_list = g_list_remove_link(files_list, node);
376 if (current_image == NULL) {
377 /* Move to the head. */
378 node->next = files_list;
379 if (files_list)
380 files_list->prev = node;
382 files_list = node;
383 } else {
384 /* Move to the next image. */
385 node->prev = current_image->node;
386 node->next = current_image->node->next;
388 if (node->prev != NULL)
389 node->prev->next = node;
391 if (node->next != NULL)
392 node->next->prev = node;
396 /* Returns the number of files inserted. */
397 gint insert_after_current(gchar ** names, gint nb, gboolean recursive,
398 gboolean shuffle, gboolean sort, gboolean do_stat)
400 gint nb_inserted = 0;
402 /* Backup the current list. */
403 GList *files_list_old = files_list, *last;
404 gint list_length_old = list_length;
406 /* Reinitialize the current list. */
407 files_list = NULL;
408 list_length = 0;
409 for (; nb != 0; names++, nb--)
410 nb_inserted += do_stat ? add_to_list(*names, recursive) :
411 add_file_to_list(*names);
413 if (files_list == NULL) {
414 /* No files were added. */
415 files_list = files_list_old;
416 list_length = list_length_old;
417 return 0;
420 touch(&timestamp);
421 if (sort || shuffle)
422 reorder_list(shuffle);
423 else
424 /* We inserted in the head. */
425 reverse();
427 if (files_list_old == NULL)
428 /* The previous list was empty => nothing to merge. */
429 return nb_inserted;
431 if (current_image != NULL) {
432 /* Insert after the current image. */
434 /* Search the last inserted. */
435 for (last = files_list; last->next != NULL; last = last->next);
437 /* Merge the insertion end. */
438 last->next = current_image->node->next;
439 if (last->next)
440 last->next->prev = last;
442 /* Merge the insertion beginning. */
443 current_image->node->next = files_list;
444 files_list->prev = current_image->node;
446 files_list = files_list_old;
447 } else {
448 /* Insert at the end. */
449 GList *old_last = g_list_last(files_list_old);
450 old_last->next = files_list;
451 files_list->prev = old_last;
452 files_list = files_list_old;
455 list_length += list_length_old;
457 return nb_inserted;
460 static gboolean show_remove_dialog(const gchar * msg)
462 GtkMessageDialog *dialog;
463 gint res;
465 dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(get_current_window(),
466 GTK_DIALOG_MODAL,
467 GTK_MESSAGE_QUESTION,
468 GTK_BUTTONS_OK_CANCEL,
469 "%s", msg));
471 /* We want the cursor to be visible when the dialog is shown. */
472 set_hide_cursor_enabled(FALSE);
473 res = gtk_dialog_run(GTK_DIALOG(dialog));
474 set_hide_cursor_enabled(TRUE);
476 gtk_widget_destroy(GTK_WIDGET(dialog));
478 return res == GTK_RESPONSE_ACCEPT || res == GTK_RESPONSE_OK ||
479 res == GTK_RESPONSE_YES;
482 void confirm_remove_current(void)
484 gchar *filename, *msg;
486 if (current_image == NULL)
487 return;
489 filename = current_image->node->data;
490 msg = g_strdup_printf(_("Do you really want to delete this file?\n%s\n"),
491 filename_to_utf8(filename));
493 if (show_remove_dialog(msg)) {
494 to_destroy_node = current_image->node;
495 to_destroy_number = current_image->number;
497 if (remove(filename) < 0)
498 perror(filename);
501 g_free(msg);
504 /* Called when the displayed image is changed. */
505 void delete_selected_image(void)
507 if (to_destroy_node == NULL)
508 /* Nothing to destroy. */
509 return;
511 unload(to_destroy_node);
513 if (current_image->number > to_destroy_number)
514 current_image->number--;
516 remove_from_list(to_destroy_node);
518 to_destroy_node = NULL;
521 timestamp_t get_list_timestamp(void)
523 return timestamp;