a146fc79dc7f611017900c0ed0485d4e8323e00e
[geeqie.git] / src / utilops.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "utilops.h"
24
25 #include "cache.h"
26 #include "filedata.h"
27 #include "filefilter.h"
28 #include "image.h"
29 #include "thumb-standard.h"
30 #include "trash.h"
31 #include "ui-bookmark.h"
32 #include "ui-fileops.h"
33 #include "ui-misc.h"
34 #include "editors.h"
35 #include "metadata.h"
36 #include "exif.h"
37
38 #define DIALOG_WIDTH 750
39
40 static GdkPixbuf *file_util_get_error_icon(FileData *fd, GList *list, GtkWidget *widget);
41
42 static GtkTargetEntry target_types[] =
43 {
44         {(gchar *)const_cast<gchar *>("text/plain"), 0, CLIPBOARD_TEXT_PLAIN},
45         {(gchar *)const_cast<gchar *>("text/uri-list"), 0, CLIPBOARD_TEXT_URI_LIST},
46         {(gchar *)"x-special/gnome-copied-files", 0, CLIPBOARD_X_SPECIAL_GNOME_COPIED_FILES},
47         {(gchar *)"UTF8_STRING", 0, CLIPBOARD_UTF8_STRING}
48 };
49 static gint target_types_n = 4;
50
51 typedef struct _ClipboardData ClipboardData;
52 struct _ClipboardData
53 {
54         GList *path_list; /**< g_strdup(fd->path) */
55         gboolean quoted;
56 };
57
58 /*
59  *--------------------------------------------------------------------------
60  * Adds 1 or 2 images (if 2, side by side) to a GenericDialog
61  *--------------------------------------------------------------------------
62  */
63
64 #define DIALOG_DEF_IMAGE_DIM_X 150
65 #define DIALOG_DEF_IMAGE_DIM_Y 100
66
67 static void generic_dialog_add_image(GenericDialog *gd, GtkWidget *box,
68                                      FileData *fd1, const gchar *header1,
69                                      gboolean second_image,
70                                      FileData *fd2, const gchar *header2,
71                                      gboolean show_filename)
72 {
73         ImageWindow *imd;
74         GtkWidget *preview_box = NULL;
75         GtkWidget *vbox;
76         GtkWidget *label = NULL;
77
78         if (!box) box = gd->vbox;
79
80         if (second_image)
81                 {
82                 preview_box = pref_box_new(box, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_SPACE);
83                 }
84
85         /* image 1 */
86
87         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
88         if (preview_box)
89                 {
90                 GtkWidget *sep;
91
92                 gtk_box_pack_start(GTK_BOX(preview_box), vbox, FALSE, TRUE, 0);
93
94                 sep = gtk_hseparator_new();
95                 gtk_box_pack_start(GTK_BOX(preview_box), sep, FALSE, FALSE, 0);
96                 gtk_widget_show(sep);
97                 }
98         else
99                 {
100                 gtk_box_pack_start(GTK_BOX(box), vbox, FALSE, TRUE, PREF_PAD_GAP);
101                 }
102         gtk_widget_show(vbox);
103
104         if (header1)
105                 {
106                 GtkWidget *head;
107
108                 head = pref_label_new(vbox, header1);
109                 pref_label_bold(head, TRUE, FALSE);
110                 gtk_label_set_xalign(GTK_LABEL(head), 0.0);
111                 gtk_label_set_yalign(GTK_LABEL(head), 0.5);
112                 }
113
114         imd = image_new(FALSE);
115         g_object_set(G_OBJECT(imd->pr), "zoom_expand", FALSE, NULL);
116         gtk_widget_set_size_request(imd->widget, DIALOG_DEF_IMAGE_DIM_X, DIALOG_DEF_IMAGE_DIM_Y);
117         gtk_box_pack_start(GTK_BOX(vbox), imd->widget, TRUE, TRUE, 0);
118         image_change_fd(imd, fd1, 0.0);
119         gtk_widget_show(imd->widget);
120
121         if (show_filename)
122                 {
123                 label = pref_label_new(vbox, (fd1 == NULL) ? "" : fd1->name);
124                 }
125
126         /* only the first image is stored (for use in gd_image_set) */
127         g_object_set_data(G_OBJECT(gd->dialog), "img_image", imd);
128         g_object_set_data(G_OBJECT(gd->dialog), "img_label", label);
129
130
131         /* image 2 */
132
133         if (preview_box)
134                 {
135                 vbox = pref_box_new(preview_box, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
136
137                 if (header2)
138                         {
139                         GtkWidget *head;
140
141                         head = pref_label_new(vbox, header2);
142                         pref_label_bold(head, TRUE, FALSE);
143                         gtk_label_set_xalign(GTK_LABEL(head), 0.0);
144                         gtk_label_set_yalign(GTK_LABEL(head), 0.5);
145                         }
146
147                 imd = image_new(FALSE);
148                 g_object_set(G_OBJECT(imd->pr), "zoom_expand", FALSE, NULL);
149                 gtk_widget_set_size_request(imd->widget, DIALOG_DEF_IMAGE_DIM_X, DIALOG_DEF_IMAGE_DIM_Y);
150                 gtk_box_pack_start(GTK_BOX(vbox), imd->widget, TRUE, TRUE, 0);
151                 if (fd2) image_change_fd(imd, fd2, 0.0);
152                 gtk_widget_show(imd->widget);
153
154                 if (show_filename)
155                         {
156                         label = pref_label_new(vbox, (fd2 == NULL) ? "" : fd2->name);
157                         }
158                 g_object_set_data(G_OBJECT(gd->dialog), "img_image2", imd);
159                 g_object_set_data(G_OBJECT(gd->dialog), "img_label2", label);
160                 }
161 }
162
163 /*
164  *--------------------------------------------------------------------------
165  * Wrappers to aid in setting additional dialog properties (unde mouse, etc.)
166  *--------------------------------------------------------------------------
167  */
168
169 GenericDialog *file_util_gen_dlg(const gchar *title,
170                                  const gchar *role,
171                                  GtkWidget *parent, gboolean auto_close,
172                                  void (*cancel_cb)(GenericDialog *, gpointer), gpointer data)
173 {
174         GenericDialog *gd;
175
176         gd = generic_dialog_new(title, role, parent, auto_close, cancel_cb, data);
177         if (options->place_dialogs_under_mouse)
178                 {
179                 gtk_window_set_position(GTK_WINDOW(gd->dialog), GTK_WIN_POS_MOUSE);
180                 }
181
182         return gd;
183 }
184
185 FileDialog *file_util_file_dlg(const gchar *title,
186                                const gchar *role,
187                                GtkWidget *parent,
188                                void (*cancel_cb)(FileDialog *, gpointer), gpointer data)
189 {
190         FileDialog *fdlg;
191
192         fdlg = file_dialog_new(title, role, parent, cancel_cb, data);
193         if (options->place_dialogs_under_mouse)
194                 {
195                 gtk_window_set_position(GTK_WINDOW(GENERIC_DIALOG(fdlg)->dialog), GTK_WIN_POS_MOUSE);
196                 }
197
198         return fdlg;
199 }
200
201 /* this warning dialog is copied from SLIK's ui_utildg.c,
202  * because it does not have a mouse center option,
203  * and we must center it before show, implement it here.
204  */
205 static void file_util_warning_dialog_ok_cb(GenericDialog *UNUSED(gd), gpointer UNUSED(data))
206 {
207         /* no op */
208 }
209
210 GenericDialog *file_util_warning_dialog(const gchar *heading, const gchar *message,
211                                         const gchar *icon_stock_id, GtkWidget *parent)
212 {
213         GenericDialog *gd;
214
215         gd = file_util_gen_dlg(heading, "warning", parent, TRUE, NULL, NULL);
216         generic_dialog_add_message(gd, icon_stock_id, heading, message, TRUE);
217         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, file_util_warning_dialog_ok_cb, TRUE);
218         if (options->place_dialogs_under_mouse)
219                 {
220                 gtk_window_set_position(GTK_WINDOW(gd->dialog), GTK_WIN_POS_MOUSE);
221                 }
222         gtk_widget_show(gd->dialog);
223
224         return gd;
225 }
226
227 static gint filename_base_length(const gchar *name)
228 {
229         gint n;
230
231         if (!name) return 0;
232
233         n = strlen(name);
234
235         if (filter_name_exists(name))
236                 {
237                 const gchar *ext;
238
239                 ext = registered_extension_from_path(name);
240                 if (ext) n -= strlen(ext);
241                 }
242
243         return n;
244 }
245
246
247
248
249 typedef enum {
250         UTILITY_TYPE_COPY,
251         UTILITY_TYPE_MOVE,
252         UTILITY_TYPE_RENAME,
253         UTILITY_TYPE_RENAME_FOLDER,
254         UTILITY_TYPE_EDITOR,
255         UTILITY_TYPE_FILTER,
256         UTILITY_TYPE_DELETE,
257         UTILITY_TYPE_DELETE_LINK,
258         UTILITY_TYPE_DELETE_FOLDER,
259         UTILITY_TYPE_CREATE_FOLDER,
260         UTILITY_TYPE_WRITE_METADATA
261 } UtilityType;
262
263 typedef enum {
264         UTILITY_PHASE_START = 0,
265         UTILITY_PHASE_INTERMEDIATE,
266         UTILITY_PHASE_ENTERING,
267         UTILITY_PHASE_CHECKED,
268         UTILITY_PHASE_DONE,
269         UTILITY_PHASE_CANCEL,
270         UTILITY_PHASE_DISCARD
271 } UtilityPhase;
272
273 enum {
274         UTILITY_RENAME = 0,
275         UTILITY_RENAME_AUTO,
276         UTILITY_RENAME_FORMATTED
277 };
278
279 typedef struct _UtilityDataMessages UtilityDataMessages;
280 struct _UtilityDataMessages {
281         const gchar *title;
282         const gchar *question;
283         const gchar *desc_flist;
284         const gchar *desc_source_fd;
285         const gchar *fail;
286 };
287
288 typedef struct _UtilityData UtilityData;
289
290 struct _UtilityData {
291         UtilityType type;
292         UtilityPhase phase;
293
294         FileData *dir_fd;
295         GList *content_list;
296         GList *flist;
297
298         FileData *sel_fd;
299
300         GtkWidget *parent;
301         GenericDialog *gd;
302         FileDialog *fdlg;
303
304         guint update_idle_id; /* event source id */
305         guint perform_idle_id; /* event source id */
306
307         gboolean with_sidecars; /* operate on grouped or single files; TRUE = use file_data_sc_, FALSE = use file_data_ functions */
308
309         /* alternative dialog parts */
310         GtkWidget *notebook;
311
312         UtilityDataMessages messages;
313
314         /* helper entries for various modes */
315         GtkWidget *rename_entry;
316         GtkWidget *rename_label;
317         GtkWidget *auto_entry_front;
318         GtkWidget *auto_entry_end;
319         GtkWidget *auto_spin_start;
320         GtkWidget *auto_spin_pad;
321         GtkWidget *format_entry;
322         GtkWidget *format_spin;
323
324         GtkWidget *listview;
325
326
327         gchar *dest_path;
328
329         /* data for the operation itself, internal or external */
330         gboolean external; /* TRUE for external command, FALSE for internal */
331
332         gchar *external_command;
333         gpointer resume_data;
334
335         FileUtilDoneFunc done_func;
336         void (*details_func)(UtilityData *ud, FileData *fd);
337         gboolean (*finalize_func)(FileData *fd);
338         gboolean (*discard_func)(FileData *fd);
339         gpointer done_data;
340 };
341
342 enum {
343         UTILITY_COLUMN_FD = 0,
344         UTILITY_COLUMN_PIXBUF,
345         UTILITY_COLUMN_PATH,
346         UTILITY_COLUMN_NAME,
347         UTILITY_COLUMN_SIDECARS,
348         UTILITY_COLUMN_DEST_PATH,
349         UTILITY_COLUMN_DEST_NAME,
350         UTILITY_COLUMN_COUNT
351 };
352
353 typedef struct _UtilityDelayData UtilityDelayData;
354
355 struct _UtilityDelayData {
356         UtilityType type;
357         UtilityPhase phase;
358         GList *flist;
359         gchar *dest_path;
360         gchar *editor_key;
361         GtkWidget *parent;
362         guint idle_id; /* event source id */
363         };
364
365 static void generic_dialog_image_set(UtilityData *ud, FileData *fd)
366 {
367         ImageWindow *imd;
368         GtkWidget *label;
369         FileData *fd2 = NULL;
370         gchar *buf;
371
372         imd = static_cast<ImageWindow *>(g_object_get_data(G_OBJECT(ud->gd->dialog), "img_image"));
373         label = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(ud->gd->dialog), "img_label"));
374
375         if (!imd) return;
376
377         image_change_fd(imd, fd, 0.0);
378         buf = g_strjoin("\n", text_from_time(fd->date), text_from_size(fd->size), NULL);
379         if (label) gtk_label_set_text(GTK_LABEL(label), buf);
380         g_free(buf);
381
382         if (ud->type == UTILITY_TYPE_RENAME || ud->type == UTILITY_TYPE_COPY || ud->type == UTILITY_TYPE_MOVE)
383                 {
384                 imd = static_cast<ImageWindow *>(g_object_get_data(G_OBJECT(ud->gd->dialog), "img_image2"));
385                 label = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(ud->gd->dialog), "img_label2"));
386
387                 if (imd)
388                         {
389                         if (isfile(fd->change->dest))
390                                 {
391                                 fd2 = file_data_new_group(fd->change->dest);
392                                 image_change_fd(imd, fd2, 0.0);
393                                 buf = g_strjoin("\n", text_from_time(fd2->date), text_from_size(fd2->size), NULL);
394                                 if (label && fd->change->dest) gtk_label_set_text(GTK_LABEL(label), buf);
395                                 file_data_unref(fd2);
396                                 g_free(buf);
397                                 }
398                         else
399                                 {
400                                 image_change_fd(imd, NULL, 0.0);
401                                 if (label) gtk_label_set_text(GTK_LABEL(label), "");
402                                 }
403                         }
404                 }
405 }
406
407 static gboolean file_util_write_metadata_first(UtilityType type, UtilityPhase phase, GList *flist, const gchar *dest_path, const gchar *editor_key, GtkWidget *parent);
408
409 #define UTILITY_LIST_MIN_WIDTH  250
410 #define UTILITY_LIST_MIN_HEIGHT 150
411
412 /* thumbnail spec has a max depth of 4 (.thumb??/fail/appname/??.png) */
413 #define UTILITY_DELETE_MAX_DEPTH 5
414
415 static UtilityData *file_util_data_new(UtilityType type)
416 {
417         UtilityData *ud;
418
419         ud = g_new0(UtilityData, 1);
420
421         ud->type = type;
422         ud->phase = UTILITY_PHASE_START;
423
424         return ud;
425 }
426
427 static void file_util_data_free(UtilityData *ud)
428 {
429         if (!ud) return;
430
431         if (ud->update_idle_id) g_source_remove(ud->update_idle_id);
432         if (ud->perform_idle_id) g_source_remove(ud->perform_idle_id);
433
434         file_data_unref(ud->dir_fd);
435         filelist_free(ud->content_list);
436         filelist_free(ud->flist);
437
438         if (ud->gd) generic_dialog_close(ud->gd);
439
440         g_free(ud->dest_path);
441         g_free(ud->external_command);
442
443         g_free(ud);
444 }
445
446 static GtkTreeViewColumn *file_util_dialog_add_list_column(GtkWidget *view, const gchar *text, gboolean image, gint n)
447 {
448         GtkTreeViewColumn *column;
449         GtkCellRenderer *renderer;
450
451         column = gtk_tree_view_column_new();
452         gtk_tree_view_column_set_title(column, text);
453         gtk_tree_view_column_set_min_width(column, 4);
454         if (image)
455                 {
456                 gtk_tree_view_column_set_min_width(column, 20);
457                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
458                 renderer = gtk_cell_renderer_pixbuf_new();
459                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
460                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
461                 }
462         else
463                 {
464                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
465                 renderer = gtk_cell_renderer_text_new();
466                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
467                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
468                 }
469         gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
470
471         return column;
472 }
473
474 static void file_util_dialog_list_select(GtkWidget *view, gint n)
475 {
476         GtkTreeModel *store;
477         GtkTreeIter iter;
478         GtkTreeSelection *selection;
479
480         store = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
481         if (gtk_tree_model_iter_nth_child(store, &iter, NULL, n))
482                 {
483                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
484                 gtk_tree_selection_select_iter(selection, &iter);
485                 }
486 }
487
488 static GtkWidget *file_util_dialog_add_list(GtkWidget *box, GList *list, gboolean full_paths, gboolean with_sidecars)
489 {
490         GtkWidget *scrolled;
491         GtkWidget *view;
492         GtkListStore *store;
493
494         scrolled = gtk_scrolled_window_new(NULL, NULL);
495         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
496         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
497                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
498         gtk_box_pack_start(GTK_BOX(box), scrolled, TRUE, TRUE, 0);
499         gtk_widget_show(scrolled);
500
501         store = gtk_list_store_new(UTILITY_COLUMN_COUNT, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
502         view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
503         g_object_unref(store);
504
505         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
506         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), FALSE);
507
508         file_util_dialog_add_list_column(view, "", TRUE, UTILITY_COLUMN_PIXBUF);
509
510         if (full_paths)
511                 {
512                 file_util_dialog_add_list_column(view, _("Path"), FALSE, UTILITY_COLUMN_PATH);
513                 }
514         else
515                 {
516                 file_util_dialog_add_list_column(view, _("Name"), FALSE, UTILITY_COLUMN_NAME);
517                 }
518
519         gtk_widget_set_size_request(view, UTILITY_LIST_MIN_WIDTH, UTILITY_LIST_MIN_HEIGHT);
520         gtk_container_add(GTK_CONTAINER(scrolled), view);
521         gtk_widget_show(view);
522
523         while (list)
524                 {
525                 FileData *fd = static_cast<FileData *>(list->data);
526                 GtkTreeIter iter;
527                 gchar *sidecars;
528
529                 sidecars = with_sidecars ? file_data_sc_list_to_string(fd) : NULL;
530                 GdkPixbuf *icon = file_util_get_error_icon(fd, list, view);
531                 gtk_list_store_append(store, &iter);
532                 gtk_list_store_set(store, &iter,
533                                    UTILITY_COLUMN_FD, fd,
534                                    UTILITY_COLUMN_PIXBUF, icon,
535                                    UTILITY_COLUMN_PATH, fd->path,
536                                    UTILITY_COLUMN_NAME, fd->name,
537                                    UTILITY_COLUMN_SIDECARS, sidecars,
538                                    UTILITY_COLUMN_DEST_PATH, fd->change ? fd->change->dest : "error",
539                                    UTILITY_COLUMN_DEST_NAME, fd->change ? filename_from_path(fd->change->dest) : "error",
540                                    -1);
541                 g_free(sidecars);
542
543                 list = list->next;
544                 }
545
546         return view;
547 }
548
549
550 static gboolean file_util_perform_ci_internal(gpointer data);
551 void file_util_dialog_run(UtilityData *ud);
552 static gint file_util_perform_ci_cb(gpointer resume_data, EditorFlags flags, GList *list, gpointer data);
553
554 /* call file_util_perform_ci_internal or start_editor_from_filelist_full */
555
556
557 static void file_util_resume_cb(GenericDialog *UNUSED(gd), gpointer data)
558 {
559         UtilityData *ud = static_cast<UtilityData *>(data);
560         if (ud->external)
561                 editor_resume(ud->resume_data);
562         else
563                 file_util_perform_ci_internal(ud);
564 }
565
566 static void file_util_abort_cb(GenericDialog *UNUSED(gd), gpointer data)
567 {
568         UtilityData *ud = static_cast<UtilityData *>(data);
569         if (ud->external)
570                 editor_skip(ud->resume_data);
571         else
572                 file_util_perform_ci_cb(NULL, EDITOR_ERROR_SKIPPED, ud->flist, ud);
573
574 }
575
576
577 static gint file_util_perform_ci_cb(gpointer resume_data, EditorFlags flags, GList *list, gpointer data)
578 {
579         UtilityData *ud = static_cast<UtilityData *>(data);
580         gint ret = EDITOR_CB_CONTINUE;
581
582         ud->resume_data = resume_data;
583
584         if (EDITOR_ERRORS_BUT_SKIPPED(flags))
585                 {
586                 GString *msg = g_string_new(editor_get_error_str(flags));
587                 GenericDialog *d;
588                 g_string_append(msg, "\n");
589                 g_string_append(msg, ud->messages.fail);
590                 g_string_append(msg, "\n");
591                 while (list)
592                         {
593                         FileData *fd = static_cast<FileData *>(list->data);
594
595                         g_string_append(msg, fd->path);
596                         g_string_append(msg, "\n");
597                         list = list->next;
598                         }
599                 if (resume_data)
600                         {
601                         g_string_append(msg, _("\n Continue multiple file operation?"));
602                         d = file_util_gen_dlg(ud->messages.fail, "dlg_confirm",
603                                               NULL, TRUE,
604                                               file_util_abort_cb, ud);
605
606                         generic_dialog_add_message(d, GTK_STOCK_DIALOG_WARNING, NULL, msg->str, TRUE);
607
608                         generic_dialog_add_button(d, GTK_STOCK_GO_FORWARD, _("Co_ntinue"),
609                                                   file_util_resume_cb, TRUE);
610                         gtk_widget_show(d->dialog);
611                         ret = EDITOR_CB_SUSPEND;
612                         }
613                 else
614                         {
615                         file_util_warning_dialog(ud->messages.fail, msg->str, GTK_STOCK_DIALOG_ERROR, NULL);
616                         }
617                 g_string_free(msg, TRUE);
618                 }
619
620
621         while (list)  /* be careful, file_util_perform_ci_internal can pass ud->flist as list */
622                 {
623                 FileData *fd = static_cast<FileData *>(list->data);
624                 list = list->next;
625
626                 if (!EDITOR_ERRORS(flags)) /* files were successfully deleted, call the maint functions */
627                         {
628                         if (ud->with_sidecars)
629                                 file_data_sc_apply_ci(fd);
630                         else
631                                 file_data_apply_ci(fd);
632                         }
633
634                 ud->flist = g_list_remove(ud->flist, fd);
635
636                 if (ud->finalize_func)
637                         {
638                         ud->finalize_func(fd);
639                         }
640
641                 if (ud->with_sidecars)
642                         file_data_sc_free_ci(fd);
643                 else
644                         file_data_free_ci(fd);
645                 file_data_unref(fd);
646                 }
647
648         if (!resume_data) /* end of the list */
649                 {
650                 ud->phase = UTILITY_PHASE_DONE;
651                 file_util_dialog_run(ud);
652                 }
653
654         return ret;
655 }
656
657
658 /*
659  * Perform the operation described by FileDataChangeInfo on all files in the list
660  * it is an alternative to start_editor_from_filelist_full, it should use similar interface
661  */
662
663
664 static gboolean file_util_perform_ci_internal(gpointer data)
665 {
666         UtilityData *ud = static_cast<UtilityData *>(data);
667
668         if (!ud->perform_idle_id)
669                 {
670                 /* this function was called directly
671                    just setup idle callback and wait until we are called again
672                 */
673
674                 /* this is removed when ud is destroyed */
675                 ud->perform_idle_id = g_idle_add(file_util_perform_ci_internal, ud);
676                 return TRUE;
677                 }
678
679         g_assert(ud->flist);
680
681         if (ud->flist)
682                 {
683                 gint ret;
684
685                 /* take a single entry each time, this allows better control over the operation */
686                 GList *single_entry = g_list_append(NULL, ud->flist->data);
687                 gboolean last = !ud->flist->next;
688                 EditorFlags status = EDITOR_ERROR_STATUS;
689
690                 if (ud->with_sidecars ? file_data_sc_perform_ci(static_cast<FileData *>(single_entry->data))
691                                       : file_data_perform_ci(static_cast<FileData *>(single_entry->data)))
692                         status = static_cast<EditorFlags>(0); /* OK */
693
694                 ret = file_util_perform_ci_cb(GINT_TO_POINTER(!last), status, single_entry, ud);
695                 g_list_free(single_entry);
696
697                 if (ret == EDITOR_CB_SUSPEND || last) return FALSE;
698
699                 if (ret == EDITOR_CB_SKIP)
700                         {
701                         file_util_perform_ci_cb(NULL, EDITOR_ERROR_SKIPPED, ud->flist, ud);
702                         return FALSE;
703                         }
704                 }
705
706         return TRUE;
707 }
708
709 static void file_util_perform_ci_dir(UtilityData *ud, gboolean internal, gboolean ext_result)
710 {
711         switch (ud->type)
712                 {
713                 case UTILITY_TYPE_DELETE_LINK:
714                         {
715                         g_assert(ud->dir_fd->sidecar_files == NULL); // directories should not have sidecars
716                         if ((internal && file_data_perform_ci(ud->dir_fd)) ||
717                             (!internal && ext_result))
718                                 {
719                                 file_data_apply_ci(ud->dir_fd);
720                                 }
721                         else
722                                 {
723                                 gchar *text;
724
725                                 text = g_strdup_printf("%s:\n\n%s", ud->messages.fail, ud->dir_fd->path);
726                                 file_util_warning_dialog(ud->messages.fail, text, GTK_STOCK_DIALOG_ERROR, NULL);
727                                 g_free(text);
728                                 }
729                         file_data_free_ci(ud->dir_fd);
730                         break;
731                         }
732                 case UTILITY_TYPE_DELETE_FOLDER:
733                         {
734                         FileData *fail = NULL;
735                         GList *work;
736                         work = ud->content_list;
737                         while (work)
738                                 {
739                                 FileData *fd;
740
741                                 fd = static_cast<FileData *>(work->data);
742                                 work = work->next;
743
744                                 if (!fail)
745                                         {
746                                         if ((internal && file_data_sc_perform_ci(fd)) ||
747                                             (!internal && ext_result))
748                                                 {
749                                                 file_data_sc_apply_ci(fd);
750                                                 }
751                                         else
752                                                 {
753                                                 if (internal) fail = file_data_ref(fd);
754                                                 }
755                                         }
756                                 file_data_sc_free_ci(fd);
757                                 }
758
759                         if (!fail)
760                                 {
761                                 g_assert(ud->dir_fd->sidecar_files == NULL); // directories should not have sidecars
762                                 if ((internal && file_data_sc_perform_ci(ud->dir_fd)) ||
763                                     (!internal && ext_result))
764                                         {
765                                         file_data_apply_ci(ud->dir_fd);
766                                         }
767                                 else
768                                         {
769                                         fail = file_data_ref(ud->dir_fd);
770                                         }
771                                 }
772
773                         if (fail)
774                                 {
775                                 gchar *text;
776                                 GenericDialog *gd;
777
778                                 text = g_strdup_printf("%s:\n\n%s", ud->messages.fail, ud->dir_fd->path);
779                                 gd = file_util_warning_dialog(ud->messages.fail, text, GTK_STOCK_DIALOG_ERROR, NULL);
780                                 g_free(text);
781
782                                 if (fail != ud->dir_fd)
783                                         {
784                                         pref_spacer(gd->vbox, PREF_PAD_GROUP);
785                                         text = g_strdup_printf(_("Removal of folder contents failed at this file:\n\n%s"),
786                                                                 fail->path);
787                                         pref_label_new(gd->vbox, text);
788                                         g_free(text);
789                                         }
790
791                                 file_data_unref(fail);
792                                 }
793                         break;
794                         }
795                 case UTILITY_TYPE_RENAME_FOLDER:
796                         {
797                         FileData *fail = NULL;
798                         GList *work;
799                         g_assert(ud->dir_fd->sidecar_files == NULL); // directories should not have sidecars
800
801                         if ((internal && file_data_sc_perform_ci(ud->dir_fd)) ||
802                             (!internal && ext_result))
803                                 {
804                                 file_data_sc_apply_ci(ud->dir_fd);
805                                 }
806                         else
807                                 {
808                                 fail = file_data_ref(ud->dir_fd);
809                                 }
810
811
812                         work = ud->content_list;
813                         while (work)
814                                 {
815                                 FileData *fd;
816
817                                 fd = static_cast<FileData *>(work->data);
818                                 work = work->next;
819
820                                 if (!fail)
821                                         {
822                                         file_data_sc_apply_ci(fd);
823                                         }
824                                 file_data_sc_free_ci(fd);
825                                 }
826
827                         if (fail)
828                                 {
829                                 gchar *text;
830
831                                 text = g_strdup_printf("%s:\n\n%s", ud->messages.fail, ud->dir_fd->path);
832                                 (void) file_util_warning_dialog(ud->messages.fail, text, GTK_STOCK_DIALOG_ERROR, NULL);
833                                 g_free(text);
834
835                                 file_data_unref(fail);
836                                 }
837                         break;
838                         }
839                 case UTILITY_TYPE_CREATE_FOLDER:
840                         {
841                         if ((internal && mkdir_utf8(ud->dir_fd->path, 0755)) ||
842                             (!internal && ext_result))
843                                 {
844                                 file_data_check_changed_files(ud->dir_fd); /* this will update the FileData and send notification */
845                                 }
846                         else
847                                 {
848                                 gchar *text;
849
850                                 text = g_strdup_printf("%s:\n\n%s", ud->messages.fail, ud->dir_fd->path);
851                                 (void) file_util_warning_dialog(ud->messages.fail, text, GTK_STOCK_DIALOG_ERROR, NULL);
852                                 g_free(text);
853                                 }
854
855                         break;
856                         }
857                 default:
858                         g_warning("unhandled operation");
859                 }
860         ud->phase = UTILITY_PHASE_DONE;
861         file_util_dialog_run(ud);
862 }
863
864 static gint file_util_perform_ci_dir_cb(gpointer UNUSED(resume_data), EditorFlags flags, GList *UNUSED(list), gpointer data)
865 {
866         UtilityData *ud = static_cast<UtilityData *>(data);
867         file_util_perform_ci_dir(ud, FALSE, !EDITOR_ERRORS_BUT_SKIPPED(flags));
868         return EDITOR_CB_CONTINUE; /* does not matter, there was just single directory */
869 }
870
871 void file_util_perform_ci(UtilityData *ud)
872 {
873         switch (ud->type)
874                 {
875                 case UTILITY_TYPE_COPY:
876                         ud->external_command = g_strdup(CMD_COPY);
877                         break;
878                 case UTILITY_TYPE_MOVE:
879                         ud->external_command = g_strdup(CMD_MOVE);
880                         break;
881                 case UTILITY_TYPE_RENAME:
882                 case UTILITY_TYPE_RENAME_FOLDER:
883                         ud->external_command = g_strdup(CMD_RENAME);
884                         break;
885                 case UTILITY_TYPE_DELETE:
886                 case UTILITY_TYPE_DELETE_LINK:
887                 case UTILITY_TYPE_DELETE_FOLDER:
888                         ud->external_command = g_strdup(CMD_DELETE);
889                         break;
890                 case UTILITY_TYPE_CREATE_FOLDER:
891                         ud->external_command = g_strdup(CMD_FOLDER);
892                         break;
893                 case UTILITY_TYPE_FILTER:
894                 case UTILITY_TYPE_EDITOR:
895                         g_assert(ud->external_command != NULL); /* it should be already set */
896                         break;
897                 case UTILITY_TYPE_WRITE_METADATA:
898                         ud->external_command = NULL;
899                 }
900
901         if (is_valid_editor_command(ud->external_command))
902                 {
903                 EditorFlags flags;
904
905                 ud->external = TRUE;
906
907                 if (ud->dir_fd)
908                         {
909                         flags = start_editor_from_file_full(ud->external_command, ud->dir_fd, file_util_perform_ci_dir_cb, ud);
910                         }
911                 else
912                         {
913                         if (editor_blocks_file(ud->external_command))
914                                 {
915                                 DEBUG_1("Starting %s and waiting for results", ud->external_command);
916                                 flags = start_editor_from_filelist_full(ud->external_command, ud->flist, NULL, file_util_perform_ci_cb, ud);
917                                 }
918                         else
919                                 {
920                                 /* start the editor without callback and finish the operation internally */
921                                 DEBUG_1("Starting %s and finishing the operation", ud->external_command);
922                                 flags = start_editor_from_filelist(ud->external_command, ud->flist);
923                                 file_util_perform_ci_internal(ud);
924                                 }
925                         }
926
927                 if (EDITOR_ERRORS(flags))
928                         {
929                         gchar *text = g_strdup_printf(_("%s\nUnable to start external command.\n"), editor_get_error_str(flags));
930                         file_util_warning_dialog(ud->messages.fail, text, GTK_STOCK_DIALOG_ERROR, NULL);
931                         g_free(text);
932
933                         ud->gd = NULL;
934                         ud->phase = UTILITY_PHASE_CANCEL;
935                         file_util_dialog_run(ud);
936                         }
937                 }
938         else
939                 {
940                 ud->external = FALSE;
941                 if (ud->dir_fd)
942                         {
943                         file_util_perform_ci_dir(ud, TRUE, FALSE);
944                         }
945                 else
946                         {
947                         file_util_perform_ci_internal(ud);
948                         }
949                 }
950 }
951
952 static GdkPixbuf *file_util_get_error_icon(FileData *fd, GList *list, GtkWidget *widget)
953 {
954         static GdkPixbuf *pb_warning;
955         static GdkPixbuf *pb_error;
956         static GdkPixbuf *pb_apply;
957         gint error;
958
959         if (!pb_warning)
960                 {
961                 pb_warning = gtk_widget_render_icon(widget, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU, NULL);
962                 }
963
964         if (!pb_error)
965                 {
966                 pb_error = gtk_widget_render_icon(widget, GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU, NULL);
967                 }
968
969         if (!pb_apply)
970                 {
971                 pb_apply = gtk_widget_render_icon(widget, GTK_STOCK_APPLY, GTK_ICON_SIZE_MENU, NULL);
972                 }
973
974         error = file_data_sc_verify_ci(fd, list);
975
976         if (!error) return pb_apply;
977
978         if (error & CHANGE_ERROR_MASK)
979                 {
980                 return pb_error;
981                 }
982         else
983                 {
984                 return pb_warning;
985                 }
986 }
987
988 static void file_util_check_resume_cb(GenericDialog *UNUSED(gd), gpointer data)
989 {
990         UtilityData *ud = static_cast<UtilityData *>(data);
991         ud->phase = UTILITY_PHASE_CHECKED;
992         file_util_dialog_run(ud);
993 }
994
995 static void file_util_check_abort_cb(GenericDialog *UNUSED(gd), gpointer data)
996 {
997         UtilityData *ud = static_cast<UtilityData *>(data);
998         ud->phase = UTILITY_PHASE_START;
999         file_util_dialog_run(ud);
1000 }
1001
1002 void file_util_check_ci(UtilityData *ud)
1003 {
1004         gint error = CHANGE_OK;
1005         gchar *desc = NULL;
1006
1007         if (ud->type != UTILITY_TYPE_CREATE_FOLDER &&
1008             ud->type != UTILITY_TYPE_RENAME_FOLDER)
1009                 {
1010                 if (ud->dest_path && !isdir(ud->dest_path))
1011                         {
1012                         error = CHANGE_GENERIC_ERROR;
1013                         desc = g_strdup_printf(_("%s is not a directory"), ud->dest_path);
1014                         }
1015                 else if (ud->dir_fd)
1016                         {
1017                         g_assert(ud->dir_fd->sidecar_files == NULL); // directories should not have sidecars
1018                         error = file_data_verify_ci(ud->dir_fd, ud->flist);
1019                         if (error) desc = file_data_get_error_string(error);
1020                         }
1021                 else
1022                         {
1023                         error = file_data_verify_ci_list(ud->flist, &desc, ud->with_sidecars);
1024                         }
1025                 }
1026         else
1027                 {
1028                 if (ud->type == UTILITY_TYPE_CREATE_FOLDER || ud->type == UTILITY_TYPE_RENAME_FOLDER)
1029                         {
1030                         if (isdir(ud->dest_path) || isfile(ud->dest_path))
1031                                 {
1032                                 error = CHANGE_DEST_EXISTS;
1033                                 desc = g_strdup_printf(_("%s already exists"), ud->dest_path);
1034                                 }
1035                         }
1036                 }
1037
1038         if (!error)
1039                 {
1040                 ud->phase = UTILITY_PHASE_CHECKED;
1041                 file_util_dialog_run(ud);
1042                 return;
1043                 }
1044
1045         if (!(error & CHANGE_ERROR_MASK))
1046                 {
1047                 /* just a warning */
1048                 GenericDialog *d;
1049
1050                 d = file_util_gen_dlg(ud->messages.title, "dlg_confirm",
1051                                         ud->parent, TRUE,
1052                                         file_util_check_abort_cb, ud);
1053
1054                 generic_dialog_add_message(d, GTK_STOCK_DIALOG_WARNING, _("Really continue?"), desc, TRUE);
1055
1056                 generic_dialog_add_button(d, GTK_STOCK_GO_FORWARD, _("Co_ntinue"),
1057                                           file_util_check_resume_cb, TRUE);
1058                 gtk_widget_show(d->dialog);
1059                 }
1060         else
1061                 {
1062                 /* fatal error */
1063                 GenericDialog *d;
1064
1065                 d = file_util_gen_dlg(ud->messages.title, "dlg_confirm",
1066                                         ud->parent, TRUE,
1067                                         file_util_check_abort_cb, ud);
1068                 generic_dialog_add_message(d, GTK_STOCK_DIALOG_WARNING, _("This operation can't continue:"), desc, TRUE);
1069
1070                 gtk_widget_show(d->dialog);
1071                 }
1072         g_free(desc);
1073 }
1074
1075
1076
1077
1078
1079 static void file_util_cancel_cb(GenericDialog *gd, gpointer data)
1080 {
1081         UtilityData *ud = static_cast<UtilityData *>(data);
1082
1083         generic_dialog_close(gd);
1084
1085         ud->gd = NULL;
1086
1087         ud->phase = UTILITY_PHASE_CANCEL;
1088         file_util_dialog_run(ud);
1089 }
1090
1091 static void file_util_discard_cb(GenericDialog *gd, gpointer data)
1092 {
1093         UtilityData *ud = static_cast<UtilityData *>(data);
1094
1095         generic_dialog_close(gd);
1096
1097         ud->gd = NULL;
1098
1099         ud->phase = UTILITY_PHASE_DISCARD;
1100         file_util_dialog_run(ud);
1101 }
1102
1103 static void file_util_ok_cb(GenericDialog *gd, gpointer data)
1104 {
1105         UtilityData *ud = static_cast<UtilityData *>(data);
1106
1107         generic_dialog_close(gd);
1108
1109         ud->gd = NULL;
1110
1111         file_util_dialog_run(ud);
1112 }
1113
1114 static void file_util_fdlg_cancel_cb(FileDialog *fdlg, gpointer data)
1115 {
1116         UtilityData *ud = static_cast<UtilityData *>(data);
1117
1118         file_dialog_close(fdlg);
1119
1120         ud->fdlg = NULL;
1121
1122         ud->phase = UTILITY_PHASE_CANCEL;
1123         file_util_dialog_run(ud);
1124 }
1125
1126 static void file_util_dest_folder_update_path(UtilityData *ud)
1127 {
1128         g_free(ud->dest_path);
1129         ud->dest_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(ud->fdlg->entry)));
1130
1131         switch (ud->type)
1132                 {
1133                 case UTILITY_TYPE_COPY:
1134                         file_data_sc_update_ci_copy_list(ud->flist, ud->dest_path);
1135                         break;
1136                 case UTILITY_TYPE_MOVE:
1137                         file_data_sc_update_ci_move_list(ud->flist, ud->dest_path);
1138                         break;
1139                 case UTILITY_TYPE_FILTER:
1140                 case UTILITY_TYPE_EDITOR:
1141                         file_data_sc_update_ci_unspecified_list(ud->flist, ud->dest_path);
1142                         break;
1143                 case UTILITY_TYPE_CREATE_FOLDER:
1144                         file_data_unref(ud->dir_fd);
1145                         ud->dir_fd = file_data_new_dir(ud->dest_path);
1146                         break;
1147                 case UTILITY_TYPE_DELETE:
1148                 case UTILITY_TYPE_DELETE_LINK:
1149                 case UTILITY_TYPE_DELETE_FOLDER:
1150                 case UTILITY_TYPE_RENAME:
1151                 case UTILITY_TYPE_RENAME_FOLDER:
1152                 case UTILITY_TYPE_WRITE_METADATA:
1153                         g_warning("unhandled operation");
1154                 }
1155 }
1156
1157 static void file_util_fdlg_rename_cb(FileDialog *fdlg, gpointer data)
1158 {
1159         UtilityData *ud = static_cast<UtilityData *>(data);
1160         gchar *desc = NULL;
1161         GenericDialog *d = NULL;
1162
1163         file_util_dest_folder_update_path(ud);
1164         if (isdir(ud->dest_path))
1165                 {
1166                 file_dialog_sync_history(fdlg, TRUE);
1167                 file_dialog_close(fdlg);
1168                 ud->fdlg = NULL;
1169                 file_util_dialog_run(ud);
1170                 }
1171         else
1172                 {
1173                 /* During copy/move operations it is necessary to ensure that the
1174                  * target directory exists before continuing with the next step.
1175                  * If not revert to the select directory dialog
1176                  */
1177                 desc = g_strdup_printf(_("%s is not a directory"), ud->dest_path);
1178
1179                 d = file_util_gen_dlg(ud->messages.title, "dlg_confirm",
1180                                         ud->parent, TRUE,
1181                                         file_util_check_abort_cb, ud);
1182                 generic_dialog_add_message(d, GTK_STOCK_DIALOG_WARNING, _("This operation can't continue:"), desc, TRUE);
1183
1184                 gtk_widget_show(d->dialog);
1185                 ud->phase = UTILITY_PHASE_START;
1186
1187                 file_dialog_close(fdlg);
1188                 ud->fdlg = NULL;
1189                 g_free(desc);
1190                 }
1191 }
1192
1193 static void file_util_fdlg_ok_cb(FileDialog *fdlg, gpointer data)
1194 {
1195         UtilityData *ud = static_cast<UtilityData *>(data);
1196
1197         file_util_dest_folder_update_path(ud);
1198         if (isdir(ud->dest_path)) file_dialog_sync_history(fdlg, TRUE);
1199         file_dialog_close(fdlg);
1200
1201         ud->fdlg = NULL;
1202         ud->phase = UTILITY_PHASE_ENTERING;
1203
1204         file_util_dialog_run(ud);
1205
1206         return;
1207 }
1208
1209 static void file_util_dest_folder_entry_cb(GtkWidget *UNUSED(entry), gpointer data)
1210 {
1211         UtilityData *ud = static_cast<UtilityData *>(data);
1212         file_util_dest_folder_update_path(ud);
1213 }
1214
1215 /* format: * = filename without extension, ## = number position, extension is kept */
1216 static gchar *file_util_rename_multiple_auto_format_name(const gchar *format, const gchar *name, gint n)
1217 {
1218         gchar *new_name;
1219         gchar *parsed;
1220         const gchar *ext;
1221         gchar *middle;
1222         gchar *tmp;
1223         gchar *pad_start;
1224         gchar *pad_end;
1225         gint padding;
1226
1227         if (!format || !name) return NULL;
1228
1229         tmp = g_strdup(format);
1230         pad_start = strchr(tmp, '#');
1231         if (pad_start)
1232                 {
1233                 pad_end = pad_start;
1234                 padding = 0;
1235                 while (*pad_end == '#')
1236                         {
1237                         pad_end++;
1238                         padding++;
1239                         }
1240                 *pad_start = '\0';
1241
1242                 parsed = g_strdup_printf("%s%0*d%s", tmp, padding, n, pad_end);
1243                 g_free(tmp);
1244                 }
1245         else
1246                 {
1247                 parsed = tmp;
1248                 }
1249
1250         ext = registered_extension_from_path(name);
1251
1252         middle = strchr(parsed, '*');
1253         if (middle)
1254                 {
1255                 gchar *base;
1256
1257                 *middle = '\0';
1258                 middle++;
1259
1260                 base = remove_extension_from_path(name);
1261                 new_name = g_strconcat(parsed, base, middle, ext, NULL);
1262                 g_free(base);
1263                 }
1264         else
1265                 {
1266                 new_name = g_strconcat(parsed, ext, NULL);
1267                 }
1268
1269         g_free(parsed);
1270
1271         return new_name;
1272 }
1273
1274
1275 static void file_util_rename_preview_update(UtilityData *ud)
1276 {
1277         GtkTreeModel *store;
1278         GtkTreeSelection *selection;
1279         GtkTreeIter iter, iter_selected;
1280         GtkTreePath *path_iter, *path_selected;
1281         const gchar *front;
1282         const gchar *end;
1283         const gchar *format;
1284         gboolean valid;
1285         gint start_n;
1286         gint padding;
1287         gint n;
1288         gint mode;
1289         gchar *dirname;
1290         gchar *destname;
1291
1292         mode = gtk_notebook_get_current_page(GTK_NOTEBOOK(ud->notebook));
1293
1294         if (mode == UTILITY_RENAME)
1295                 {
1296                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ud->listview));
1297                 if (gtk_tree_selection_get_selected(selection, &store, &iter))
1298                         {
1299                         FileData *fd;
1300                         const gchar *dest = gtk_entry_get_text(GTK_ENTRY(ud->rename_entry));
1301
1302                         gtk_tree_model_get(store, &iter, UTILITY_COLUMN_FD, &fd, -1);
1303                         g_assert(ud->with_sidecars); /* sidecars must be renamed too, it would break the pairing otherwise */
1304
1305                         dirname = g_path_get_dirname(fd->change->dest);
1306                         destname = g_build_filename(dirname, dest, NULL);
1307                         switch (ud->type)
1308                                 {
1309                                 case UTILITY_TYPE_RENAME:
1310                                         file_data_sc_update_ci_rename(fd, dest);
1311                                         break;
1312                                 case UTILITY_TYPE_COPY:
1313                                         file_data_sc_update_ci_copy(fd, destname);
1314                                         break;
1315                                 case UTILITY_TYPE_MOVE:
1316                                         file_data_sc_update_ci_move(fd, destname);
1317                                         break;
1318                                 default:;
1319                                 }
1320                         generic_dialog_image_set(ud, fd);
1321
1322                         gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1323                                    UTILITY_COLUMN_DEST_PATH, fd->change->dest,
1324                                    UTILITY_COLUMN_DEST_NAME, filename_from_path(fd->change->dest),
1325                                    -1);
1326                         }
1327                 }
1328         else
1329                 {
1330                 front = gtk_entry_get_text(GTK_ENTRY(ud->auto_entry_front));
1331                 end = gtk_entry_get_text(GTK_ENTRY(ud->auto_entry_end));
1332                 padding = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ud->auto_spin_pad));
1333
1334                 format = gtk_entry_get_text(GTK_ENTRY(ud->format_entry));
1335
1336                 g_free(options->cp_mv_rn.auto_end);
1337                 options->cp_mv_rn.auto_end = g_strdup(end);
1338                 options->cp_mv_rn.auto_padding = padding;
1339
1340                 if (mode == UTILITY_RENAME_FORMATTED)
1341                         {
1342                         start_n = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ud->format_spin));
1343                         options->cp_mv_rn.formatted_start = start_n;
1344                         }
1345                 else
1346                         {
1347                         start_n = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ud->auto_spin_start));
1348                         options->cp_mv_rn.auto_start = start_n;
1349                         }
1350
1351                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(ud->listview));
1352                 n = start_n;
1353                 valid = gtk_tree_model_get_iter_first(store, &iter);
1354                 while (valid)
1355                         {
1356                         gchar *dest;
1357                         FileData *fd;
1358                         gtk_tree_model_get(store, &iter, UTILITY_COLUMN_FD, &fd, -1);
1359
1360                         if (mode == UTILITY_RENAME_FORMATTED)
1361                                 {
1362                                 dest = file_util_rename_multiple_auto_format_name(format, fd->name, n);
1363                                 }
1364                         else
1365                                 {
1366                                 dest = g_strdup_printf("%s%0*d%s", front, padding, n, end);
1367                                 }
1368
1369                         g_assert(ud->with_sidecars); /* sidecars must be renamed too, it would break the pairing otherwise */
1370
1371                         dirname = g_path_get_dirname(fd->change->dest);
1372                         destname = g_build_filename(dirname, dest, NULL);
1373
1374                         switch (ud->type)
1375                                 {
1376                                 case UTILITY_TYPE_RENAME:
1377                                         file_data_sc_update_ci_rename(fd, dest);
1378                                         break;
1379                                 case UTILITY_TYPE_COPY:
1380                                         file_data_sc_update_ci_copy(fd, destname);
1381                                         break;
1382                                 case UTILITY_TYPE_MOVE:
1383                                         file_data_sc_update_ci_move(fd, destname);
1384                                         break;
1385                                 default:;
1386                                 }
1387
1388                         g_free(dirname);
1389                         g_free(destname);
1390                         g_free(dest);
1391
1392                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ud->listview));
1393                         gtk_tree_selection_get_selected(selection, &store, &iter_selected);
1394                         path_iter=gtk_tree_model_get_path(store,&iter);
1395                         path_selected=gtk_tree_model_get_path(store,&iter_selected);
1396                         if (!gtk_tree_path_compare(path_iter,path_selected))
1397                                 {
1398                                 generic_dialog_image_set(ud, fd);
1399                                 }
1400                         gtk_tree_path_free(path_iter);
1401                         gtk_tree_path_free(path_selected);
1402
1403                         gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1404                                            UTILITY_COLUMN_DEST_PATH, fd->change->dest,
1405                                            UTILITY_COLUMN_DEST_NAME, filename_from_path(fd->change->dest),
1406                                            -1);
1407                         n++;
1408                         valid = gtk_tree_model_iter_next(store, &iter);
1409                         }
1410                 }
1411
1412         /* Check the other entries in the list - if there are
1413          * multiple destination filenames with the same name the
1414          * error icons must be updated
1415          */
1416         valid = gtk_tree_model_get_iter_first(store, &iter);
1417         while (valid)
1418                 {
1419                 FileData *fd;
1420                 gtk_tree_model_get(store, &iter, UTILITY_COLUMN_FD, &fd, -1);
1421
1422                 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1423                            UTILITY_COLUMN_PIXBUF, file_util_get_error_icon(fd, ud->flist, ud->listview),
1424                            -1);
1425                 valid = gtk_tree_model_iter_next(store, &iter);
1426                 }
1427
1428 }
1429
1430 static void file_util_rename_preview_entry_cb(GtkWidget *UNUSED(entry), gpointer data)
1431 {
1432         UtilityData *ud = static_cast<UtilityData *>(data);
1433         file_util_rename_preview_update(ud);
1434 }
1435
1436 static void file_util_rename_preview_adj_cb(GtkWidget *UNUSED(spin), gpointer data)
1437 {
1438         UtilityData *ud = static_cast<UtilityData *>(data);
1439         file_util_rename_preview_update(ud);
1440 }
1441
1442 static gboolean file_util_rename_idle_cb(gpointer data)
1443 {
1444         UtilityData *ud = static_cast<UtilityData *>(data);
1445
1446         file_util_rename_preview_update(ud);
1447
1448         ud->update_idle_id = 0;
1449         return FALSE;
1450 }
1451
1452 static void file_util_rename_preview_order_cb(GtkTreeModel *UNUSED(treemodel), GtkTreePath *UNUSED(tpath),
1453                                               GtkTreeIter *UNUSED(iter), gpointer data)
1454 {
1455         UtilityData *ud = static_cast<UtilityData *>(data);
1456
1457         if (ud->update_idle_id) return;
1458
1459         ud->update_idle_id = g_idle_add(file_util_rename_idle_cb, ud);
1460 }
1461
1462
1463 static gboolean file_util_preview_cb(GtkTreeSelection *UNUSED(selection), GtkTreeModel *store,
1464                                      GtkTreePath *tpath, gboolean path_currently_selected,
1465                                      gpointer data)
1466 {
1467         UtilityData *ud = static_cast<UtilityData *>(data);
1468         GtkTreeIter iter;
1469         FileData *fd = NULL;
1470
1471         if (path_currently_selected ||
1472             !gtk_tree_model_get_iter(store, &iter, tpath)) return TRUE;
1473
1474         gtk_tree_model_get(store, &iter, UTILITY_COLUMN_FD, &fd, -1);
1475         generic_dialog_image_set(ud, fd);
1476
1477         ud->sel_fd = fd;
1478
1479         if (ud->type == UTILITY_TYPE_RENAME || ud->type == UTILITY_TYPE_COPY || ud->type == UTILITY_TYPE_MOVE)
1480                 {
1481                 const gchar *name = filename_from_path(fd->change->dest);
1482
1483                 gtk_widget_grab_focus(ud->rename_entry);
1484                 gtk_label_set_text(GTK_LABEL(ud->rename_label), fd->name);
1485                 g_signal_handlers_block_by_func(ud->rename_entry, (gpointer)(file_util_rename_preview_entry_cb), ud);
1486                 gtk_entry_set_text(GTK_ENTRY(ud->rename_entry), name);
1487                 gtk_editable_select_region(GTK_EDITABLE(ud->rename_entry), 0, filename_base_length(name));
1488                 g_signal_handlers_unblock_by_func(ud->rename_entry, (gpointer)file_util_rename_preview_entry_cb, ud);
1489                 }
1490
1491         return TRUE;
1492 }
1493
1494
1495
1496 static void box_append_safe_delete_status(GenericDialog *gd)
1497 {
1498         GtkWidget *label;
1499         gchar *buf;
1500
1501         buf = file_util_safe_delete_status();
1502         label = pref_label_new(gd->vbox, buf);
1503         g_free(buf);
1504
1505         gtk_label_set_xalign(GTK_LABEL(label), 1.0);
1506         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
1507         gtk_widget_set_sensitive(label, FALSE);
1508 }
1509
1510 static void file_util_details_cb(GenericDialog *UNUSED(gd), gpointer data)
1511 {
1512         UtilityData *ud = static_cast<UtilityData *>(data);
1513         if (ud->details_func && ud->sel_fd)
1514                 {
1515                 ud->details_func(ud, ud->sel_fd);
1516                 }
1517 }
1518
1519 static void file_util_dialog_init_simple_list(UtilityData *ud)
1520 {
1521         GtkWidget *box;
1522         GtkTreeSelection *selection;
1523         gchar *dir_msg;
1524
1525         const gchar *stock_id;
1526
1527         /** @FIXME use ud->stock_id */
1528         if (ud->type == UTILITY_TYPE_DELETE ||
1529             ud->type == UTILITY_TYPE_DELETE_LINK ||
1530             ud->type == UTILITY_TYPE_DELETE_FOLDER)
1531                 {
1532                 stock_id = GTK_STOCK_DELETE;
1533                 }
1534         else
1535                 {
1536                 stock_id = GTK_STOCK_OK;
1537                 }
1538
1539         ud->gd = file_util_gen_dlg(ud->messages.title, "dlg_confirm",
1540                                    ud->parent, FALSE,  file_util_cancel_cb, ud);
1541         if (ud->discard_func) generic_dialog_add_button(ud->gd, GTK_STOCK_REVERT_TO_SAVED, _("Discard changes"), file_util_discard_cb, FALSE);
1542         if (ud->details_func) generic_dialog_add_button(ud->gd, GTK_STOCK_INFO, _("File details"), file_util_details_cb, FALSE);
1543
1544         generic_dialog_add_button(ud->gd, stock_id, NULL, file_util_ok_cb, TRUE);
1545
1546         if (ud->dir_fd)
1547                 {
1548                 dir_msg = g_strdup_printf("%s\n\n%s\n", ud->messages.desc_source_fd, ud->dir_fd->path);
1549                 }
1550         else
1551                 {
1552                 dir_msg = g_strdup("");
1553                 }
1554
1555         box = generic_dialog_add_message(ud->gd, GTK_STOCK_DIALOG_QUESTION,
1556                                          ud->messages.question,
1557                                          dir_msg, TRUE);
1558
1559         g_free(dir_msg);
1560
1561         box = pref_group_new(box, TRUE, ud->messages.desc_flist, GTK_ORIENTATION_HORIZONTAL);
1562
1563         ud->listview = file_util_dialog_add_list(box, ud->flist, FALSE, ud->with_sidecars);
1564         if (ud->with_sidecars) file_util_dialog_add_list_column(ud->listview, _("Sidecars"), FALSE, UTILITY_COLUMN_SIDECARS);
1565
1566         if (ud->type == UTILITY_TYPE_WRITE_METADATA) file_util_dialog_add_list_column(ud->listview, _("Write to file"), FALSE, UTILITY_COLUMN_DEST_NAME);
1567
1568         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ud->listview));
1569         gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1570         gtk_tree_selection_set_select_function(selection, file_util_preview_cb, ud, NULL);
1571
1572         generic_dialog_add_image(ud->gd, box, NULL, NULL, FALSE, NULL, NULL, FALSE);
1573
1574         if (ud->type == UTILITY_TYPE_DELETE ||
1575             ud->type == UTILITY_TYPE_DELETE_LINK ||
1576             ud->type == UTILITY_TYPE_DELETE_FOLDER)
1577                 box_append_safe_delete_status(ud->gd);
1578
1579         gtk_widget_show(ud->gd->dialog);
1580
1581         file_util_dialog_list_select(ud->listview, 0);
1582 }
1583
1584 static void file_util_dialog_init_dest_folder(UtilityData *ud)
1585 {
1586         FileDialog *fdlg;
1587         GtkWidget *label;
1588         const gchar *stock_id;
1589
1590         if (ud->type == UTILITY_TYPE_COPY)
1591                 {
1592                 stock_id = GTK_STOCK_COPY;
1593                 }
1594         else
1595                 {
1596                 stock_id = GTK_STOCK_OK;
1597                 }
1598
1599         fdlg = file_util_file_dlg(ud->messages.title, "dlg_dest_folder", ud->parent,
1600                                   file_util_fdlg_cancel_cb, ud);
1601
1602         ud->fdlg = fdlg;
1603
1604         generic_dialog_add_message(GENERIC_DIALOG(fdlg), NULL, ud->messages.question, NULL, FALSE);
1605
1606         label = pref_label_new(GENERIC_DIALOG(fdlg)->vbox, _("Choose the destination folder."));
1607         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
1608         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
1609
1610         pref_spacer(GENERIC_DIALOG(fdlg)->vbox, 0);
1611
1612         if (options->with_rename)
1613                 {
1614                 file_dialog_add_button(fdlg, stock_id, ud->messages.title, file_util_fdlg_ok_cb, TRUE);
1615                 file_dialog_add_button(fdlg, GTK_STOCK_EDIT, "With Rename", file_util_fdlg_rename_cb, TRUE);
1616                 }
1617         else
1618                 {
1619                 file_dialog_add_button(fdlg, GTK_STOCK_EDIT, "With Rename", file_util_fdlg_rename_cb, TRUE);
1620                 file_dialog_add_button(fdlg, stock_id, ud->messages.title, file_util_fdlg_ok_cb, TRUE);
1621                 }
1622
1623         file_dialog_add_path_widgets(fdlg, NULL, ud->dest_path, "move_copy", NULL, NULL);
1624
1625         g_signal_connect(G_OBJECT(fdlg->entry), "changed",
1626                          G_CALLBACK(file_util_dest_folder_entry_cb), ud);
1627
1628         gtk_widget_show(GENERIC_DIALOG(fdlg)->dialog);
1629 }
1630
1631
1632 static GtkWidget *furm_simple_vlabel(GtkWidget *box, const gchar *text, gboolean expand)
1633 {
1634         GtkWidget *vbox;
1635         GtkWidget *label;
1636
1637         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1638         gtk_box_pack_start(GTK_BOX(box), vbox, expand, expand, 0);
1639         gtk_widget_show(vbox);
1640
1641         label = gtk_label_new(text);
1642         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1643         gtk_widget_show(label);
1644
1645         return vbox;
1646 }
1647
1648
1649 static void file_util_dialog_init_source_dest(UtilityData *ud, gboolean second_image)
1650 {
1651         GtkTreeModel *store;
1652         GtkTreeSelection *selection;
1653         GtkWidget *box;
1654         GtkWidget *hbox;
1655         GtkWidget *box2;
1656         GtkWidget *table;
1657         GtkWidget *combo;
1658         GtkWidget *page;
1659         gchar *destination_message;
1660
1661         ud->gd = file_util_gen_dlg(ud->messages.title, "dlg_confirm",
1662                                    ud->parent, FALSE,  file_util_cancel_cb, ud);
1663
1664         box = generic_dialog_add_message(ud->gd, NULL, ud->messages.question, NULL, TRUE);
1665
1666         if (ud->discard_func) generic_dialog_add_button(ud->gd, GTK_STOCK_REVERT_TO_SAVED, _("Discard changes"), file_util_discard_cb, FALSE);
1667         if (ud->details_func) generic_dialog_add_button(ud->gd, GTK_STOCK_INFO, _("File details"), file_util_details_cb, FALSE);
1668
1669         generic_dialog_add_button(ud->gd, GTK_STOCK_OK, ud->messages.title, file_util_ok_cb, TRUE);
1670
1671         if (ud->type == UTILITY_TYPE_COPY || ud->type == UTILITY_TYPE_MOVE)
1672                 {
1673                 destination_message = g_strconcat(ud->messages.desc_flist," to: ", ud->dest_path, NULL);
1674                 }
1675         else
1676                 {
1677                 destination_message = g_strdup(ud->messages.desc_flist);
1678                 }
1679
1680         box = pref_group_new(box, TRUE, destination_message, GTK_ORIENTATION_HORIZONTAL);
1681         g_free(destination_message);
1682
1683         ud->listview = file_util_dialog_add_list(box, ud->flist, FALSE, ud->with_sidecars);
1684         file_util_dialog_add_list_column(ud->listview, _("Sidecars"), FALSE, UTILITY_COLUMN_SIDECARS);
1685
1686         file_util_dialog_add_list_column(ud->listview, _("New name"), FALSE, UTILITY_COLUMN_DEST_NAME);
1687
1688         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ud->listview));
1689         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE);
1690         gtk_tree_selection_set_select_function(selection, file_util_preview_cb, ud, NULL);
1691
1692
1693 //      column = file_util_rename_multiple_add_column(rd, _("Preview"), RENAME_COLUMN_PREVIEW);
1694 //      gtk_tree_view_column_set_visible(column, FALSE);
1695
1696         gtk_tree_view_set_reorderable(GTK_TREE_VIEW(ud->listview), TRUE);
1697
1698         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ud->listview));
1699         g_signal_connect(G_OBJECT(store), "row_changed",
1700                          G_CALLBACK(file_util_rename_preview_order_cb), ud);
1701         gtk_widget_set_size_request(ud->listview, 300, 150);
1702
1703         if (second_image)
1704                 {
1705                 generic_dialog_add_image(ud->gd, box, NULL, "Source", TRUE, NULL, "Destination", TRUE);
1706                 }
1707         else
1708                 {
1709                 generic_dialog_add_image(ud->gd, box, NULL, NULL, FALSE, NULL, NULL, FALSE);
1710                 }
1711
1712 //      gtk_container_add(GTK_CONTAINER(scrolled), view);
1713         gtk_widget_show(ud->gd->dialog);
1714
1715
1716         ud->notebook = gtk_notebook_new();
1717
1718         gtk_box_pack_start(GTK_BOX(ud->gd->vbox), ud->notebook, FALSE, FALSE, 0);
1719         gtk_widget_show(ud->notebook);
1720
1721
1722         page = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1723         gtk_notebook_append_page(GTK_NOTEBOOK(ud->notebook), page, gtk_label_new(_("Manual rename")));
1724         gtk_widget_show(page);
1725
1726         table = pref_table_new(page, 2, 2, FALSE, FALSE);
1727
1728         pref_table_label(table, 0, 0, _("Original name:"), 1.0);
1729         ud->rename_label = pref_table_label(table, 1, 0, "", 0.0);
1730
1731         pref_table_label(table, 0, 1, _("New name:"), 1.0);
1732
1733         ud->rename_entry = gtk_entry_new();
1734         gtk_table_attach(GTK_TABLE(table), ud->rename_entry, 1, 2, 1, 2, static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), static_cast<GtkAttachOptions>(0), 0, 0);
1735         generic_dialog_attach_default(GENERIC_DIALOG(ud->gd), ud->rename_entry);
1736         gtk_widget_grab_focus(ud->rename_entry);
1737
1738         g_signal_connect(G_OBJECT(ud->rename_entry), "changed",
1739                          G_CALLBACK(file_util_rename_preview_entry_cb), ud);
1740
1741         gtk_widget_show(ud->rename_entry);
1742
1743         page = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1744         gtk_notebook_append_page(GTK_NOTEBOOK(ud->notebook), page, gtk_label_new(_("Auto rename")));
1745         gtk_widget_show(page);
1746
1747
1748         hbox = pref_box_new(page, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1749
1750         box2 = furm_simple_vlabel(hbox, _("Begin text"), TRUE);
1751
1752         combo = history_combo_new(&ud->auto_entry_front, "", "numerical_rename_prefix", -1);
1753         g_signal_connect(G_OBJECT(ud->auto_entry_front), "changed",
1754                          G_CALLBACK(file_util_rename_preview_entry_cb), ud);
1755         gtk_box_pack_start(GTK_BOX(box2), combo, TRUE, TRUE, 0);
1756         gtk_widget_show(combo);
1757
1758         box2 = furm_simple_vlabel(hbox, _("Start #"), FALSE);
1759
1760         ud->auto_spin_start = pref_spin_new(box2, NULL, NULL,
1761                                             0.0, 1000000.0, 1.0, 0, options->cp_mv_rn.auto_start,
1762                                             G_CALLBACK(file_util_rename_preview_adj_cb), ud);
1763
1764         box2 = furm_simple_vlabel(hbox, _("End text"), TRUE);
1765
1766         combo = history_combo_new(&ud->auto_entry_end, options->cp_mv_rn.auto_end, "numerical_rename_suffix", -1);
1767         g_signal_connect(G_OBJECT(ud->auto_entry_end), "changed",
1768                          G_CALLBACK(file_util_rename_preview_entry_cb), ud);
1769         gtk_box_pack_start(GTK_BOX(box2), combo, TRUE, TRUE, 0);
1770         gtk_widget_show(combo);
1771
1772         ud->auto_spin_pad = pref_spin_new(page, _("Padding:"), NULL,
1773                                           1.0, 8.0, 1.0, 0, options->cp_mv_rn.auto_padding,
1774                                           G_CALLBACK(file_util_rename_preview_adj_cb), ud);
1775
1776         page = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1777         gtk_notebook_append_page(GTK_NOTEBOOK(ud->notebook), page, gtk_label_new(_("Formatted rename")));
1778         gtk_widget_show(page);
1779
1780         hbox = pref_box_new(page, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1781
1782         box2 = furm_simple_vlabel(hbox, _("Format (* = original name, ## = numbers)"), TRUE);
1783
1784         combo = history_combo_new(&ud->format_entry, "", "auto_rename_format", -1);
1785         g_signal_connect(G_OBJECT(ud->format_entry), "changed",
1786                          G_CALLBACK(file_util_rename_preview_entry_cb), ud);
1787         gtk_box_pack_start(GTK_BOX(box2), combo, TRUE, TRUE, 0);
1788         gtk_widget_show(combo);
1789
1790         box2 = furm_simple_vlabel(hbox, _("Start #"), FALSE);
1791
1792         ud->format_spin = pref_spin_new(box2, NULL, NULL,
1793                                         0.0, 1000000.0, 1.0, 0, options->cp_mv_rn.formatted_start,
1794                                         G_CALLBACK(file_util_rename_preview_adj_cb), ud);
1795
1796 //      gtk_combo_box_set_active(GTK_COMBO_BOX(ud->combo_type), 0); /* callback will take care of the rest */
1797
1798         file_util_dialog_list_select(ud->listview, 0);
1799 }
1800
1801 static void file_util_finalize_all(UtilityData *ud)
1802 {
1803         GList *work = ud->flist;
1804
1805         if (ud->phase == UTILITY_PHASE_CANCEL) return;
1806         if (ud->phase == UTILITY_PHASE_DONE && !ud->finalize_func) return;
1807         if (ud->phase == UTILITY_PHASE_DISCARD && !ud->discard_func) return;
1808
1809         while (work)
1810                 {
1811                 FileData *fd = static_cast<FileData *>(work->data);
1812                 work = work->next;
1813                 if (ud->phase == UTILITY_PHASE_DONE) ud->finalize_func(fd);
1814                 else if (ud->phase == UTILITY_PHASE_DISCARD) ud->discard_func(fd);
1815                 }
1816 }
1817
1818 static gboolean file_util_exclude_fd(UtilityData *ud, FileData *fd)
1819 {
1820         GtkTreeModel *store;
1821         GtkTreeIter iter;
1822         gboolean valid;
1823
1824         if (!g_list_find(ud->flist, fd)) return FALSE;
1825
1826         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ud->listview));
1827         valid = gtk_tree_model_get_iter_first(store, &iter);
1828         while (valid)
1829                 {
1830                 FileData *store_fd;
1831                 gtk_tree_model_get(store, &iter, UTILITY_COLUMN_FD, &store_fd, -1);
1832
1833                 if (store_fd == fd)
1834                         {
1835                         gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
1836                         break;
1837                         }
1838                 valid = gtk_tree_model_iter_next(store, &iter);
1839                 }
1840
1841         ud->flist = g_list_remove(ud->flist, fd);
1842
1843         if (ud->with_sidecars)
1844                 file_data_sc_free_ci(fd);
1845         else
1846                 file_data_free_ci(fd);
1847
1848         file_data_unref(fd);
1849         return TRUE;
1850 }
1851
1852 void file_util_dialog_run(UtilityData *ud)
1853 {
1854         switch (ud->phase)
1855                 {
1856                 case UTILITY_PHASE_START:
1857                         /* create the dialogs */
1858                         switch (ud->type)
1859                                 {
1860                                 case UTILITY_TYPE_DELETE:
1861                                 case UTILITY_TYPE_DELETE_LINK:
1862                                 case UTILITY_TYPE_DELETE_FOLDER:
1863                                 case UTILITY_TYPE_EDITOR:
1864                                 case UTILITY_TYPE_WRITE_METADATA:
1865                                         file_util_dialog_init_simple_list(ud);
1866                                         ud->phase = UTILITY_PHASE_ENTERING;
1867                                         break;
1868                                 case UTILITY_TYPE_RENAME:
1869                                         file_util_dialog_init_source_dest(ud, TRUE);
1870                                         ud->phase = UTILITY_PHASE_ENTERING;
1871                                         break;
1872                                 case UTILITY_TYPE_COPY:
1873                                 case UTILITY_TYPE_MOVE:
1874                                         file_util_dialog_init_dest_folder(ud);
1875                                         ud->phase = UTILITY_PHASE_INTERMEDIATE;
1876                                         break;
1877                                 case UTILITY_TYPE_FILTER:
1878                                 case UTILITY_TYPE_CREATE_FOLDER:
1879                                         file_util_dialog_init_dest_folder(ud);
1880                                         ud->phase = UTILITY_PHASE_ENTERING;
1881                                         break;
1882                                 case UTILITY_TYPE_RENAME_FOLDER:
1883                                         ud->phase = UTILITY_PHASE_CANCEL; /**< @FIXME not handled for now */
1884                                         file_util_dialog_run(ud);
1885                                         return;
1886                                 }
1887                         break;
1888                 case UTILITY_PHASE_INTERMEDIATE:
1889                         switch (ud->type)
1890                                 {
1891                                 case UTILITY_TYPE_COPY:
1892                                 case UTILITY_TYPE_MOVE:
1893                                         file_util_dialog_init_source_dest(ud, TRUE);
1894                                         break;
1895                                 default:;
1896                                 }
1897                         ud->phase = UTILITY_PHASE_ENTERING;
1898                         break;
1899                 case UTILITY_PHASE_ENTERING:
1900                         file_util_check_ci(ud);
1901                         break;
1902                 case UTILITY_PHASE_CHECKED:
1903                         file_util_perform_ci(ud);
1904                         break;
1905                 case UTILITY_PHASE_CANCEL:
1906                 case UTILITY_PHASE_DONE:
1907                 case UTILITY_PHASE_DISCARD:
1908
1909                         file_util_finalize_all(ud);
1910
1911                         /* both DISCARD and DONE finishes the operation for good */
1912                         if (ud->done_func)
1913                                 ud->done_func((ud->phase != UTILITY_PHASE_CANCEL), ud->dest_path, ud->done_data);
1914
1915                         if (ud->with_sidecars)
1916                                 file_data_sc_free_ci_list(ud->flist);
1917                         else
1918                                 file_data_free_ci_list(ud->flist);
1919
1920                         /* directory content is always handled including sidecars */
1921                         file_data_sc_free_ci_list(ud->content_list);
1922
1923                         if (ud->dir_fd) file_data_free_ci(ud->dir_fd);
1924                         file_util_data_free(ud);
1925                         break;
1926                 }
1927 }
1928
1929
1930
1931
1932 static void file_util_warn_op_in_progress(const gchar *title)
1933 {
1934         file_util_warning_dialog(title, _("Another operation in progress.\n"), GTK_STOCK_DIALOG_ERROR, NULL);
1935 }
1936
1937 static void file_util_details_dialog_close_cb(GtkWidget *UNUSED(widget), gpointer data)
1938 {
1939         gtk_widget_destroy(GTK_WIDGET(data));
1940
1941 }
1942
1943 static void file_util_details_dialog_destroy_cb(GtkWidget *widget, gpointer data)
1944 {
1945         UtilityData *ud = static_cast<UtilityData *>(data);
1946         g_signal_handlers_disconnect_by_func(ud->gd->dialog, (gpointer)(file_util_details_dialog_close_cb), widget);
1947 }
1948
1949
1950 static void file_util_details_dialog_ok_cb(GenericDialog *UNUSED(gd), gpointer UNUSED(data))
1951 {
1952         /* no op */
1953 }
1954
1955 static void file_util_details_dialog_exclude(GenericDialog *gd, gpointer data, gboolean discard)
1956 {
1957         UtilityData *ud = static_cast<UtilityData *>(data);
1958         FileData *fd = static_cast<FileData *>(g_object_get_data(G_OBJECT(gd->dialog), "file_data"));
1959
1960         if (!fd) return;
1961         file_util_exclude_fd(ud, fd);
1962
1963         if (discard && ud->discard_func) ud->discard_func(fd);
1964
1965         /* all files were excluded, this has the same effect as pressing the cancel button in the confirmation dialog*/
1966         if (!ud->flist)
1967                 {
1968                 /* both dialogs will be closed anyway, the signals would cause duplicate calls */
1969                 g_signal_handlers_disconnect_by_func(ud->gd->dialog, (gpointer)(file_util_details_dialog_close_cb), gd->dialog);
1970                 g_signal_handlers_disconnect_by_func(gd->dialog, (gpointer)(file_util_details_dialog_destroy_cb), ud);
1971
1972                 file_util_cancel_cb(ud->gd, ud);
1973                 }
1974 }
1975
1976 static void file_util_details_dialog_exclude_cb(GenericDialog *gd, gpointer data)
1977 {
1978         file_util_details_dialog_exclude(gd, data, FALSE);
1979 }
1980
1981 static void file_util_details_dialog_discard_cb(GenericDialog *gd, gpointer data)
1982 {
1983         file_util_details_dialog_exclude(gd, data, TRUE);
1984 }
1985
1986 static gchar *file_util_details_get_message(UtilityData *ud, FileData *fd, const gchar **stock_id)
1987 {
1988         GString *message = g_string_new("");
1989         gint error;
1990         g_string_append_printf(message, _("File: '%s'\n"), fd->path);
1991
1992         if (ud->with_sidecars && fd->sidecar_files)
1993                 {
1994                 GList *work = fd->sidecar_files;
1995                 g_string_append(message, _("with sidecar files:\n"));
1996
1997                 while (work)
1998                         {
1999                         FileData *sfd = static_cast<FileData *>(work->data);
2000                         work =work->next;
2001                         g_string_append_printf(message, _(" '%s'\n"), sfd->path);
2002                         }
2003                 }
2004
2005         g_string_append(message, _("\nStatus: "));
2006
2007         error = ud->with_sidecars ? file_data_sc_verify_ci(fd, ud->flist) : file_data_verify_ci(fd, ud->flist);
2008
2009         if (error)
2010                 {
2011                 gchar *err_msg = file_data_get_error_string(error);
2012                 g_string_append(message, err_msg);
2013                 if (stock_id) *stock_id = (error & CHANGE_ERROR_MASK) ? GTK_STOCK_DIALOG_ERROR : GTK_STOCK_DIALOG_WARNING;
2014                 }
2015         else
2016                 {
2017                 g_string_append(message, _("no problem detected"));
2018                 if (stock_id) *stock_id = GTK_STOCK_DIALOG_INFO;
2019                 }
2020
2021         return g_string_free(message, FALSE);;
2022 }
2023
2024 static void file_util_details_dialog(UtilityData *ud, FileData *fd)
2025 {
2026         GenericDialog *gd;
2027         GtkWidget *box;
2028         gchar *message;
2029         const gchar *stock_id;
2030
2031         gd = file_util_gen_dlg(_("File details"), "details", ud->gd->dialog, TRUE, NULL, ud);
2032         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, file_util_details_dialog_ok_cb, TRUE);
2033         generic_dialog_add_button(gd, GTK_STOCK_REMOVE, _("Exclude file"), file_util_details_dialog_exclude_cb, FALSE);
2034
2035         g_object_set_data(G_OBJECT(gd->dialog), "file_data", fd);
2036
2037         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
2038                          G_CALLBACK(file_util_details_dialog_destroy_cb), ud);
2039
2040         /* in case the ud->gd->dialog is closed during editing */
2041         g_signal_connect(G_OBJECT(ud->gd->dialog), "destroy",
2042                          G_CALLBACK(file_util_details_dialog_close_cb), gd->dialog);
2043
2044
2045         message = file_util_details_get_message(ud, fd, &stock_id);
2046
2047         box = generic_dialog_add_message(gd, stock_id, _("File details"), message, TRUE);
2048
2049         generic_dialog_add_image(gd, box, fd, NULL, FALSE, NULL, NULL, FALSE);
2050
2051         gtk_widget_show(gd->dialog);
2052
2053         g_free(message);
2054 }
2055
2056 static void file_util_write_metadata_details_dialog(UtilityData *ud, FileData *fd)
2057 {
2058         GenericDialog *gd;
2059         GtkWidget *box;
2060         GtkWidget *table;
2061         GtkWidget *frame;
2062         GtkWidget *label;
2063         GList *keys = NULL;
2064         GList *work;
2065         gchar *message1;
2066         gchar *message2;
2067         gint i;
2068         const gchar *stock_id;
2069
2070         if (fd && fd->modified_xmp)
2071                 {
2072                 keys = g_hash_table_get_keys(fd->modified_xmp);
2073                 }
2074
2075         g_assert(keys);
2076
2077
2078         gd = file_util_gen_dlg(_("Overview of changed metadata"), "details", ud->gd->dialog, TRUE, NULL, ud);
2079         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, file_util_details_dialog_ok_cb, TRUE);
2080         generic_dialog_add_button(gd, GTK_STOCK_REMOVE, _("Exclude file"), file_util_details_dialog_exclude_cb, FALSE);
2081         generic_dialog_add_button(gd, GTK_STOCK_REVERT_TO_SAVED, _("Discard changes"), file_util_details_dialog_discard_cb, FALSE);
2082
2083         g_object_set_data(G_OBJECT(gd->dialog), "file_data", fd);
2084
2085         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
2086                          G_CALLBACK(file_util_details_dialog_destroy_cb), ud);
2087
2088         /* in case the ud->gd->dialog is closed during editing */
2089         g_signal_connect(G_OBJECT(ud->gd->dialog), "destroy",
2090                          G_CALLBACK(file_util_details_dialog_close_cb), gd->dialog);
2091
2092         message1 = file_util_details_get_message(ud, fd, &stock_id);
2093
2094         if (fd->change && fd->change->dest)
2095                 {
2096                 message2 = g_strdup_printf(_("The following metadata tags will be written to\n'%s'."), fd->change->dest);
2097                 }
2098         else
2099                 {
2100                 message2 = g_strdup_printf(_("The following metadata tags will be written to the image file itself."));
2101                 }
2102
2103         box = generic_dialog_add_message(gd, stock_id, _("Overview of changed metadata"), message1, TRUE);
2104
2105         box = pref_group_new(box, TRUE, message2, GTK_ORIENTATION_HORIZONTAL);
2106
2107         frame = pref_frame_new(box, TRUE, NULL, GTK_ORIENTATION_HORIZONTAL, 2);
2108         table = pref_table_new(frame, 2, g_list_length(keys), FALSE, TRUE);
2109
2110         work = keys;
2111         i = 0;
2112         while (work)
2113                 {
2114                 const gchar *key = static_cast<const gchar *>(work->data);
2115                 gchar *title = exif_get_description_by_key(key);
2116                 gchar *title_f = g_strdup_printf("%s:", title);
2117                 gchar *value = metadata_read_string(fd, key, METADATA_FORMATTED);
2118                 work = work->next;
2119
2120
2121                 label = gtk_label_new(title_f);
2122                 gtk_label_set_xalign(GTK_LABEL(label), 1.0);
2123                 gtk_label_set_yalign(GTK_LABEL(label), 0.0);
2124
2125                 pref_label_bold(label, TRUE, FALSE);
2126                 gtk_table_attach(GTK_TABLE(table), label,
2127                                  0, 1, i, i + 1,
2128                                  GTK_FILL, GTK_FILL,
2129                                  2, 2);
2130                 gtk_widget_show(label);
2131
2132                 label = gtk_label_new(value);
2133
2134                 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
2135                 gtk_label_set_yalign(GTK_LABEL(label), 0.0);
2136
2137                 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
2138                 gtk_table_attach(GTK_TABLE(table), label,
2139                                  1, 2, i, i + 1,
2140                                  GTK_FILL, GTK_FILL,
2141                                  2, 2);
2142                 gtk_widget_show(label);
2143
2144                 g_free(title);
2145                 g_free(title_f);
2146                 g_free(value);
2147                 i++;
2148                 }
2149
2150         generic_dialog_add_image(gd, box, fd, NULL, FALSE, NULL, NULL, FALSE);
2151
2152         gtk_widget_set_size_request(gd->dialog, DIALOG_WIDTH, -1);
2153         gtk_widget_show(gd->dialog);
2154
2155         g_list_free(keys);
2156         g_free(message1);
2157         g_free(message2);
2158 }
2159
2160
2161 static void file_util_mark_ungrouped_files(GList *work)
2162 {
2163         while (work)
2164                 {
2165                 FileData *fd = static_cast<FileData *>(work->data);
2166                 file_data_set_regroup_when_finished(fd, TRUE);
2167                 work = work->next;
2168                 }
2169 }
2170
2171 static void file_util_delete_full(FileData *source_fd, GList *flist, GtkWidget *parent, UtilityPhase phase, FileUtilDoneFunc done_func, gpointer done_data)
2172 {
2173         UtilityData *ud;
2174         GList *ungrouped = NULL;
2175         gchar *message;
2176
2177         if (source_fd)
2178                 flist = g_list_append(flist, file_data_ref(source_fd));
2179
2180         if (!flist) return;
2181
2182         flist = file_data_process_groups_in_selection(flist, TRUE, &ungrouped);
2183
2184         if (!file_data_sc_add_ci_delete_list(flist))
2185                 {
2186                 file_util_warn_op_in_progress(_("File deletion failed"));
2187                 file_data_disable_grouping_list(ungrouped, FALSE);
2188                 filelist_free(flist);
2189                 filelist_free(ungrouped);
2190                 return;
2191                 }
2192
2193         file_util_mark_ungrouped_files(ungrouped);
2194         filelist_free(ungrouped);
2195
2196         ud = file_util_data_new(UTILITY_TYPE_DELETE);
2197
2198         ud->phase = phase;
2199
2200         ud->with_sidecars = TRUE;
2201
2202         ud->dir_fd = NULL;
2203         ud->flist = flist;
2204         ud->content_list = NULL;
2205         ud->parent = parent;
2206         ud->done_data = done_data;
2207         ud->done_func = done_func;
2208
2209         ud->details_func = file_util_details_dialog;
2210         if(options->file_ops.safe_delete_enable)
2211                 {
2212                 message = _("This will move the following files to the Trash bin");
2213                 }
2214         else
2215                 {
2216                 message = _("This will permanently delete the following files");
2217                 }
2218         ud->messages.title = _("Delete");
2219         ud->messages.question = _("Delete files?");
2220         ud->messages.desc_flist = message;
2221         ud->messages.desc_source_fd = "";
2222         ud->messages.fail = _("File deletion failed");
2223
2224         file_util_dialog_run(ud);
2225 }
2226
2227
2228 static void file_util_write_metadata_full(FileData *source_fd, GList *flist, GtkWidget *parent, UtilityPhase phase, FileUtilDoneFunc done_func, gpointer done_data)
2229 {
2230         UtilityData *ud;
2231
2232         if (source_fd)
2233                 flist = g_list_append(flist, file_data_ref(source_fd));
2234
2235         if (!flist) return;
2236
2237         if (!file_data_add_ci_write_metadata_list(flist))
2238                 {
2239                 file_util_warn_op_in_progress(_("Can't write metadata"));
2240                 filelist_free(flist);
2241                 return;
2242                 }
2243
2244         ud = file_util_data_new(UTILITY_TYPE_WRITE_METADATA);
2245
2246         ud->phase = phase;
2247
2248         ud->with_sidecars = FALSE; /* operate on individual files, not groups */
2249
2250         ud->dir_fd = NULL;
2251         ud->flist = flist;
2252         ud->content_list = NULL;
2253         ud->parent = parent;
2254
2255         ud->done_func = done_func;
2256         ud->done_data = done_data;
2257
2258         ud->details_func = file_util_write_metadata_details_dialog;
2259         ud->finalize_func = metadata_write_queue_remove;
2260         ud->discard_func = metadata_write_queue_remove;
2261
2262         ud->messages.title = _("Write metadata");
2263         ud->messages.question = _("Write metadata?");
2264         ud->messages.desc_flist = _("This will write the changed metadata into the following files");
2265         ud->messages.desc_source_fd = "";
2266         ud->messages.fail = _("Metadata writing failed");
2267
2268         file_util_dialog_run(ud);
2269 }
2270
2271 static void file_util_move_full(FileData *source_fd, GList *flist, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase)
2272 {
2273         UtilityData *ud;
2274         GList *ungrouped = NULL;
2275
2276         if (source_fd)
2277                 flist = g_list_append(flist, file_data_ref(source_fd));
2278
2279         if (!flist) return;
2280
2281         flist = file_data_process_groups_in_selection(flist, TRUE, &ungrouped);
2282
2283         if (!file_data_sc_add_ci_move_list(flist, dest_path))
2284                 {
2285                 file_util_warn_op_in_progress(_("Move failed"));
2286                 file_data_disable_grouping_list(ungrouped, FALSE);
2287                 filelist_free(flist);
2288                 filelist_free(ungrouped);
2289                 return;
2290                 }
2291
2292         file_util_mark_ungrouped_files(ungrouped);
2293         filelist_free(ungrouped);
2294
2295         ud = file_util_data_new(UTILITY_TYPE_MOVE);
2296
2297         ud->phase = phase;
2298
2299         ud->with_sidecars = TRUE;
2300
2301         ud->dir_fd = NULL;
2302         ud->flist = flist;
2303         ud->content_list = NULL;
2304         ud->parent = parent;
2305         ud->details_func = file_util_details_dialog;
2306
2307         if (dest_path) ud->dest_path = g_strdup(dest_path);
2308
2309         ud->messages.title = _("Move");
2310         ud->messages.question = _("Move files?");
2311         ud->messages.desc_flist = _("This will move the following files");
2312         ud->messages.desc_source_fd = "";
2313         ud->messages.fail = _("Move failed");
2314
2315         file_util_dialog_run(ud);
2316 }
2317
2318 static void file_util_copy_full(FileData *source_fd, GList *flist, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase)
2319 {
2320         UtilityData *ud;
2321         GList *ungrouped = NULL;
2322
2323         if (source_fd)
2324                 flist = g_list_append(flist, file_data_ref(source_fd));
2325
2326         if (!flist) return;
2327
2328         if (file_util_write_metadata_first(UTILITY_TYPE_COPY, phase, flist, dest_path, NULL, parent))
2329                 return;
2330
2331         flist = file_data_process_groups_in_selection(flist, TRUE, &ungrouped);
2332
2333         if (!file_data_sc_add_ci_copy_list(flist, dest_path))
2334                 {
2335                 file_util_warn_op_in_progress(_("Copy failed"));
2336                 file_data_disable_grouping_list(ungrouped, FALSE);
2337                 filelist_free(flist);
2338                 filelist_free(ungrouped);
2339                 return;
2340                 }
2341
2342         file_util_mark_ungrouped_files(ungrouped);
2343         filelist_free(ungrouped);
2344
2345         ud = file_util_data_new(UTILITY_TYPE_COPY);
2346
2347         ud->phase = phase;
2348
2349         ud->with_sidecars = TRUE;
2350
2351         ud->dir_fd = NULL;
2352         ud->flist = flist;
2353         ud->content_list = NULL;
2354         ud->parent = parent;
2355         ud->details_func = file_util_details_dialog;
2356
2357         if (dest_path) ud->dest_path = g_strdup(dest_path);
2358
2359         ud->messages.title = _("Copy");
2360         ud->messages.question = _("Copy files?");
2361         ud->messages.desc_flist = _("This will copy the following files");
2362         ud->messages.desc_source_fd = "";
2363         ud->messages.fail = _("Copy failed");
2364
2365         file_util_dialog_run(ud);
2366 }
2367
2368 static void file_util_rename_full(FileData *source_fd, GList *flist, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase)
2369 {
2370         UtilityData *ud;
2371         GList *ungrouped = NULL;
2372
2373         if (source_fd)
2374                 flist = g_list_append(flist, file_data_ref(source_fd));
2375
2376         if (!flist) return;
2377
2378         flist = file_data_process_groups_in_selection(flist, TRUE, &ungrouped);
2379
2380         if (!file_data_sc_add_ci_rename_list(flist, dest_path))
2381                 {
2382                 file_util_warn_op_in_progress(_("Rename failed"));
2383                 file_data_disable_grouping_list(ungrouped, FALSE);
2384                 filelist_free(flist);
2385                 filelist_free(ungrouped);
2386                 return;
2387                 }
2388
2389         file_util_mark_ungrouped_files(ungrouped);
2390         filelist_free(ungrouped);
2391
2392         ud = file_util_data_new(UTILITY_TYPE_RENAME);
2393
2394         ud->phase = phase;
2395
2396         ud->with_sidecars = TRUE;
2397
2398         ud->dir_fd = NULL;
2399         ud->flist = flist;
2400         ud->content_list = NULL;
2401         ud->parent = parent;
2402
2403         ud->details_func = file_util_details_dialog;
2404
2405         ud->messages.title = _("Rename");
2406         ud->messages.question = _("Rename files?");
2407         ud->messages.desc_flist = _("This will rename the following files");
2408         ud->messages.desc_source_fd = "";
2409         ud->messages.fail = _("Rename failed");
2410
2411         file_util_dialog_run(ud);
2412 }
2413
2414 static void file_util_start_editor_full(const gchar *key, FileData *source_fd, GList *flist, const gchar *dest_path, const gchar *working_directory, GtkWidget *parent, UtilityPhase phase)
2415 {
2416         UtilityData *ud;
2417         GList *ungrouped = NULL;
2418
2419         if (editor_no_param(key))
2420                 {
2421                 gchar *file_directory = NULL;
2422                 if (!working_directory)
2423                         {
2424                         /* working directory was not specified, try to extract it from the files */
2425                         if (source_fd)
2426                                 file_directory = remove_level_from_path(source_fd->path);
2427
2428                         if (!file_directory && flist)
2429                                 file_directory = remove_level_from_path(((FileData *)flist->data)->path);
2430                         working_directory = file_directory;
2431                         }
2432
2433                 /* just start the editor, don't care about files */
2434                 start_editor(key, working_directory);
2435                 g_free(file_directory);
2436                 filelist_free(flist);
2437                 return;
2438                 }
2439
2440
2441         if (source_fd)
2442                 {
2443                 /* flist is most probably NULL
2444                    operate on source_fd and it's sidecars
2445                 */
2446                 flist = g_list_concat(filelist_copy(source_fd->sidecar_files), flist);
2447                 flist = g_list_append(flist, file_data_ref(source_fd));
2448                 }
2449
2450         if (!flist) return;
2451
2452         if (file_util_write_metadata_first(UTILITY_TYPE_FILTER, phase, flist, dest_path, key, parent))
2453                 return;
2454
2455         flist = file_data_process_groups_in_selection(flist, TRUE, &ungrouped);
2456
2457         if (!file_data_sc_add_ci_unspecified_list(flist, dest_path))
2458                 {
2459                 file_util_warn_op_in_progress(_("Can't run external editor"));
2460                 file_data_disable_grouping_list(ungrouped, FALSE);
2461                 filelist_free(flist);
2462                 filelist_free(ungrouped);
2463                 return;
2464                 }
2465
2466         file_util_mark_ungrouped_files(ungrouped);
2467         filelist_free(ungrouped);
2468
2469         if (editor_is_filter(key))
2470                 ud = file_util_data_new(UTILITY_TYPE_FILTER);
2471         else
2472                 ud = file_util_data_new(UTILITY_TYPE_EDITOR);
2473
2474
2475         /* ask for destination if we don't have it */
2476         if (ud->type == UTILITY_TYPE_FILTER && dest_path == NULL) phase = UTILITY_PHASE_START;
2477
2478         ud->phase = phase;
2479
2480         ud->with_sidecars = TRUE;
2481
2482         ud->external_command = g_strdup(key);
2483
2484         ud->dir_fd = NULL;
2485         ud->flist = flist;
2486         ud->content_list = NULL;
2487         ud->parent = parent;
2488
2489         ud->details_func = file_util_details_dialog;
2490
2491         if (dest_path) ud->dest_path = g_strdup(dest_path);
2492
2493         ud->messages.title = _("Editor");
2494         ud->messages.question = _("Run editor?");
2495         ud->messages.desc_flist = _("This will copy the following files");
2496         ud->messages.desc_source_fd = "";
2497         ud->messages.fail = _("External command failed");
2498
2499         file_util_dialog_run(ud);
2500 }
2501
2502 static GList *file_util_delete_dir_remaining_folders(GList *dlist)
2503 {
2504         GList *rlist = NULL;
2505
2506         while (dlist)
2507                 {
2508                 FileData *fd;
2509
2510                 fd = static_cast<FileData *>(dlist->data);
2511                 dlist = dlist->next;
2512
2513                 if (!fd->name ||
2514                     (strcmp(fd->name, THUMB_FOLDER_GLOBAL) != 0 &&
2515                      strcmp(fd->name, THUMB_FOLDER_LOCAL) != 0 &&
2516                      strcmp(fd->name, GQ_CACHE_LOCAL_METADATA) != 0) )
2517                         {
2518                         rlist = g_list_prepend(rlist, fd);
2519                         }
2520                 }
2521
2522         return g_list_reverse(rlist);
2523 }
2524
2525 static gboolean file_util_delete_dir_empty_path(UtilityData *ud, FileData *fd, gint level)
2526 {
2527         GList *dlist;
2528         GList *flist;
2529         GList *work;
2530
2531         gboolean ok = TRUE;
2532
2533         DEBUG_1("deltree into: %s", fd->path);
2534
2535         level++;
2536         if (level > UTILITY_DELETE_MAX_DEPTH)
2537                 {
2538                 log_printf("folder recursion depth past %d, giving up\n", UTILITY_DELETE_MAX_DEPTH);
2539                 // ud->fail_fd = fd
2540                 return 0;
2541                 }
2542
2543         if (!filelist_read_lstat(fd, &flist, &dlist))
2544                 {
2545                 // ud->fail_fd = fd
2546                 return 0;
2547                 }
2548
2549         if (ok)
2550                 {
2551                 ok = file_data_sc_add_ci_delete(fd);
2552                 if (ok)
2553                         {
2554                         ud->content_list = g_list_prepend(ud->content_list, fd);
2555                         }
2556                 // ud->fail_fd = fd
2557                 }
2558
2559         work = dlist;
2560         while (work && ok)
2561                 {
2562                 FileData *lfd;
2563
2564                 lfd = static_cast<FileData *>(work->data);
2565                 work = work->next;
2566
2567                 ok = file_util_delete_dir_empty_path(ud, lfd, level);
2568                 }
2569
2570         work = flist;
2571         while (work && ok)
2572                 {
2573                 FileData *lfd;
2574
2575                 lfd = static_cast<FileData *>(work->data);
2576                 work = work->next;
2577
2578                 DEBUG_1("deltree child: %s", lfd->path);
2579
2580                 ok = file_data_sc_add_ci_delete(lfd);
2581                 if (ok)
2582                         {
2583                         ud->content_list = g_list_prepend(ud->content_list, lfd);
2584                         }
2585                 // ud->fail_fd = fd
2586                 }
2587
2588         filelist_free(dlist);
2589         filelist_free(flist);
2590
2591
2592         DEBUG_1("deltree done: %s", fd->path);
2593
2594         return ok;
2595 }
2596
2597 static gboolean file_util_delete_dir_prepare(UtilityData *ud, GList *flist, GList *dlist)
2598 {
2599         gboolean ok = TRUE;
2600         GList *work;
2601
2602
2603         work = dlist;
2604         while (work && ok)
2605                 {
2606                 FileData *fd;
2607
2608                 fd = static_cast<FileData *>(work->data);
2609                 work = work->next;
2610
2611                 ok = file_util_delete_dir_empty_path(ud, fd, 0);
2612                 }
2613
2614         work = flist;
2615         if (ok && file_data_sc_add_ci_delete_list(flist))
2616                 {
2617                 ud->content_list = g_list_concat(filelist_copy(flist), ud->content_list);
2618                 }
2619         else
2620                 {
2621                 ok = FALSE;
2622                 }
2623
2624         if (ok)
2625                 {
2626                 ok = file_data_sc_add_ci_delete(ud->dir_fd);
2627                 }
2628
2629         if (!ok)
2630                 {
2631                 work = ud->content_list;
2632                 while (work)
2633                         {
2634                         FileData *fd;
2635
2636                         fd = static_cast<FileData *>(work->data);
2637                         work = work->next;
2638                         file_data_sc_free_ci(fd);
2639                         }
2640                 }
2641
2642         return ok;
2643 }
2644
2645 static void file_util_delete_dir_full(FileData *fd, GtkWidget *parent, UtilityPhase phase)
2646 {
2647         GList *dlist;
2648         GList *flist;
2649         GList *rlist;
2650
2651         if (!isdir(fd->path)) return;
2652
2653         if (islink(fd->path))
2654                 {
2655                 UtilityData *ud;
2656                 ud = file_util_data_new(UTILITY_TYPE_DELETE_LINK);
2657
2658                 ud->phase = phase;
2659                 ud->with_sidecars = TRUE;
2660                 ud->dir_fd = file_data_ref(fd);
2661                 ud->content_list = NULL;
2662                 ud->flist = NULL;
2663
2664                 ud->parent = parent;
2665
2666                 ud->messages.title = _("Delete folder");
2667                 ud->messages.question = _("Delete symbolic link?");
2668                 ud->messages.desc_flist = "";
2669                 ud->messages.desc_source_fd = _("This will delete the symbolic link.\n"
2670                                                 "The folder this link points to will not be deleted.");
2671                 ud->messages.fail = _("Link deletion failed");
2672
2673                 file_util_dialog_run(ud);
2674                 return;
2675                 }
2676
2677         if (!access_file(fd->path, W_OK | X_OK))
2678                 {
2679                 gchar *text;
2680
2681                 text = g_strdup_printf(_("Unable to remove folder %s\n"
2682                                          "Permissions do not allow writing to the folder."), fd->path);
2683                 file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, parent);
2684                 g_free(text);
2685
2686                 return;
2687                 }
2688
2689         if (!filelist_read_lstat(fd, &flist, &dlist))
2690                 {
2691                 gchar *text;
2692
2693                 text = g_strdup_printf(_("Unable to list contents of folder %s"), fd->path);
2694                 file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, parent);
2695                 g_free(text);
2696
2697                 return;
2698                 }
2699
2700         rlist = file_util_delete_dir_remaining_folders(dlist);
2701         if (rlist)
2702                 {
2703                 GenericDialog *gd;
2704                 GtkWidget *box;
2705                 gchar *text;
2706
2707                 gd = file_util_gen_dlg(_("Folder contains subfolders"), "dlg_warning",
2708                                         parent, TRUE, NULL, NULL);
2709                 generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
2710
2711                 text = g_strdup_printf(_("Unable to delete the folder:\n\n%s\n\n"
2712                                          "This folder contains subfolders which must be moved before it can be deleted."),
2713                                         fd->path);
2714                 box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
2715                                                  _("Folder contains subfolders"),
2716                                                  text, TRUE);
2717                 g_free(text);
2718
2719                 box = pref_group_new(box, TRUE, _("Subfolders:"), GTK_ORIENTATION_VERTICAL);
2720
2721                 rlist = filelist_sort_path(rlist);
2722                 file_util_dialog_add_list(box, rlist, FALSE, FALSE);
2723
2724                 gtk_widget_show(gd->dialog);
2725                 }
2726         else
2727                 {
2728                 UtilityData *ud;
2729                 ud = file_util_data_new(UTILITY_TYPE_DELETE_FOLDER);
2730
2731                 ud->phase = phase;
2732                 ud->with_sidecars = TRUE;
2733                 ud->dir_fd = file_data_ref(fd);
2734                 ud->content_list = NULL; /* will be filled by file_util_delete_dir_prepare */
2735                 ud->flist = flist = filelist_sort_path(flist);
2736
2737                 ud->parent = parent;
2738
2739                 ud->messages.title = _("Delete folder");
2740                 ud->messages.question = _("Delete folder?");
2741                 ud->messages.desc_flist = _("The folder contains these files:");
2742                 ud->messages.desc_source_fd = _("This will delete the folder.\n"
2743                                                 "The contents of this folder will also be deleted.");
2744                 ud->messages.fail = _("File deletion failed");
2745
2746                 if (!file_util_delete_dir_prepare(ud, flist, dlist))
2747                         {
2748                         gchar *text;
2749
2750                         text = g_strdup_printf(_("Unable to list contents of folder %s"), fd->path);
2751                         file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, parent);
2752                         g_free(text);
2753                         file_data_unref(ud->dir_fd);
2754                         file_util_data_free(ud);
2755                         }
2756                 else
2757                         {
2758                         filelist_free(dlist);
2759                         file_util_dialog_run(ud);
2760                         return;
2761                         }
2762                 }
2763
2764         g_list_free(rlist);
2765         filelist_free(dlist);
2766         filelist_free(flist);
2767 }
2768
2769 static gboolean file_util_rename_dir_scan(UtilityData *ud, FileData *fd)
2770 {
2771         GList *dlist;
2772         GList *flist;
2773         GList *work;
2774
2775         gboolean ok = TRUE;
2776
2777         if (!filelist_read_lstat(fd, &flist, &dlist))
2778                 {
2779                 // ud->fail_fd = fd
2780                 return 0;
2781                 }
2782
2783         ud->content_list = g_list_concat(flist, ud->content_list);
2784
2785         work = dlist;
2786         while (work && ok)
2787                 {
2788                 FileData *lfd;
2789
2790                 lfd = static_cast<FileData *>(work->data);
2791                 work = work->next;
2792
2793                 ud->content_list = g_list_prepend(ud->content_list, file_data_ref(lfd));
2794                 ok = file_util_rename_dir_scan(ud, lfd);
2795                 }
2796
2797         filelist_free(dlist);
2798
2799         return ok;
2800 }
2801
2802 static gboolean file_util_rename_dir_prepare(UtilityData *ud, const gchar *new_path)
2803 {
2804         gboolean ok;
2805         GList *work;
2806         gint orig_len = strlen(ud->dir_fd->path);
2807
2808         ok = file_util_rename_dir_scan(ud, ud->dir_fd);
2809
2810         work = ud->content_list;
2811
2812         while (ok && work)
2813                 {
2814                 gchar *np;
2815                 FileData *fd;
2816
2817                 fd = static_cast<FileData *>(work->data);
2818                 work = work->next;
2819
2820                 g_assert(strncmp(fd->path, ud->dir_fd->path, orig_len) == 0);
2821
2822                 np = g_strconcat(new_path, fd->path + orig_len, NULL);
2823
2824                 ok = file_data_sc_add_ci_rename(fd, np);
2825
2826                 DEBUG_1("Dir rename: %s -> %s", fd->path, np);
2827                 g_free(np);
2828                 }
2829
2830         if (ok)
2831                 {
2832                 ok = file_data_sc_add_ci_rename(ud->dir_fd, new_path);
2833                 }
2834
2835         if (!ok)
2836                 {
2837                 work = ud->content_list;
2838                 while (work)
2839                         {
2840                         FileData *fd;
2841
2842                         fd = static_cast<FileData *>(work->data);
2843                         work = work->next;
2844                         file_data_sc_free_ci(fd);
2845                         }
2846                 }
2847
2848         return ok;
2849 }
2850
2851
2852 static void file_util_rename_dir_full(FileData *fd, const gchar *new_path, GtkWidget *parent, UtilityPhase phase, FileUtilDoneFunc done_func, gpointer done_data)
2853 {
2854         UtilityData *ud;
2855
2856         ud = file_util_data_new(UTILITY_TYPE_RENAME_FOLDER);
2857
2858         ud->phase = phase;
2859         ud->with_sidecars = TRUE; /* does not matter, the directory should not have sidecars
2860                                     and the content must be handled including sidecars */
2861
2862         ud->dir_fd = file_data_ref(fd);
2863         ud->flist = NULL;
2864         ud->content_list = NULL;
2865         ud->parent = parent;
2866
2867         ud->done_func = done_func;
2868         ud->done_data = done_data;
2869         ud->dest_path = g_strdup(new_path);
2870
2871         ud->messages.title = _("Rename");
2872         ud->messages.question = _("Rename folder?");
2873         ud->messages.desc_flist = _("The folder contains the following files");
2874         ud->messages.desc_source_fd = "";
2875         ud->messages.fail = _("Rename failed");
2876
2877         if (!file_util_rename_dir_prepare(ud, new_path))
2878                 {
2879                 file_util_warn_op_in_progress(ud->messages.fail);
2880                 file_util_data_free(ud);
2881                 return;
2882                 }
2883
2884 //      ud->flist = filelist_recursive(fd);
2885
2886         file_util_dialog_run(ud);
2887 }
2888
2889 static void file_util_create_dir_full(FileData *fd, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase, FileUtilDoneFunc done_func, gpointer done_data)
2890 {
2891         UtilityData *ud;
2892
2893         ud = file_util_data_new(UTILITY_TYPE_CREATE_FOLDER);
2894
2895         ud->phase = phase;
2896         ud->with_sidecars = TRUE;
2897
2898         ud->dir_fd = NULL;
2899         ud->flist = NULL;
2900         ud->content_list = NULL;
2901         ud->parent = parent;
2902
2903         if (dest_path)
2904                 {
2905                 g_assert_not_reached(); // not used in current design
2906                 ud->dest_path = g_strdup(dest_path);
2907                 }
2908         else
2909                 {
2910                 gchar *buf = new_folder(GTK_WINDOW(parent), fd->path);
2911                 if (!buf)
2912                         {
2913                         ud->phase = UTILITY_PHASE_CANCEL;
2914                         ud->dir_fd = NULL;
2915                         }
2916                 else
2917                         {
2918                         ud->dest_path = buf;
2919                         ud->dir_fd = file_data_new_dir(ud->dest_path);
2920                         }
2921                 }
2922
2923         ud->done_func = done_func;
2924         ud->done_data = done_data;
2925
2926         ud->messages.title = _("Create Folder");
2927         ud->messages.question = _("Create folder?");
2928         ud->messages.desc_flist = "";
2929         ud->messages.desc_source_fd = "";
2930         ud->messages.fail = _("Can't create folder");
2931
2932         file_util_dialog_run(ud);
2933 }
2934
2935
2936 static gboolean file_util_write_metadata_first_after_done(gpointer data)
2937 {
2938         UtilityDelayData *dd = static_cast<UtilityDelayData *>(data);
2939
2940         /* start the delayed operation with original arguments */
2941         switch (dd->type)
2942                 {
2943                 case UTILITY_TYPE_FILTER:
2944                 case UTILITY_TYPE_EDITOR:
2945                         file_util_start_editor_full(dd->editor_key, NULL, dd->flist, dd->dest_path, NULL, dd->parent, dd->phase);
2946                         break;
2947                 case UTILITY_TYPE_COPY:
2948                         file_util_copy_full(NULL, dd->flist, dd->dest_path, dd->parent, dd->phase);
2949                         break;
2950                 default:
2951                         g_warning("unsupported type");
2952                 }
2953         g_free(dd->dest_path);
2954         g_free(dd->editor_key);
2955         g_free(dd);
2956         return FALSE;
2957 }
2958
2959 static void file_util_write_metadata_first_done(gboolean success, const gchar *UNUSED(done_path), gpointer data)
2960 {
2961         UtilityDelayData *dd = static_cast<UtilityDelayData *>(data);
2962
2963         if (success)
2964                 {
2965                 dd->idle_id = g_idle_add(file_util_write_metadata_first_after_done, dd);
2966                 return;
2967                 }
2968
2969         /* the operation was cancelled */
2970         filelist_free(dd->flist);
2971         g_free(dd->dest_path);
2972         g_free(dd->editor_key);
2973         g_free(dd);
2974 }
2975
2976 static gboolean file_util_write_metadata_first(UtilityType type, UtilityPhase phase, GList *flist, const gchar *dest_path, const gchar *editor_key, GtkWidget *parent)
2977 {
2978         GList *unsaved = NULL;
2979         UtilityDelayData *dd;
2980
2981         GList *work;
2982
2983         work = flist;
2984         while (work)
2985                 {
2986                 FileData *fd = static_cast<FileData *>(work->data);
2987                 work = work->next;
2988
2989                 if (fd->change)
2990                         {
2991                         filelist_free(unsaved);
2992                         return FALSE; /* another op. in progress, let the caller handle it */
2993                         }
2994
2995                 if (fd->modified_xmp) /* has unsaved metadata */
2996                         {
2997                         unsaved = g_list_prepend(unsaved, file_data_ref(fd));
2998                         }
2999                 }
3000
3001         if (!unsaved) return FALSE;
3002
3003         /* save arguments of the original operation */
3004
3005         dd = g_new0(UtilityDelayData, 1);
3006
3007         dd->type = type;
3008         dd->phase = phase;
3009         dd->flist = flist;
3010         dd->dest_path = g_strdup(dest_path);
3011         dd->editor_key = g_strdup(editor_key);
3012         dd->parent = parent;
3013
3014         file_util_write_metadata(NULL, unsaved, parent, FALSE, file_util_write_metadata_first_done, dd);
3015         return TRUE;
3016 }
3017
3018
3019 /* full-featured entry points
3020 */
3021
3022 void file_util_delete(FileData *source_fd, GList *source_list, GtkWidget *parent)
3023 {
3024         file_util_delete_full(source_fd, source_list, parent, options->file_ops.confirm_delete ? UTILITY_PHASE_START : UTILITY_PHASE_ENTERING, NULL, NULL);
3025 }
3026
3027 void file_util_delete_notify_done(FileData *source_fd, GList *source_list, GtkWidget *parent, FileUtilDoneFunc done_func, gpointer done_data)
3028 {
3029         file_util_delete_full(source_fd, source_list, parent, options->file_ops.confirm_delete ? UTILITY_PHASE_START : UTILITY_PHASE_ENTERING, done_func, done_data);
3030 }
3031
3032 void file_util_write_metadata(FileData *source_fd, GList *source_list, GtkWidget *parent, gboolean force_dialog, FileUtilDoneFunc done_func, gpointer done_data)
3033 {
3034         file_util_write_metadata_full(source_fd, source_list, parent,
3035                                       ((options->metadata.save_in_image_file && options->metadata.confirm_write) || force_dialog) ? UTILITY_PHASE_START : UTILITY_PHASE_ENTERING,
3036                                       done_func, done_data);
3037 }
3038
3039 void file_util_copy(FileData *source_fd, GList *source_list, const gchar *dest_path, GtkWidget *parent)
3040 {
3041         file_util_copy_full(source_fd, source_list, dest_path, parent, UTILITY_PHASE_START);
3042 }
3043
3044 void file_util_move(FileData *source_fd, GList *source_list, const gchar *dest_path, GtkWidget *parent)
3045 {
3046         file_util_move_full(source_fd, source_list, dest_path, parent, UTILITY_PHASE_START);
3047 }
3048
3049 void file_util_rename(FileData *source_fd, GList *source_list, GtkWidget *parent)
3050 {
3051         file_util_rename_full(source_fd, source_list, NULL, parent, UTILITY_PHASE_START);
3052 }
3053
3054 /* these avoid the location entry dialog unless there is an error, list must be files only and
3055  * dest_path must be a valid directory path
3056  */
3057 void file_util_move_simple(GList *list, const gchar *dest_path, GtkWidget *parent)
3058 {
3059         file_util_move_full(NULL, list, dest_path, parent, UTILITY_PHASE_ENTERING);
3060 }
3061
3062 void file_util_copy_simple(GList *list, const gchar *dest_path, GtkWidget *parent)
3063 {
3064         file_util_copy_full(NULL, list, dest_path, parent, UTILITY_PHASE_ENTERING);
3065 }
3066
3067 void file_util_rename_simple(FileData *fd, const gchar *dest_path, GtkWidget *parent)
3068 {
3069         file_util_rename_full(fd, NULL, dest_path, parent, UTILITY_PHASE_ENTERING);
3070 }
3071
3072
3073 void file_util_start_editor_from_file(const gchar *key, FileData *fd, GtkWidget *parent)
3074 {
3075         file_util_start_editor_full(key, fd, NULL, NULL, NULL, parent, UTILITY_PHASE_ENTERING);
3076 }
3077
3078 void file_util_start_editor_from_filelist(const gchar *key, GList *list, const gchar *working_directory, GtkWidget *parent)
3079 {
3080         file_util_start_editor_full(key, NULL, list, NULL, working_directory, parent, UTILITY_PHASE_ENTERING);
3081 }
3082
3083 //void file_util_start_filter_from_file(const gchar *key, FileData *fd, const gchar *dest_path, GtkWidget *parent)
3084 //{
3085         //file_util_start_editor_full(key, fd, NULL, dest_path, NULL, parent, UTILITY_PHASE_ENTERING);
3086 //}
3087
3088 void file_util_start_filter_from_filelist(const gchar *key, GList *list, const gchar *dest_path, GtkWidget *parent)
3089 {
3090         file_util_start_editor_full(key, NULL, list, dest_path, NULL, parent, UTILITY_PHASE_ENTERING);
3091 }
3092
3093 void file_util_delete_dir(FileData *fd, GtkWidget *parent)
3094 {
3095         file_util_delete_dir_full(fd, parent, UTILITY_PHASE_START);
3096 }
3097
3098 void file_util_create_dir(FileData *dir_fd, GtkWidget *parent, FileUtilDoneFunc done_func, gpointer done_data)
3099 {
3100         file_util_create_dir_full(dir_fd, NULL, parent, UTILITY_PHASE_ENTERING, done_func, done_data);
3101 }
3102
3103 void file_util_rename_dir(FileData *source_fd, const gchar *new_path, GtkWidget *parent, FileUtilDoneFunc done_func, gpointer done_data)
3104 {
3105         file_util_rename_dir_full(source_fd, new_path, parent, UTILITY_PHASE_ENTERING, done_func, done_data);
3106 }
3107
3108 /**
3109  * @brief
3110  * @param clipboard
3111  * @param selection_data
3112  * @param info
3113  * @param data #_ClipboardData
3114  *
3115  *
3116  */
3117 static void clipboard_get_func(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gpointer data)
3118 {
3119         ClipboardData *cbd = static_cast<ClipboardData *>(data);
3120         gchar *file_path;
3121         gchar *file_path_quoted = NULL;
3122         gchar *file_path_uri;
3123         GString *path_list_str;
3124         GList *work;
3125
3126         path_list_str = g_string_new("");
3127         work = cbd->path_list;
3128
3129         if (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD) && info == CLIPBOARD_X_SPECIAL_GNOME_COPIED_FILES)
3130                 {
3131                 g_string_append(path_list_str, "copy");
3132
3133                 while (work)
3134                         {
3135                         file_path = static_cast<gchar *>(work->data);
3136                         work = work->next;
3137
3138                         file_path_uri = g_filename_to_uri(file_path, NULL, NULL);
3139                         g_string_append(path_list_str, "\n");
3140                         g_string_append(path_list_str, file_path_uri);
3141                         g_free(file_path_uri);
3142                         }
3143                 }
3144         else
3145                 {
3146                 while (work)
3147                         {
3148                         file_path = static_cast<gchar *>(work->data);
3149                         work = work->next;
3150
3151                         if (cbd->quoted)
3152                                 {
3153                                 file_path_quoted = g_shell_quote(file_path);
3154                                 g_string_append(path_list_str, file_path_quoted);
3155                                 g_free(file_path_quoted);
3156                                 }
3157                         else
3158                                 {
3159                                 g_string_append(path_list_str, file_path);
3160                                 }
3161
3162                         if (work)
3163                                 {
3164                                 g_string_append_c(path_list_str, ' ');
3165                                 }
3166                         }
3167                 }
3168
3169         gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data), 8, reinterpret_cast<guchar *>(path_list_str->str), strlen(path_list_str->str));
3170
3171         g_string_free(path_list_str, TRUE);
3172 }
3173
3174 /**
3175  * @brief
3176  * @param UNUSED
3177  * @param data _ClipboardData
3178  *
3179  *
3180  */
3181 static void clipboard_clear_func(GtkClipboard *UNUSED(clipboard), gpointer data)
3182 {
3183         ClipboardData *cbd = static_cast<ClipboardData *>(data);
3184
3185         string_list_free(cbd->path_list);
3186         g_free(cbd);
3187 }
3188
3189 void file_util_copy_path_to_clipboard(FileData *fd, gboolean quoted)
3190 {
3191         ClipboardData *cbd;
3192
3193         if (!fd || !*fd->path) return;
3194
3195
3196         if (options->clipboard_selection == CLIPBOARD_PRIMARY || options->clipboard_selection == CLIPBOARD_BOTH)
3197                 {
3198                 cbd = g_new0(ClipboardData, 1);
3199                 cbd->path_list = NULL;
3200                 cbd->quoted = quoted;
3201                 cbd->path_list = g_list_append(cbd->path_list, g_strdup(fd->path));
3202
3203                 gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_PRIMARY), target_types, target_types_n, clipboard_get_func, clipboard_clear_func, cbd);
3204                 }
3205
3206         if (options->clipboard_selection == CLIPBOARD_CLIPBOARD || options->clipboard_selection == CLIPBOARD_BOTH)
3207                 {
3208                 cbd = g_new0(ClipboardData, 1);
3209                 cbd->path_list = NULL;
3210                 cbd->quoted = quoted;
3211                 cbd->path_list = g_list_append(cbd->path_list, g_strdup(fd->path));
3212
3213                 gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), target_types, target_types_n, clipboard_get_func, clipboard_clear_func, cbd);
3214                 }
3215 }
3216
3217 /**
3218  * @brief
3219  * @param fd_list List of fd
3220  * @param quoted
3221  *
3222  *
3223  */
3224 void file_util_copy_path_list_to_clipboard(GList *fd_list, gboolean quoted)
3225 {
3226         ClipboardData *cbd;
3227         FileData *fd;
3228         GList *work;
3229
3230         if (options->clipboard_selection == CLIPBOARD_PRIMARY || options->clipboard_selection == CLIPBOARD_BOTH)
3231                 {
3232                 cbd = g_new0(ClipboardData, 1);
3233                 cbd->path_list = NULL;
3234                 cbd->quoted = quoted;
3235                 work = fd_list;
3236
3237                 while (work)
3238                         {
3239                         fd = static_cast<FileData *>(work->data);
3240                         work = work->next;
3241
3242                         if (!fd || !*fd->path) continue;
3243
3244                         cbd->path_list = g_list_append(cbd->path_list, g_strdup(fd->path));
3245                         }
3246
3247                         gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_PRIMARY), target_types, target_types_n, clipboard_get_func, clipboard_clear_func, cbd);
3248                 }
3249
3250         if (options->clipboard_selection == CLIPBOARD_CLIPBOARD || options->clipboard_selection == CLIPBOARD_BOTH)
3251                 {
3252                 cbd = g_new0(ClipboardData, 1);
3253                 cbd->path_list = NULL;
3254                 cbd->quoted = quoted;
3255                 work = fd_list;
3256
3257                 while (work)
3258                         {
3259                         fd = static_cast<FileData *>(work->data);
3260                         work = work->next;
3261
3262                         if (!fd || !*fd->path) continue;
3263
3264                         cbd->path_list = g_list_append(cbd->path_list, g_strdup(fd->path));
3265                         }
3266
3267                         gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), target_types, target_types_n, clipboard_get_func, clipboard_clear_func, cbd);
3268                 }
3269
3270         filelist_free(fd_list);
3271 }
3272
3273 static void new_folder_entry_activate_cb(GtkWidget *UNUSED(widget), gpointer data)
3274 {
3275         GtkDialog *dialog = static_cast<GtkDialog *>(data);
3276
3277         gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
3278 }
3279
3280 gchar *new_folder(GtkWindow *window , gchar *path)
3281 {
3282         GtkWidget *hbox;
3283         GtkWidget *vbox;
3284         GtkWidget *label;
3285         gchar *buf;
3286         gchar *folder_name;
3287         gchar *folder_path;
3288         GtkWidget *image;
3289         GtkWidget *folder_name_entry;
3290         GtkWidget *dialog;
3291         GtkWidget *dialog_warning;
3292         GtkWidget *content_area;
3293         gboolean ok_or_cancel = FALSE;
3294         gint result;
3295         gchar *new_folder_name;
3296         gchar *new_folder_path_full = NULL;
3297         gchar *window_title;
3298         PangoLayout *layout;
3299         gint width, height;
3300
3301         buf = g_build_filename(path, _("New folder"), NULL);
3302         folder_path = unique_filename(buf, NULL, " ", FALSE);
3303         folder_name = g_path_get_basename(folder_path);
3304         window_title = g_strconcat(_("Create Folder - "), GQ_APPNAME, NULL);
3305
3306         dialog = gtk_dialog_new_with_buttons(window_title,
3307                                                                                 GTK_WINDOW(window),
3308                                                                                 GTK_DIALOG_MODAL,
3309                                                                                 GTK_STOCK_CANCEL,
3310                                                                                 GTK_RESPONSE_REJECT,
3311                                                                                 GTK_STOCK_OK,
3312                                                                                 GTK_RESPONSE_ACCEPT,
3313                                                                                 NULL);
3314
3315         layout = gtk_widget_create_pango_layout(GTK_WIDGET(dialog), window_title);
3316         pango_layout_get_pixel_size(layout, &width, &height);
3317         gtk_window_set_default_size(GTK_WINDOW(dialog), width * 2, -1);
3318
3319         content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
3320         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
3321         gtk_box_pack_start(GTK_BOX(content_area), vbox, FALSE, FALSE, 0);
3322
3323         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
3324         gtk_container_set_border_width(GTK_CONTAINER(hbox), PREF_PAD_GAP);
3325         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3326         image = gtk_image_new_from_icon_name("dialog-question", GTK_ICON_SIZE_DIALOG);
3327         gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
3328         label = gtk_label_new(_("Create new folder"));
3329         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
3330
3331         folder_name_entry = gtk_entry_new();
3332         gtk_entry_set_text(GTK_ENTRY(folder_name_entry), folder_name);
3333         gtk_box_pack_start(GTK_BOX(vbox), folder_name_entry, FALSE, FALSE, PREF_PAD_SPACE);
3334         g_signal_connect(G_OBJECT(folder_name_entry), "activate", G_CALLBACK(new_folder_entry_activate_cb), dialog);
3335
3336         gtk_widget_show_all(dialog);
3337
3338         while (ok_or_cancel == FALSE)
3339                 {
3340                 result = gtk_dialog_run(GTK_DIALOG(dialog));
3341
3342                 switch (result)
3343                         {
3344                         case GTK_RESPONSE_ACCEPT:
3345                                 new_folder_name = g_strdup(gtk_entry_get_text(GTK_ENTRY(folder_name_entry)));
3346                                 new_folder_path_full = g_build_filename(path, new_folder_name, NULL);
3347                                 if (isname(new_folder_path_full))
3348                                         {
3349                                         dialog_warning = gtk_message_dialog_new(GTK_WINDOW(window),
3350                                                                                         GTK_DIALOG_MODAL,
3351                                                                                         GTK_MESSAGE_WARNING,
3352                                                                                         GTK_BUTTONS_OK,
3353                                                                                         _("Cannot create folder:"));
3354                                         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_warning), "%s", new_folder_name);
3355                                         gtk_window_set_title(GTK_WINDOW(dialog_warning), GQ_APPNAME);
3356
3357                                         gtk_dialog_run(GTK_DIALOG(dialog_warning));
3358
3359                                         gtk_widget_destroy(dialog_warning);
3360                                         g_free(new_folder_path_full);
3361                                         new_folder_path_full = NULL;
3362                                         g_free(new_folder_name);
3363                                         ok_or_cancel = FALSE;
3364                                         }
3365                                 else
3366                                         {
3367                                         ok_or_cancel = TRUE;
3368                                         }
3369                                 break;
3370                         case GTK_RESPONSE_REJECT:
3371                                 ok_or_cancel = TRUE;
3372                                 break;
3373                         default:
3374                                 ok_or_cancel = TRUE;
3375                                 break;
3376                   }
3377                 }
3378
3379         g_free(buf);
3380         g_free(folder_path);
3381         g_free(folder_name);
3382         g_object_unref(layout);
3383         g_free(window_title);
3384         gtk_widget_destroy(dialog);
3385
3386         return new_folder_path_full;
3387 }
3388 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */