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@gmail.com>
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 */
31 #include "files_list.h"
34 #include "str_utils.h"
35 #include "foreach_file.h"
36 #include "next_image.h"
39 #include "collection.h"
41 #include "timestamp.h"
43 #include "strnatcmp.h"
46 #include "../lib/getdelim.h"
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)
63 GList
*get_list_head(void)
68 GList
*get_list_end(void)
75 static gboolean
is_loadable(const gchar
* filename
)
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
));
94 files_list
= list_end
;
96 list_end
= list_end
->next
;
105 struct files_list_state
{
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
;
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. */
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
;
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
;
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
;
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
);
189 /* Add every file in the directory. */
192 const gchar
*dir_entry
;
194 dir
= g_dir_open(dirname
, 0, &err
);
196 g_printerr("%s\n", err
->message
);
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
);
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
);
237 /* Returns the number of files added. */
238 static gint
add_to_list(const gchar
* name
, gboolean add_all
)
240 gint nb_inserted
= 0;
243 if (stat(name
, &st
) < 0) {
248 if (S_ISDIR(st
.st_mode
)) {
249 nb_inserted
+= add_dir(name
, NULL
);
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
);
258 gchar
*dirname
= g_path_get_dirname(name
);
259 gchar
*clean
= clean_filename(name
);
261 nb_inserted
+= add_dir(dirname
, clean
);
265 nb_inserted
+= add_file_to_list(name
);
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
;
284 files_list
= g_list_delete_link(files_list
, node
);
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
;
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
;
316 /* The filenames were equal. */
319 /* Go back to the first different dir. */
329 } else if (*ptr2
== '/') {
334 /* Skip the common '/'. */
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
)
364 reorder_list(shuffle
);
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
;
384 if (list_length
== 0)
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
;
396 qsort(array
, list_length
, sizeof(gchar
*), compar
);
401 /*** Initialization ***/
403 static void list_initialized(gboolean sort
, gboolean shuffle
)
406 reorder_list(shuffle
);
411 gint
init_from_null_filenames(gboolean sort
, gboolean shuffle
, gboolean add_all
)
413 gchar
*filename
= NULL
;
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
);
426 gint
init_list(gchar
** names
, gint nb
, gboolean sort
, gboolean shuffle
,
431 nb_inserted
= insert_after_current(names
, nb
, FALSE
, add_all
);
432 list_initialized(sort
, shuffle
);
437 /*** Misc. operations. ***/
439 /* Returns the number of files inserted. */
440 gint
insert_after_current(gchar
** names
, gint nb
, gboolean just_file
,
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
--) {
449 nb_inserted
+= add_file_to_list(*names
);
451 else if (!pathset_add(paths
, *names
))
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
);
464 nb_inserted
+= add_to_list(*names
, FALSE
);
468 list_initialized(FALSE
, FALSE
);
469 merge_files_list_state(prev
, TRUE
);
473 static gboolean
show_remove_dialog(const gchar
* msg
)
475 GtkMessageDialog
*dialog
;
478 dialog
= GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL
,
480 GTK_MESSAGE_QUESTION
,
481 GTK_BUTTONS_OK_CANCEL
,
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
)
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)
513 timestamp_t
get_list_timestamp(void)
518 const gchar
*get_nth_filename(gint n
)
523 return _("directory/file");
525 if (n
<= get_list_length() / 2)
526 for (ptr
= get_list_head(); n
!= 0; n
--, ptr
= ptr
->next
);
528 n
= get_list_length() - n
- 1;
529 for (ptr
= get_list_end(); n
!= 0; n
--, ptr
= ptr
->prev
);
535 gint
get_image_number(GlivImage
* im
)
537 if (im
->number
< 0) {
541 if (im
->node
== NULL
)
544 for (ptr
= get_list_head(); ptr
!= NULL
; ptr
= ptr
->next
) {
545 if (ptr
== im
->node
) {
557 GList
*find_node_by_name(const gchar
* name
)
561 for (ptr
= get_list_head(); ptr
!= NULL
; ptr
= ptr
->next
)
562 if (ptr
->data
== name
)
568 void add_obsolete_node(GList
* node
)
570 obsolete_nodes
= g_list_prepend(obsolete_nodes
, node
);
573 gboolean
remove_obsolete_nodes(void)
576 gboolean destroyed
= FALSE
;
578 for (ptr
= obsolete_nodes
; ptr
!= NULL
; 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
);