gliv-1.7
[gliv.git] / src / files_list.c
blob8acd8262095432ef3545e05f0b4d6014b22453e2
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 <gfc@altern.org>
21 /*******************
22 * The files list. *
23 *******************/
25 #include "gliv.h"
27 #include <string.h> /* strcmp(), strchr() */
28 #include <sys/stat.h> /* stat() */
29 #include <dirent.h> /* DIR, opendir(), readdir(), closedir() */
30 #include <stdlib.h> /* qsort() */
31 #include <stdio.h> /* remove(), perror() */
33 extern gliv_image *current_image;
35 static GList *files_list = NULL;
36 static gint list_length = 0;
38 /* The node to delete when no longer displayed. */
39 static GList *to_destroy_node = NULL;
40 static gint to_destroy_number;
42 gint get_list_length(void)
44 return list_length;
47 GList *get_list_head(void)
49 return files_list;
52 /*** Additions. ***/
54 static void add_file_to_list(const gchar * filename)
56 if (is_loadable(filename)) {
57 files_list = g_list_prepend(files_list, clean_filename(filename));
58 list_length++;
62 static void add_to_list(const gchar * name, gboolean recursive)
64 struct stat st;
65 struct dirent *dir_ent;
66 DIR *dir;
67 gchar *full_path;
69 if (stat(name, &st) < 0) {
70 perror(name);
71 return;
74 if (S_ISDIR(st.st_mode)) {
76 if (recursive)
77 /* Traverse recursively the directory. */
78 foreach_file(name, add_file_to_list);
80 else {
81 /* Add every file in the directory. */
82 dir = opendir(name);
83 if (dir == NULL)
84 return;
86 for (dir_ent = readdir(dir); dir_ent != NULL;
87 dir_ent = readdir(dir)) {
89 full_path = fast_build_filename(name, dir_ent->d_name);
91 stat(full_path, &st);
93 if (S_ISDIR(st.st_mode) == FALSE)
94 add_file_to_list(full_path);
96 g_free(full_path);
98 closedir(dir);
100 } else
101 /* Add the file. */
102 add_file_to_list(name);
105 /*** Deletion ***/
107 void remove_from_list(GList * node)
109 g_free(node->data);
110 files_list = g_list_delete_link(files_list, node);
111 list_length--;
114 /*** Sorting ***/
116 /* To shuffle the list, we simply sort with a random compare func. */
117 static gint random_compar(gconstpointer unused1, gconstpointer unused2)
119 return g_random_int_range(-1, 3);
122 /* We want children to be after their parent, or the alphabetical order. */
123 G_GNUC_PURE static gint filename_compar(gconstpointer a, gconstpointer b)
125 const gchar *file1, *file2, *ptr1, *ptr2;
126 gboolean ptr1_has_dir, ptr2_has_dir;
128 ptr1 = file1 = (const gchar *) a;
129 ptr2 = file2 = (const gchar *) b;
131 if (file1[0] != file2[0])
132 /* Comparing an absolute filename, and a relative one. */
133 return file1[0] == '/' ? -1 : 1;
135 /* Skip identical characters in the paths. */
136 while (*ptr1 != '\0' && *ptr1 == *ptr2) {
137 ptr1++;
138 ptr2++;
141 if (*ptr1 == *ptr2)
142 /* The filenames were equal. */
143 return 0;
145 /* Go back to the first different dir. */
146 while (*ptr1 != '/' || *ptr2 != '/') {
147 ptr1--;
148 ptr2--;
151 /* Skip the common '/'. */
152 ptr1++;
153 ptr2++;
155 ptr1_has_dir = (strchr(ptr1, '/') != NULL);
156 ptr2_has_dir = (strchr(ptr2, '/') != NULL);
158 if (ptr1_has_dir == ptr2_has_dir)
160 * Either the files are in the same directory,
161 * or they are not parent.
163 return strcmp(ptr1, ptr2);
165 /* One of the directory is parent of the other one. */
166 return ptr1_has_dir ? -1 : 1;
169 static void reorder_list(gboolean shuffle)
171 GCompareFunc compare_func;
173 compare_func = shuffle ? random_compar : filename_compar;
174 files_list = g_list_sort(files_list, compare_func);
177 /* Called by the menu. */
178 void reorder_files(gboolean shuffle)
180 GList *node, *current_node;
181 gint new_id = 0;
183 if (files_list == NULL)
184 return;
186 current_node = current_image->node;
188 reorder_list(shuffle);
190 /* Search the new id for current_image. */
191 for (node = files_list; node != NULL; node = node->next) {
192 if (current_node == node)
193 break;
194 new_id++;
197 after_reorder(new_id);
200 G_GNUC_PURE static gint reverse_compar(gconstpointer a, gconstpointer b)
202 return -filename_compar(*((const gchar **) a), *((const gchar **) b));
206 * Used to build the images menus.
207 * The logic would be to sort the array, and then build the menus
208 * with calls to gtk_menu_shell_append().
209 * But as the latter appends to a list by searching the end each time,
210 * we reverse sort the array and then insert in the list head.
212 gchar **get_sorted_files_array(void)
214 gchar **array, **array_ptr;
215 GList *list_ptr;
217 if (list_length == 0)
218 return NULL;
220 array_ptr = array = g_new(gchar *, list_length);
222 /* Fill the array. */
223 for (list_ptr = files_list; list_ptr != NULL; list_ptr = list_ptr->next) {
224 *array_ptr = list_ptr->data;
225 array_ptr++;
228 qsort(array, list_length, sizeof(gchar *), reverse_compar);
230 return array;
233 /*** Initialization ***/
235 void init_list(gchar ** names, gint num, gboolean recursive,
236 gboolean shuffle, gboolean sort)
238 for (; num != 0; names++, num--)
239 add_to_list(*names, recursive);
241 if (files_list == NULL) {
242 ERROR_MSG(_("No image found\n"));
243 quit(1);
246 if (sort || shuffle)
247 reorder_list(shuffle);
248 else
249 /* We inserted in the head. */
250 files_list = g_list_reverse(files_list);
253 /*** Misc. operations. ***/
255 /* To avoid a gcc warning. */
256 G_GNUC_PURE static gint strcmp_compar(gconstpointer a, gconstpointer b)
258 return strcmp(a, b);
261 /* Move backward a node to the head, or to the next image. */
262 void place_next(const gchar * filename)
264 GList *node;
266 node = (current_image == NULL) ? files_list : current_image->node->next;
267 node = g_list_find_custom(node, filename, strcmp_compar);
269 files_list = g_list_remove_link(files_list, node);
271 if (current_image == NULL) {
272 /* Move to the head. */
273 node->next = files_list;
274 if (files_list)
275 files_list->prev = node;
277 files_list = node;
278 } else {
279 /* Move to the next image. */
280 node->prev = current_image->node;
281 node->next = current_image->node->next;
283 if (node->prev != NULL)
284 node->prev->next = node;
286 if (node->next != NULL)
287 node->next->prev = node;
291 /* Returns TRUE if something has been inserted. */
292 gboolean insert_after_current(gchar ** names, gint nb, gboolean shuffle)
294 /* Backup the current list. */
295 GList *files_list_old = files_list, *last;
296 gint list_length_old = list_length;
298 /* Reinitialize the current list. */
299 files_list = NULL;
300 list_length = 0;
301 for (; nb != 0; names++, nb--)
302 add_to_list(*names, FALSE);
304 if (files_list == NULL) {
305 /* No files were added. */
306 files_list = files_list_old;
307 list_length = list_length_old;
308 return FALSE;
311 reorder_list(shuffle);
313 if (current_image == NULL)
314 return TRUE;
316 /* Search the last inserted. */
317 for (last = files_list; last->next != NULL; last = last->next);
319 /* Merge the insertion end. */
320 last->next = current_image->node->next;
321 if (last->next)
322 last->next->prev = last;
324 /* Merge the insertion beginning. */
325 current_image->node->next = files_list;
326 files_list->prev = current_image->node;
328 files_list = files_list_old;
329 list_length += list_length_old;
331 return TRUE;
334 static gboolean show_remove_dialog(const gchar * title, GtkLabel * label)
336 GtkDialog *dialog;
337 gint res;
339 dialog = GTK_DIALOG(gtk_dialog_new_with_buttons
340 (title, get_current_window(),
341 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
342 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
343 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL));
345 gtk_container_add(GTK_CONTAINER(dialog->vbox), GTK_WIDGET(label));
346 gtk_widget_show_all(GTK_WIDGET(label));
348 res = gtk_dialog_run(dialog);
350 gtk_widget_destroy(GTK_WIDGET(dialog));
352 return res == GTK_RESPONSE_ACCEPT || res == GTK_RESPONSE_OK ||
353 res == GTK_RESPONSE_YES;
356 void confirm_remove_current(void)
358 gchar *filename, *msg;
359 GtkLabel *label;
361 if (current_image == NULL)
362 return;
364 filename = current_image->node->data;
365 msg = g_strdup_printf(_("Do you really want to delete this file?\n%s\n"),
366 locale_to_utf8(filename));
368 label = GTK_LABEL(gtk_label_new(msg));
370 if (show_remove_dialog(locale_to_utf8(filename), label)) {
371 to_destroy_node = current_image->node;
372 to_destroy_number = current_image->number;
374 if (remove(filename) < 0)
375 perror(filename);
378 g_free(msg);
381 /* Called when the displayed image is changed. */
382 void delete_selected_image(void)
384 if (to_destroy_node == NULL)
385 return;
387 unload(to_destroy_node);
389 if (current_image->number > to_destroy_number)
390 current_image->number--;
392 remove_from_list(to_destroy_node);
394 to_destroy_node = NULL;