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>
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 */
33 #include "files_list.h"
36 #include "str_utils.h"
37 #include "foreach_file.h"
38 #include "next_image.h"
42 #include "collection.h"
44 #include "timestamp.h"
47 #include "../lib/getdelim.h"
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)
67 GList
*get_list_head(void)
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
);
80 /* We know the end is after current_image->node. */
81 list_end
= g_list_last(current_image
->node
);
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
);
96 static gboolean
is_loadable(const gchar
* filename
)
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
));
114 if (list_length
== 1)
115 list_end
= files_list
;
123 /* Returns the number of files added. */
124 static gint
add_to_list(const gchar
* name
, gboolean recursive
)
126 gint nb_inserted
= 0;
128 struct dirent
*dir_ent
;
132 if (stat(name
, &st
) < 0) {
137 if (S_ISDIR(st
.st_mode
)) {
139 /* Traverse recursively the directory. */
140 return foreach_file(name
, add_file_to_list
);
142 /* Add every file in the directory. */
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
);
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
);
169 nb_inserted
= add_file_to_list(name
);
177 void remove_from_list(GList
* node
)
179 if (node
== list_end
)
180 list_end
= node
->prev
;
183 files_list
= g_list_delete_link(files_list
, node
);
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
) {
216 /* The filenames were equal. */
219 /* Go back to the first different dir. */
220 while (*ptr1
!= '/' || *ptr2
!= '/') {
225 /* Skip the common '/'. */
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
);
252 /* Called by the menu. */
253 void reorder_files(gboolean shuffle
)
255 GList
*node
, *current_node
;
258 if (files_list
== NULL
)
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
)
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
;
292 if (list_length
== 0)
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
;
304 qsort(array
, list_length
, sizeof(gchar
*), reverse_compar
);
309 /*** Initialization ***/
311 static void list_initialized(gboolean recursive
,
312 gboolean shuffle
, gboolean sort
)
315 reorder_list(shuffle
);
317 /* We inserted in the head. */
323 void init_list(gchar
** names
, gint num
, gboolean recursive
,
324 gboolean shuffle
, gboolean sort
)
327 add_to_list(*names
, recursive
);
332 if (files_list
== NULL
) {
333 DIALOG_MSG(_("No image found"));
337 list_initialized(recursive
, shuffle
, sort
);
340 void read_null_filenames(gboolean recursive
, gboolean shuffle
, gboolean sort
)
342 gchar
*filename
= NULL
;
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
)
360 /* Move backward a node to the head, or to the next image. */
361 void place_next(const gchar
* filename
)
365 /* Where to start the search. */
366 node
= (current_image
== NULL
) ? files_list
: current_image
->node
->next
;
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
;
380 files_list
->prev
= node
;
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. */
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
;
422 reorder_list(shuffle
);
424 /* We inserted in the head. */
427 if (files_list_old
== NULL
)
428 /* The previous list was empty => nothing to merge. */
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
;
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
;
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
;
460 static gboolean
show_remove_dialog(const gchar
* msg
)
462 GtkMessageDialog
*dialog
;
465 dialog
= GTK_MESSAGE_DIALOG(gtk_message_dialog_new(get_current_window(),
467 GTK_MESSAGE_QUESTION
,
468 GTK_BUTTONS_OK_CANCEL
,
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
)
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)
504 /* Called when the displayed image is changed. */
505 void delete_selected_image(void)
507 if (to_destroy_node
== NULL
)
508 /* Nothing to destroy. */
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)