Use the new timestamp API.
[gliv.git] / src / files_list.c
blob5973520e4e662b5be8839a91bc1a816b40d00aab
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() */
31 #include "gliv.h"
32 #include "files_list.h"
33 #include "options.h"
34 #include "loading.h"
35 #include "str_utils.h"
36 #include "foreach_file.h"
37 #include "next_image.h"
38 #include "messages.h"
39 #include "windows.h"
40 #include "gl_widget.h"
41 #include "collection.h"
42 #include "formats.h"
43 #include "timestamp.h"
45 extern options_struct *options;
46 extern gliv_image *current_image;
48 static GList *files_list = NULL;
49 static gint list_length = 0;
50 static GList *list_end = NULL;
51 static DECLARE_TIMESTAMP(timestamp); /* Last modification. */
53 /* The node to delete when no longer displayed. */
54 static GList *to_destroy_node = NULL;
55 static gint to_destroy_number;
57 gint get_list_length(void)
59 return list_length;
62 GList *get_list_head(void)
64 return files_list;
67 GList *get_list_end(void)
69 if (list_end == NULL) {
70 /* Find the end, since we lost it. */
72 if (current_image == NULL)
73 list_end = g_list_last(files_list);
74 else
75 /* We know the end is after current_image->node. */
76 list_end = g_list_last(current_image->node);
79 return list_end;
82 /* Used when we insert in the head. */
83 static void reverse(void)
85 list_end = files_list;
86 files_list = g_list_reverse(files_list);
89 /*** Additions. ***/
91 static gboolean is_loadable(const gchar * filename)
93 loader_t loader;
95 if (options->force)
96 return TRUE;
98 loader = get_loader(filename);
99 return loader == LOADER_PIXBUF || loader == LOADER_DECOMP_PIXBUF;
102 /* Returns the number of files added. */
103 static gint add_file_to_list(const gchar * filename)
105 if (is_loadable(filename)) {
106 files_list = g_list_prepend(files_list, clean_filename(filename));
107 list_length++;
109 if (list_length == 1)
110 list_end = files_list;
112 return 1;
115 return 0;
118 /* Returns the number of files added. */
119 static gint add_to_list(const gchar * name, gboolean recursive)
121 gint nb_inserted = 0;
122 struct stat st;
123 struct dirent *dir_ent;
124 DIR *dir;
125 gchar *full_path;
127 if (stat(name, &st) < 0) {
128 perror(name);
129 return 0;
132 if (S_ISDIR(st.st_mode)) {
133 if (recursive)
134 /* Traverse recursively the directory. */
135 return foreach_file(name, add_file_to_list);
137 /* Add every file in the directory. */
138 dir = opendir(name);
139 if (dir == NULL) {
140 perror(name);
141 return 0;
144 for (dir_ent = readdir(dir); dir_ent != NULL; dir_ent = readdir(dir)) {
145 full_path = g_build_filename(name, dir_ent->d_name, NULL);
147 stat(full_path, &st);
149 if (S_ISDIR(st.st_mode) == FALSE)
150 /* We have a file. */
151 nb_inserted += add_file_to_list(full_path);
153 g_free(full_path);
156 closedir(dir);
157 } else {
158 loader_t loader = get_loader(name);
159 if (loader == LOADER_DOT_GLIV || loader == LOADER_DECOMP_DOT_GLIV)
160 /* A .gliv collection. */
161 nb_inserted = load_dot_gliv(name, TRUE);
162 else
163 /* Add the file. */
164 nb_inserted = add_file_to_list(name);
167 return nb_inserted;
170 /*** Deletion ***/
172 void remove_from_list(GList * node)
174 if (node == list_end)
175 list_end = node->prev;
177 g_free(node->data);
178 files_list = g_list_delete_link(files_list, node);
179 list_length--;
180 touch(&timestamp);
183 /*** Sorting ***/
185 /* To shuffle the list, we simply sort with a random compare func. */
186 static gint random_compar(gconstpointer unused1, gconstpointer unused2)
188 return g_random_int_range(-1, 3);
191 /* We want children to be after their parent, or the alphabetical order. */
192 G_GNUC_PURE static gint filename_compar(gconstpointer a, gconstpointer b)
194 const gchar *file1, *file2, *ptr1, *ptr2;
195 gboolean ptr1_has_dir, ptr2_has_dir;
197 ptr1 = file1 = (const gchar *) a;
198 ptr2 = file2 = (const gchar *) b;
200 if (file1[0] != file2[0])
201 /* Comparing an absolute filename, and a relative one. */
202 return file1[0] == '/' ? -1 : 1;
204 /* Skip identical characters in the paths. */
205 while (*ptr1 != '\0' && *ptr1 == *ptr2) {
206 ptr1++;
207 ptr2++;
210 if (*ptr1 == *ptr2)
211 /* The filenames were equal. */
212 return 0;
214 /* Go back to the first different dir. */
215 while (*ptr1 != '/' || *ptr2 != '/') {
216 ptr1--;
217 ptr2--;
220 /* Skip the common '/'. */
221 ptr1++;
222 ptr2++;
224 ptr1_has_dir = (strchr(ptr1, '/') != NULL);
225 ptr2_has_dir = (strchr(ptr2, '/') != NULL);
227 if (ptr1_has_dir == ptr2_has_dir)
229 * Either the files are in the same directory,
230 * or they are not parent.
232 return strcmp(ptr1, ptr2);
234 /* One of the directory is parent of the other one. */
235 return ptr1_has_dir ? -1 : 1;
238 static void reorder_list(gboolean shuffle)
240 GCompareFunc compare_func;
242 compare_func = shuffle ? random_compar : filename_compar;
243 files_list = g_list_sort(files_list, compare_func);
244 list_end = NULL;
247 /* Called by the menu. */
248 void reorder_files(gboolean shuffle)
250 GList *node, *current_node;
251 gint new_id = 0;
253 if (files_list == NULL)
254 return;
256 current_node = current_image->node;
258 reorder_list(shuffle);
260 /* Search the new id for current_image. */
261 for (node = files_list; node != NULL; node = node->next) {
262 if (current_node == node)
263 break;
264 new_id++;
267 after_reorder(new_id);
270 G_GNUC_PURE static gint reverse_compar(gconstpointer a, gconstpointer b)
272 return -filename_compar(*((const gchar **) a), *((const gchar **) b));
276 * Used to build the images menus.
277 * The logic would be to sort the array, and then build the menus
278 * with calls to gtk_menu_shell_append().
279 * But as the latter appends to a list by searching the end each time,
280 * we reverse sort the array and then insert in the list head.
282 gchar **get_sorted_files_array(void)
284 gchar **array, **array_ptr;
285 GList *list_ptr;
287 if (list_length == 0)
288 return NULL;
290 array_ptr = array = g_new(gchar *, list_length + 1);
292 /* Fill the array. */
293 for (list_ptr = files_list; list_ptr != NULL; list_ptr = list_ptr->next) {
294 *array_ptr = list_ptr->data;
295 array_ptr++;
298 *array_ptr = NULL;
299 qsort(array, list_length, sizeof(gchar *), reverse_compar);
301 return array;
304 /*** Initialization ***/
306 void init_list(gchar ** names, gint num, gboolean recursive,
307 gboolean shuffle, gboolean sort)
309 while (num) {
310 add_to_list(*names, recursive);
311 names++;
312 num--;
315 if (files_list == NULL) {
316 DIALOG_MSG(_("No image found"));
317 return;
320 if (sort || shuffle)
321 reorder_list(shuffle);
322 else
323 /* We inserted in the head. */
324 reverse();
326 touch(&timestamp);
329 /*** Misc. operations. ***/
331 /* To avoid a gcc warning. */
332 G_GNUC_PURE static gint strcmp_compar(gconstpointer a, gconstpointer b)
334 return strcmp(a, b);
337 /* Move backward a node to the head, or to the next image. */
338 void place_next(const gchar * filename)
340 GList *node;
342 /* Where to start the search. */
343 node = (current_image == NULL) ? files_list : current_image->node->next;
345 /* The search. */
346 node = g_list_find_custom(node, filename, strcmp_compar);
348 if (node == list_end)
349 list_end = list_end->prev;
351 files_list = g_list_remove_link(files_list, node);
353 if (current_image == NULL) {
354 /* Move to the head. */
355 node->next = files_list;
356 if (files_list)
357 files_list->prev = node;
359 files_list = node;
360 } else {
361 /* Move to the next image. */
362 node->prev = current_image->node;
363 node->next = current_image->node->next;
365 if (node->prev != NULL)
366 node->prev->next = node;
368 if (node->next != NULL)
369 node->next->prev = node;
373 /* Returns the number of files inserted. */
374 gint insert_after_current(gchar ** names, gint nb,
375 gboolean shuffle, gboolean sort)
377 gint nb_inserted = 0;
379 /* Backup the current list. */
380 GList *files_list_old = files_list, *last;
381 gint list_length_old = list_length;
383 /* Reinitialize the current list. */
384 files_list = NULL;
385 list_length = 0;
386 for (; nb != 0; names++, nb--)
387 nb_inserted += add_to_list(*names, FALSE);
389 if (files_list == NULL) {
390 /* No files were added. */
391 files_list = files_list_old;
392 list_length = list_length_old;
393 return 0;
396 touch(&timestamp);
397 if (sort || shuffle)
398 reorder_list(shuffle);
399 else
400 /* We inserted in the head. */
401 reverse();
403 if (files_list_old == NULL)
404 /* The previous list was empty => nothing to merge. */
405 return nb_inserted;
407 if (current_image != NULL) {
408 /* Insert after the current image. */
410 /* Search the last inserted. */
411 for (last = files_list; last->next != NULL; last = last->next);
413 /* Merge the insertion end. */
414 last->next = current_image->node->next;
415 if (last->next)
416 last->next->prev = last;
418 /* Merge the insertion beginning. */
419 current_image->node->next = files_list;
420 files_list->prev = current_image->node;
422 files_list = files_list_old;
423 } else {
424 /* Insert at the end. */
425 GList *old_last = g_list_last(files_list_old);
426 old_last->next = files_list;
427 files_list->prev = old_last;
428 files_list = files_list_old;
431 list_length += list_length_old;
433 return nb_inserted;
436 static gboolean show_remove_dialog(const gchar * msg)
438 GtkMessageDialog *dialog;
439 gint res;
441 dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(get_current_window(),
442 GTK_DIALOG_MODAL,
443 GTK_MESSAGE_QUESTION,
444 GTK_BUTTONS_OK_CANCEL,
445 "%s", msg));
447 /* We want the cursor to be visible when the dialog is shown. */
448 set_hide_cursor_enabled(FALSE);
449 res = gtk_dialog_run(GTK_DIALOG(dialog));
450 set_hide_cursor_enabled(TRUE);
452 gtk_widget_destroy(GTK_WIDGET(dialog));
454 return res == GTK_RESPONSE_ACCEPT || res == GTK_RESPONSE_OK ||
455 res == GTK_RESPONSE_YES;
458 void confirm_remove_current(void)
460 gchar *filename, *msg;
462 if (current_image == NULL)
463 return;
465 filename = current_image->node->data;
466 msg = g_strdup_printf(_("Do you really want to delete this file?\n%s\n"),
467 filename_to_utf8(filename));
469 if (show_remove_dialog(msg)) {
470 to_destroy_node = current_image->node;
471 to_destroy_number = current_image->number;
473 if (remove(filename) < 0)
474 perror(filename);
477 g_free(msg);
480 /* Called when the displayed image is changed. */
481 void delete_selected_image(void)
483 if (to_destroy_node == NULL)
484 /* Nothing to destroy. */
485 return;
487 unload(to_destroy_node);
489 if (current_image->number > to_destroy_number)
490 current_image->number--;
492 remove_from_list(to_destroy_node);
494 to_destroy_node = NULL;
497 timestamp_t get_list_timestamp(void)
499 return timestamp;