Merge branch '2123_crash_while_copy'
[midnight-commander.git] / src / filegui.c
blob1d798a672abe40cf446c4f5d9c7a3ba396f12b17
1 /* File management GUI for the text mode edition
3 * Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 * 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
6 * Written by: 1994, 1995 Janne Kukonlehto
7 * 1994, 1995 Fred Leeflang
8 * 1994, 1995, 1996 Miguel de Icaza
9 * 1995, 1996 Jakub Jelinek
10 * 1997 Norbert Warmuth
11 * 1998 Pavel Machek
12 * 2009 Slava Zanko
14 * The copy code was based in GNU's cp, and was written by:
15 * Torbjorn Granlund, David MacKenzie, and Jim Meyering.
17 * The move code was based in GNU's mv, and was written by:
18 * Mike Parker and David MacKenzie.
20 * Janne Kukonlehto added much error recovery to them for being used
21 * in an interactive program.
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 2 of the License, or
26 * (at your option) any later version.
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
39 * Please note that all dialogs used here must be safe for background
40 * operations.
43 /** \file filegui.c
44 * \brief Source: file management GUI for the text mode edition
47 /* {{{ Include files */
49 #include <config.h>
51 #include <errno.h>
52 #include <ctype.h>
53 #include <stdio.h>
54 #include <string.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
58 #if defined(STAT_STATVFS) \
59 && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) \
60 || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
61 # include <sys/statvfs.h>
62 # define STRUCT_STATFS struct statvfs
63 # define STATFS statvfs
64 #elif defined(HAVE_STATFS) && !defined(STAT_STATFS4)
65 # ifdef HAVE_SYS_VFS_H
66 # include <sys/vfs.h>
67 # elif defined(HAVE_SYS_MOUNT_H) && defined(HAVE_SYS_PARAM_H)
68 # include <sys/param.h>
69 # include <sys/mount.h>
70 # elif defined(HAVE_SYS_STATFS_H)
71 # include <sys/statfs.h>
72 # endif
73 # define STRUCT_STATFS struct statfs
74 # define STATFS statfs
75 #endif
77 #include <unistd.h>
79 #include "lib/global.h"
81 #include "lib/tty/key.h" /* tty_get_event */
82 #include "lib/mcconfig.h"
83 #include "lib/search.h"
84 #include "lib/vfs/mc-vfs/vfs.h"
85 #include "lib/strescape.h"
86 #include "lib/strutil.h"
88 #include "setup.h" /* verbose */
89 #include "dialog.h" /* do_refresh() */
90 #include "widget.h" /* WLabel */
91 #include "main-widgets.h"
92 #include "main.h" /* the_hint */
93 #include "wtools.h" /* QuickDialog */
94 #include "panel.h" /* current_panel */
95 #include "fileopctx.h" /* FILE_CONT */
96 #include "filegui.h"
98 /* }}} */
99 /* *INDENT-OFF* */
100 typedef enum {
101 MSDOS_SUPER_MAGIC = 0x4d44,
102 NTFS_SB_MAGIC = 0x5346544e,
103 NTFS_3G_MAGIC = 0x65735546,
104 PROC_SUPER_MAGIC = 0x9fa0,
105 SMB_SUPER_MAGIC = 0x517B,
106 NCP_SUPER_MAGIC = 0x564c,
107 USBDEVICE_SUPER_MAGIC = 0x9fa2
108 } filegui_nonattrs_fs_t;
109 /* *INDENT-ON* */
111 /* Hack: the vfs code should not rely on this */
112 #define WITH_FULL_PATHS 1
114 /* Used for button result values */
115 typedef enum
117 REPLACE_YES = B_USER,
118 REPLACE_NO,
119 REPLACE_APPEND,
120 REPLACE_ALWAYS,
121 REPLACE_UPDATE,
122 REPLACE_NEVER,
123 REPLACE_ABORT,
124 REPLACE_SIZE,
125 REPLACE_REGET
126 } replace_action_t;
128 /* This structure describes the UI and internal data required by a file
129 * operation context.
131 typedef struct
133 /* ETA and bps */
134 gboolean showing_eta;
135 gboolean showing_bps;
137 /* Dialog and widgets for the operation progress window */
138 Dlg_head *op_dlg;
139 WLabel *file_string[2];
140 WLabel *file_label[2];
141 WGauge *progress_file_gauge;
142 WLabel *progress_file_label;
144 WGauge *progress_total_gauge;
146 WLabel *total_files_processed_label;
147 WLabel *time_label;
148 WLabel *total_bytes_label;
150 /* Query replace dialog */
151 Dlg_head *replace_dlg;
152 const char *replace_filename;
153 replace_action_t replace_result;
155 struct stat *s_stat, *d_stat;
156 } FileOpContextUI;
158 int classic_progressbar = 1;
160 /* Used to save the hint line */
161 static int last_hint_line;
163 /* File operate window sizes */
164 #define WX 58
165 #define WY 11
166 #define FCOPY_LABEL_X 3
168 static gboolean
169 filegui__check_attrs_on_fs (const char *fs_path)
171 #ifdef STATFS
172 STRUCT_STATFS stfs;
174 if (!setup_copymove_persistent_attr)
175 return FALSE;
177 if (STATFS (fs_path, &stfs) != 0)
178 return TRUE;
180 # ifdef __linux__
181 switch ((filegui_nonattrs_fs_t) stfs.f_type)
183 case MSDOS_SUPER_MAGIC:
184 case NTFS_SB_MAGIC:
185 case NTFS_3G_MAGIC:
186 case PROC_SUPER_MAGIC:
187 case SMB_SUPER_MAGIC:
188 case NCP_SUPER_MAGIC:
189 case USBDEVICE_SUPER_MAGIC:
190 return FALSE;
192 # elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) \
193 || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
194 if (!strcmp (stfs.f_fstypename, "msdos")
195 || !strcmp (stfs.f_fstypename, "msdosfs")
196 || !strcmp (stfs.f_fstypename, "ntfs")
197 || !strcmp (stfs.f_fstypename, "procfs")
198 || !strcmp (stfs.f_fstypename, "smbfs") || strstr (stfs.f_fstypename, "fusefs"))
199 return FALSE;
200 # elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
201 if (!strcmp (stfs.f_basetype, "pcfs")
202 || !strcmp (stfs.f_basetype, "ntfs")
203 || !strcmp (stfs.f_basetype, "proc")
204 || !strcmp (stfs.f_basetype, "smbfs") || !strcmp (stfs.f_basetype, "fuse"))
205 return FALSE;
206 # endif
207 #endif /* STATFS */
209 return TRUE;
212 FileProgressStatus
213 check_progress_buttons (FileOpContext * ctx)
215 int c;
216 Gpm_Event event;
217 FileOpContextUI *ui;
219 if (ctx->ui == NULL)
220 return FILE_CONT;
222 ui = ctx->ui;
224 event.x = -1; /* Don't show the GPM cursor */
225 c = tty_get_event (&event, FALSE, FALSE);
226 if (c == EV_NONE)
227 return FILE_CONT;
229 /* Reinitialize to avoid old values after events other than
230 selecting a button */
231 ui->op_dlg->ret_value = FILE_CONT;
233 dlg_process_event (ui->op_dlg, c, &event);
234 switch (ui->op_dlg->ret_value)
236 case FILE_SKIP:
237 return FILE_SKIP;
238 case B_CANCEL:
239 case FILE_ABORT:
240 return FILE_ABORT;
241 default:
242 return FILE_CONT;
246 /* {{{ File progress display routines */
248 void
249 file_op_context_create_ui_without_init (FileOpContext * ctx, gboolean with_eta,
250 filegui_dialog_type_t dialog_type)
252 FileOpContextUI *ui;
253 int minus = 0, total_reserve = 0;
254 const char *abort_button_label = N_("&Abort");
255 const char *skip_button_label = N_("&Skip");
256 int abort_button_width, skip_button_width, buttons_width;
257 int dlg_width;
259 g_return_if_fail (ctx != NULL);
260 g_return_if_fail (ctx->ui == NULL);
262 #ifdef ENABLE_NLS
263 abort_button_label = _(abort_button_label);
264 skip_button_label = _(skip_button_label);
265 #endif
267 abort_button_width = str_term_width1 (abort_button_label) + 3;
268 skip_button_width = str_term_width1 (skip_button_label) + 3;
269 buttons_width = abort_button_width + skip_button_width + 2;
271 dlg_width = max (WX, buttons_width + 6);
273 ui = g_new0 (FileOpContextUI, 1);
274 ctx->ui = ui;
276 ctx->dialog_type = dialog_type;
278 switch (dialog_type)
280 case FILEGUI_DIALOG_ONE_ITEM:
281 total_reserve = 0;
282 minus = verbose ? 0 : 2;
283 break;
284 case FILEGUI_DIALOG_MULTI_ITEM:
285 total_reserve = 5;
286 minus = verbose ? 0 : 7;
287 break;
288 case FILEGUI_DIALOG_DELETE_ITEM:
289 total_reserve = -5;
290 minus = 0;
291 break;
294 ctx->recursive_result = RECURSIVE_YES;
296 ui->replace_result = REPLACE_YES;
297 ui->showing_eta = with_eta;
298 ui->showing_bps = with_eta;
300 ui->op_dlg =
301 create_dlg (TRUE, 0, 0, WY - minus + 1 + total_reserve, dlg_width,
302 dialog_colors, NULL, NULL, op_names[ctx->operation], DLG_CENTER | DLG_REVERSE);
304 last_hint_line = the_hint->widget.y;
305 if ((ui->op_dlg->y + ui->op_dlg->lines) > last_hint_line)
306 the_hint->widget.y = ui->op_dlg->y + ui->op_dlg->lines + 1;
308 add_widget (ui->op_dlg,
309 button_new (WY - minus - 2 + total_reserve,
310 dlg_width / 2 + 1, FILE_ABORT,
311 NORMAL_BUTTON, abort_button_label, NULL));
312 add_widget (ui->op_dlg,
313 button_new (WY - minus - 2 + total_reserve,
314 dlg_width / 2 - 1 - skip_button_width, FILE_SKIP,
315 NORMAL_BUTTON, skip_button_label, NULL));
318 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
320 add_widget (ui->op_dlg, hline_new (8, 1, dlg_width - 2));
322 add_widget (ui->op_dlg, ui->total_bytes_label = label_new (8, FCOPY_LABEL_X + 15, ""));
324 add_widget (ui->op_dlg, ui->progress_total_gauge =
325 gauge_new (9, FCOPY_LABEL_X + 3, 0, 100, 0));
327 add_widget (ui->op_dlg, ui->total_files_processed_label =
328 label_new (11, FCOPY_LABEL_X, ""));
330 add_widget (ui->op_dlg, ui->time_label = label_new (12, FCOPY_LABEL_X, ""));
333 add_widget (ui->op_dlg, ui->progress_file_label = label_new (7, FCOPY_LABEL_X, ""));
335 add_widget (ui->op_dlg, ui->progress_file_gauge = gauge_new (6, FCOPY_LABEL_X + 3, 0, 100, 0));
337 add_widget (ui->op_dlg, ui->file_string[1] = label_new (5, FCOPY_LABEL_X, ""));
339 add_widget (ui->op_dlg, ui->file_label[1] = label_new (4, FCOPY_LABEL_X, ""));
340 add_widget (ui->op_dlg, ui->file_string[0] = label_new (3, FCOPY_LABEL_X, ""));
341 add_widget (ui->op_dlg, ui->file_label[0] = label_new (2, FCOPY_LABEL_X, ""));
343 if ((right_panel == current_panel) && !classic_progressbar)
345 ui->progress_file_gauge->from_left_to_right = FALSE;
346 if (dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
347 ui->progress_total_gauge->from_left_to_right = FALSE;
351 void
352 file_op_context_create_ui (FileOpContext * ctx, gboolean with_eta,
353 filegui_dialog_type_t dialog_type)
355 FileOpContextUI *ui;
357 g_return_if_fail (ctx != NULL);
358 g_return_if_fail (ctx->ui == NULL);
360 file_op_context_create_ui_without_init (ctx, with_eta, dialog_type);
361 ui = ctx->ui;
363 /* We will manage the dialog without any help, that's why
364 we have to call init_dlg */
365 init_dlg (ui->op_dlg);
368 void
369 file_op_context_destroy_ui (FileOpContext * ctx)
371 FileOpContextUI *ui;
373 g_return_if_fail (ctx != NULL);
375 if (ctx->ui)
377 ui = ctx->ui;
379 dlg_run_done (ui->op_dlg);
380 destroy_dlg (ui->op_dlg);
381 g_free (ui);
384 the_hint->widget.y = last_hint_line;
386 ctx->ui = NULL;
389 static void
390 file_frmt_time (char *buffer, double eta_secs)
392 int eta_hours, eta_mins, eta_s;
393 eta_hours = eta_secs / (60 * 60);
394 eta_mins = (eta_secs - (eta_hours * 60 * 60)) / 60;
395 eta_s = eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
396 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
399 static void
400 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
402 char _fmt_buff[BUF_TINY];
403 if (eta_secs <= 0.5 && !always_show)
405 *buffer = '\0';
406 return;
408 if (eta_secs <= 0.5)
409 eta_secs = 1;
410 file_frmt_time (_fmt_buff, eta_secs);
411 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
414 static void
415 file_bps_prepare_for_show (char *buffer, long bps)
417 if (bps > 1024 * 1024)
419 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
421 else if (bps > 1024)
423 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
425 else if (bps > 1)
427 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
429 else
430 *buffer = 0;
434 show progressbar for file
436 void
437 file_progress_show (FileOpContext * ctx, off_t done, off_t total,
438 const char *stalled_msg, gboolean force_update)
440 FileOpContextUI *ui;
441 char buffer[BUF_TINY];
442 char buffer2[BUF_TINY];
443 char buffer3[BUF_TINY];
445 g_return_if_fail (ctx != NULL);
447 if (ctx->ui == NULL)
448 return;
450 ui = ctx->ui;
452 if (!verbose)
453 return;
455 if (total == 0)
457 gauge_show (ui->progress_file_gauge, 0);
458 return;
461 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
462 gauge_show (ui->progress_file_gauge, 1);
464 if (!force_update)
465 return;
467 if (ui->showing_eta && ctx->eta_secs > 0.5)
469 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
470 file_bps_prepare_for_show (buffer3, ctx->bps);
471 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
473 else
475 g_snprintf (buffer, BUF_TINY, "%s", stalled_msg);
478 label_set_text (ui->progress_file_label, buffer);
481 void
482 file_progress_show_count (FileOpContext * ctx, off_t done, off_t total)
484 char buffer[BUF_TINY];
485 FileOpContextUI *ui;
487 g_return_if_fail (ctx != NULL);
489 if (ctx->dialog_type != FILEGUI_DIALOG_MULTI_ITEM || ctx->ui == NULL)
490 return;
492 ui = ctx->ui;
494 if (!verbose)
495 return;
497 g_snprintf (buffer, BUF_TINY, _("Files processed: %llu of %llu"),
498 (unsigned long long) done, (unsigned long long) total);
500 label_set_text (ui->total_files_processed_label, buffer);
503 void
504 file_progress_show_total (FileOpTotalContext * tctx, FileOpContext * ctx, double copyed_bytes,
505 gboolean need_show_total_summary)
507 char buffer[BUF_TINY];
508 char buffer2[BUF_TINY];
509 char buffer3[BUF_TINY];
510 char buffer4[BUF_TINY];
511 struct timeval tv_current;
512 FileOpContextUI *ui;
514 if (!verbose)
515 return;
517 if (ctx->dialog_type != FILEGUI_DIALOG_MULTI_ITEM || ctx->ui == NULL)
518 return;
520 ui = ctx->ui;
522 if (ctx->progress_bytes > 0)
524 gauge_set_value (ui->progress_total_gauge, 1024,
525 (int) (1024 * copyed_bytes / ctx->progress_bytes));
526 gauge_show (ui->progress_total_gauge, 1);
528 else
529 gauge_show (ui->progress_total_gauge, 0);
532 if (!need_show_total_summary && tctx->bps == 0)
533 return;
535 gettimeofday (&tv_current, NULL);
536 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
537 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
538 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
540 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
541 label_set_text (ui->time_label, buffer);
543 size_trunc_len (buffer2, 5, tctx->copyed_bytes, 0, panels_options.kilobyte_si);
544 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
546 g_snprintf (buffer, BUF_TINY, _("Total: %s of %s"), buffer2, buffer3);
548 label_set_text (ui->total_bytes_label, buffer);
552 /* }}} */
554 #define truncFileString(ui, s) str_trunc (s, 52)
555 #define truncFileStringSecure(ui, s) path_trunc (s, 52)
557 void
558 file_progress_show_source (FileOpContext * ctx, const char *s)
560 FileOpContextUI *ui;
562 g_return_if_fail (ctx != NULL);
564 if (ctx->ui == NULL)
565 return;
567 ui = ctx->ui;
569 if (s != NULL)
571 #ifdef WITH_FULL_PATHS
572 int i = strlen (current_panel->cwd);
574 /* We remove the full path we have added before */
575 if (!strncmp (s, current_panel->cwd, i))
577 if (s[i] == PATH_SEP)
578 s += i + 1;
580 #endif /* WITH_FULL_PATHS */
582 label_set_text (ui->file_label[0], _("Source"));
583 label_set_text (ui->file_string[0], truncFileString (ui, s));
585 else
587 label_set_text (ui->file_label[0], "");
588 label_set_text (ui->file_string[0], "");
592 void
593 file_progress_show_target (FileOpContext * ctx, const char *s)
595 FileOpContextUI *ui;
597 g_return_if_fail (ctx != NULL);
599 if (ctx->ui == NULL)
600 return;
602 ui = ctx->ui;
604 if (s != NULL)
606 label_set_text (ui->file_label[1], _("Target"));
607 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
609 else
611 label_set_text (ui->file_label[1], "");
612 label_set_text (ui->file_string[1], "");
616 void
617 file_progress_show_deleting (FileOpContext * ctx, const char *s)
619 FileOpContextUI *ui;
621 g_return_if_fail (ctx != NULL);
623 if (ctx->ui == NULL)
624 return;
626 ui = ctx->ui;
627 label_set_text (ui->file_label[0], _("Deleting"));
628 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
632 * FIXME: probably it is better to replace this with quick dialog machinery,
633 * but actually I'm not familiar with it and have not much time :(
634 * alex
636 static replace_action_t
637 overwrite_query_dialog (FileOpContext * ctx, enum OperationMode mode)
639 #define ADD_RD_BUTTON(i)\
640 add_widget (ui->replace_dlg,\
641 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value,\
642 NORMAL_BUTTON, rd_widgets [i].text, 0))
644 #define ADD_RD_LABEL(i, p1, p2)\
645 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2);\
646 add_widget (ui->replace_dlg,\
647 label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer))
649 /* dialog sizes */
650 const int rd_ylen = 17;
651 int rd_xlen = 60;
653 struct
655 const char *text;
656 int ypos, xpos;
657 int value; /* 0 for labels */
658 } rd_widgets[] =
660 /* *INDENT-OFF* */
661 /* 0 */
662 { N_("Target file already exists!"), 3, 4, 0 },
663 /* 1 */
664 { "%s", 4, 4, 0 },
665 #if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || (defined _LARGE_FILES && _LARGE_FILES)
666 /* 2 */
667 { N_("Source date: %s, size %llu"), 6, 4, 0 },
668 /* 3 */
669 { N_("Target date: %s, size %llu"), 7, 4, 0 },
670 #else
671 /* 2 */
672 { N_("Source date: %s, size %u"), 6, 4, 0 },
673 /* 3 */
674 { N_("Target date: %s, size %u"), 7, 4, 0 },
675 #endif
676 /* 4 */
677 { N_("&Abort"), 14, 25, REPLACE_ABORT },
678 /* 5 */
679 { N_("If &size differs"), 12, 28, REPLACE_SIZE },
680 /* 6 */
681 { N_("Non&e"), 11, 47, REPLACE_NEVER },
682 /* 7 */
683 { N_("&Update"), 11, 36, REPLACE_UPDATE },
684 /* 8 */
685 { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
686 /* 9 */
687 { N_("Overwrite all targets?"), 11, 4, 0 },
688 /* 10 */
689 { N_("&Reget"), 10, 28, REPLACE_REGET },
690 /* 11 */
691 { N_("A&ppend"), 9, 45, REPLACE_APPEND },
692 /* 12 */
693 { N_("&No"), 9, 37, REPLACE_NO },
694 /* 13 */
695 { N_("&Yes"), 9, 28, REPLACE_YES },
696 /* 14 */
697 { N_("Overwrite this target?"), 9, 4, 0 }
698 /* *INDENT-ON* */
701 const int num = sizeof (rd_widgets) / sizeof (rd_widgets[0]);
702 int *widgets_len;
704 FileOpContextUI *ui = ctx->ui;
706 char buffer[BUF_SMALL];
707 const char *title;
708 const char *stripped_name = strip_home_and_password (ui->replace_filename);
709 int stripped_name_len;
711 int result;
713 widgets_len = g_new0 (int, num);
715 if (mode == Foreground)
716 title = _("File exists");
717 else
718 title = _("Background process: File exists");
720 stripped_name_len = str_term_width1 (stripped_name);
723 int i, l1, l2, l, row;
725 for (i = 0; i < num; i++)
727 #ifdef ENABLE_NLS
728 if (i != 1) /* skip filename */
729 rd_widgets[i].text = _(rd_widgets[i].text);
730 #endif /* ENABLE_NLS */
731 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
735 * longest of "Overwrite..." labels
736 * (assume "Target date..." are short enough)
738 l1 = max (widgets_len[9], widgets_len[14]);
740 /* longest of button rows */
741 i = num;
742 for (row = l = l2 = 0; i--;)
743 if (rd_widgets[i].value != 0)
745 if (row != rd_widgets[i].ypos)
747 row = rd_widgets[i].ypos;
748 l2 = max (l2, l);
749 l = 0;
751 l += widgets_len[i] + 4;
754 l2 = max (l2, l); /* last row */
755 rd_xlen = max (rd_xlen, l1 + l2 + 8);
756 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
757 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
759 /* Now place widgets */
760 l1 += 5; /* start of first button in the row */
761 i = num;
762 for (l = l1, row = 0; --i > 1;)
763 if (rd_widgets[i].value != 0)
765 if (row != rd_widgets[i].ypos)
767 row = rd_widgets[i].ypos;
768 l = l1;
770 rd_widgets[i].xpos = l;
771 l += widgets_len[i] + 4;
774 /* Abort button is centered */
775 rd_widgets[4].xpos = (rd_xlen - widgets_len[4] - 3) / 2;
778 /* FIXME - missing help node */
779 ui->replace_dlg =
780 create_dlg (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, "[Replace]",
781 title, DLG_CENTER | DLG_REVERSE);
783 /* prompt -- centered */
784 add_widget (ui->replace_dlg,
785 label_new (rd_widgets[0].ypos, (rd_xlen - widgets_len[0]) / 2, rd_widgets[0].text));
786 /* file name -- centered */
787 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
788 stripped_name_len = str_term_width1 (stripped_name);
789 add_widget (ui->replace_dlg,
790 label_new (rd_widgets[1].ypos, (rd_xlen - stripped_name_len) / 2, stripped_name));
792 /* source date */
793 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), (off_t) ui->s_stat->st_size);
794 /* destination date */
795 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), (off_t) ui->d_stat->st_size);
797 ADD_RD_BUTTON (4); /* Abort */
798 ADD_RD_BUTTON (5); /* If size differs */
799 ADD_RD_BUTTON (6); /* None */
800 ADD_RD_BUTTON (7); /* Update */
801 ADD_RD_BUTTON (8); /* All" */
802 ADD_RD_LABEL (9, 0, 0); /* Overwrite all targets? */
804 /* "this target..." widgets */
805 if (!S_ISDIR (ui->d_stat->st_mode))
807 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
808 && (ui->s_stat->st_size > ui->d_stat->st_size))
809 ADD_RD_BUTTON (10); /* Reget */
811 ADD_RD_BUTTON (11); /* Append */
813 ADD_RD_BUTTON (12); /* No */
814 ADD_RD_BUTTON (13); /* Yes */
815 ADD_RD_LABEL (14, 0, 0); /* Overwrite this target? */
817 result = run_dlg (ui->replace_dlg);
818 destroy_dlg (ui->replace_dlg);
820 g_free (widgets_len);
822 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
823 #undef ADD_RD_LABEL
824 #undef ADD_RD_BUTTON
827 FileProgressStatus
828 file_progress_real_query_replace (FileOpContext * ctx,
829 enum OperationMode mode, const char *destname,
830 struct stat * _s_stat, struct stat * _d_stat)
832 FileOpContextUI *ui;
834 g_return_val_if_fail (ctx != NULL, FILE_CONT);
835 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
837 ui = ctx->ui;
839 if (ui->replace_result < REPLACE_ALWAYS)
841 ui->replace_filename = destname;
842 ui->s_stat = _s_stat;
843 ui->d_stat = _d_stat;
844 ui->replace_result = overwrite_query_dialog (ctx, mode);
847 switch (ui->replace_result)
849 case REPLACE_UPDATE:
850 do_refresh ();
851 if (_s_stat->st_mtime > _d_stat->st_mtime)
852 return FILE_CONT;
853 else
854 return FILE_SKIP;
856 case REPLACE_SIZE:
857 do_refresh ();
858 if (_s_stat->st_size == _d_stat->st_size)
859 return FILE_SKIP;
860 else
861 return FILE_CONT;
863 case REPLACE_REGET:
864 /* Careful: we fall through and set do_append */
865 ctx->do_reget = _d_stat->st_size;
867 case REPLACE_APPEND:
868 ctx->do_append = TRUE;
870 case REPLACE_YES:
871 case REPLACE_ALWAYS:
872 do_refresh ();
873 return FILE_CONT;
874 case REPLACE_NO:
875 case REPLACE_NEVER:
876 do_refresh ();
877 return FILE_SKIP;
878 case REPLACE_ABORT:
879 default:
880 return FILE_ABORT;
884 static gboolean
885 is_wildcarded (char *p)
887 for (; *p; p++)
889 if (*p == '*')
890 return TRUE;
891 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
892 return TRUE;
894 return FALSE;
897 char *
898 file_mask_dialog (FileOpContext * ctx, FileOperation operation,
899 gboolean only_one,
900 const char *format, const void *text,
901 const char *def_text, gboolean * do_background)
903 const size_t FMDY = 13;
904 const size_t FMDX = 68;
905 size_t fmd_xlen;
907 /* buttons */
908 const size_t gap = 1;
909 size_t b0_len, b2_len;
910 size_t b1_len = 0;
912 int source_easy_patterns = easy_patterns;
913 size_t i, len;
914 char fmd_buf[BUF_MEDIUM];
915 char *source_mask, *orig_mask, *dest_dir, *tmp;
916 char *def_text_secure;
917 int val;
919 QuickWidget fmd_widgets[] = {
920 /* 0 */ QUICK_BUTTON (42, 64, 10, FMDY, N_("&Cancel"), B_CANCEL, NULL),
921 #ifdef WITH_BACKGROUND
922 /* 1 */ QUICK_BUTTON (25, 64, 10, FMDY, N_("&Background"), B_USER, NULL),
923 #define OFFSET 0
924 #else
925 #define OFFSET 1
926 #endif /* WITH_BACKGROUND */
927 /* 2 - OFFSET */
928 QUICK_BUTTON (14, FMDX, 10, FMDY, N_("&OK"), B_ENTER, NULL),
929 /* 3 - OFFSET */
930 QUICK_CHECKBOX (42, FMDX, 8, FMDY, N_("&Stable Symlinks"), &ctx->stable_symlinks),
931 /* 4 - OFFSET */
932 QUICK_CHECKBOX (31, FMDX, 7, FMDY, N_("Di&ve into subdir if exists"),
933 &ctx->dive_into_subdirs),
934 /* 5 - OFFSET */
935 QUICK_CHECKBOX (3, FMDX, 8, FMDY, N_("Preserve &attributes"), &ctx->op_preserve),
936 /* 6 - OFFSET */
937 QUICK_CHECKBOX (3, FMDX, 7, FMDY, N_("Follow &links"), &ctx->follow_links),
938 /* 7 - OFFSET */
939 QUICK_INPUT (3, FMDX, 6, FMDY, "", 58, 0, "input2", &dest_dir),
940 /* 8 - OFFSET */
941 QUICK_LABEL (3, FMDX, 5, FMDY, N_("to:")),
942 /* 9 - OFFSET */
943 QUICK_CHECKBOX (37, FMDX, 4, FMDY, N_("&Using shell patterns"), &source_easy_patterns),
944 /* 10 - OFFSET */
945 QUICK_INPUT (3, FMDX, 3, FMDY, easy_patterns ? "*" : "^(.*)$", 58, 0, "input-def",
946 &source_mask),
947 /* 11 - OFFSET */
948 QUICK_LABEL (3, FMDX, 2, FMDY, fmd_buf),
949 QUICK_END
952 g_return_val_if_fail (ctx != NULL, NULL);
954 #ifdef ENABLE_NLS
955 /* buttons */
956 for (i = 0; i <= 2 - OFFSET; i++)
957 fmd_widgets[i].u.button.text = _(fmd_widgets[i].u.button.text);
959 /* checkboxes */
960 for (i = 3 - OFFSET; i <= 9 - OFFSET; i++)
961 if (i != 7 - OFFSET)
962 fmd_widgets[i].u.checkbox.text = _(fmd_widgets[i].u.checkbox.text);
963 #endif /* !ENABLE_NLS */
965 fmd_xlen = max (FMDX, (size_t) COLS * 2 / 3);
967 len = str_term_width1 (fmd_widgets[6 - OFFSET].u.checkbox.text)
968 + str_term_width1 (fmd_widgets[4 - OFFSET].u.checkbox.text) + 15;
969 fmd_xlen = max (fmd_xlen, len);
971 len = str_term_width1 (fmd_widgets[5 - OFFSET].u.checkbox.text)
972 + str_term_width1 (fmd_widgets[3 - OFFSET].u.checkbox.text) + 15;
973 fmd_xlen = max (fmd_xlen, len);
975 /* buttons */
976 b2_len = str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text) + 6 + gap; /* OK */
977 #ifdef WITH_BACKGROUND
978 b1_len = str_term_width1 (fmd_widgets[1].u.button.text) + 4 + gap; /* Background */
979 #endif
980 b0_len = str_term_width1 (fmd_widgets[0].u.button.text) + 4; /* Cancel */
981 len = b0_len + b1_len + b2_len;
982 fmd_xlen = min (max (fmd_xlen, len + 6), (size_t) COLS);
984 if (only_one)
986 int flen;
988 flen = str_term_width1 (format);
989 i = fmd_xlen - flen - 4; /* FIXME */
990 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc ((const char *) text, i));
992 else
994 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
995 fmd_xlen = max (fmd_xlen, (size_t) str_term_width1 (fmd_buf) + 6);
998 for (i = sizeof (fmd_widgets) / sizeof (fmd_widgets[0]); i > 0;)
999 fmd_widgets[--i].x_divisions = fmd_xlen;
1001 i = (fmd_xlen - len) / 2;
1002 /* OK button */
1003 fmd_widgets[2 - OFFSET].relative_x = i;
1004 i += b2_len;
1005 #ifdef WITH_BACKGROUND
1006 /* Background button */
1007 fmd_widgets[1].relative_x = i;
1008 i += b1_len;
1009 #endif
1010 /* Cancel button */
1011 fmd_widgets[0].relative_x = i;
1013 #define chkbox_xpos(i) \
1014 fmd_widgets [i].relative_x = fmd_xlen - str_term_width1 (fmd_widgets [i].u.checkbox.text) - 6
1015 chkbox_xpos (3 - OFFSET);
1016 chkbox_xpos (4 - OFFSET);
1017 chkbox_xpos (9 - OFFSET);
1018 #undef chkbox_xpos
1020 /* inputs */
1021 fmd_widgets[7 - OFFSET].u.input.len = fmd_widgets[10 - OFFSET].u.input.len = fmd_xlen - 6;
1023 /* unselect checkbox if target filesystem don't support attributes */
1024 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1026 /* filter out a possible password from def_text */
1027 tmp = strip_password (g_strdup (def_text), 1);
1028 if (source_easy_patterns)
1029 def_text_secure = strutils_glob_escape (tmp);
1030 else
1031 def_text_secure = strutils_regex_escape (tmp);
1032 g_free (tmp);
1034 /* destination */
1035 fmd_widgets[7 - OFFSET].u.input.text = def_text_secure;
1037 ctx->stable_symlinks = FALSE;
1038 *do_background = FALSE;
1041 struct stat buf;
1043 QuickDialog Quick_input = {
1044 fmd_xlen, FMDY, -1, -1, op_names[operation],
1045 "[Mask Copy/Rename]", fmd_widgets, TRUE
1048 ask_file_mask:
1049 val = quick_dialog_skip (&Quick_input, 4);
1051 if (val == B_CANCEL)
1053 g_free (def_text_secure);
1054 return NULL;
1057 if (ctx->follow_links)
1058 ctx->stat_func = mc_stat;
1059 else
1060 ctx->stat_func = mc_lstat;
1062 if (ctx->op_preserve)
1064 ctx->preserve = TRUE;
1065 ctx->umask_kill = 0777777;
1066 ctx->preserve_uidgid = (geteuid () == 0);
1068 else
1070 int i2;
1071 ctx->preserve = ctx->preserve_uidgid = FALSE;
1072 i2 = umask (0);
1073 umask (i2);
1074 ctx->umask_kill = i2 ^ 0777777;
1077 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1079 g_free (def_text_secure);
1080 g_free (source_mask);
1081 return dest_dir;
1084 ctx->search_handle = mc_search_new (source_mask, -1);
1086 if (ctx->search_handle == NULL)
1088 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"), source_mask);
1089 g_free (dest_dir);
1090 g_free (source_mask);
1091 goto ask_file_mask;
1094 g_free (def_text_secure);
1095 g_free (source_mask);
1097 ctx->search_handle->is_case_sensitive = TRUE;
1098 if (source_easy_patterns)
1099 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1100 else
1101 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1103 tmp = dest_dir;
1104 dest_dir = tilde_expand (tmp);
1105 g_free (tmp);
1107 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1108 if (ctx->dest_mask == NULL)
1109 ctx->dest_mask = dest_dir;
1110 else
1111 ctx->dest_mask++;
1112 orig_mask = ctx->dest_mask;
1113 if (!*ctx->dest_mask
1114 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1115 && (!only_one
1116 || (!mc_stat (dest_dir, &buf) && S_ISDIR (buf.st_mode))))
1117 || (ctx->dive_into_subdirs
1118 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1119 || (only_one && !mc_stat (dest_dir, &buf) && S_ISDIR (buf.st_mode)))))
1120 ctx->dest_mask = g_strdup ("\\0");
1121 else
1123 ctx->dest_mask = g_strdup (ctx->dest_mask);
1124 *orig_mask = '\0';
1126 if (!*dest_dir)
1128 g_free (dest_dir);
1129 dest_dir = g_strdup ("./");
1131 if (val == B_USER)
1132 *do_background = TRUE;
1135 return dest_dir;