Merge branch '1858_segfault_in_search'
[midnight-commander.git] / src / filegui.c
blob6d7fa2e329e931975b6fbf472b2a03c87f6972d0
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 "global.h"
81 #include "../src/tty/key.h" /* tty_get_event */
83 #include "../src/search/search.h"
85 #include "setup.h" /* verbose */
86 #include "dialog.h" /* do_refresh() */
87 #include "widget.h" /* WLabel */
88 #include "main-widgets.h"
89 #include "main.h" /* the_hint */
90 #include "wtools.h" /* QuickDialog */
91 #include "panel.h" /* current_panel */
92 #include "fileopctx.h" /* FILE_CONT */
93 #include "filegui.h"
94 #include "util.h" /* strip_password() */
95 #include "strutil.h"
96 #include "../src/strescape.h"
98 /* }}} */
99 typedef enum {
100 MSDOS_SUPER_MAGIC = 0x4d44,
101 NTFS_SB_MAGIC = 0x5346544e,
102 NTFS_3G_MAGIC = 0x65735546,
103 PROC_SUPER_MAGIC = 0x9fa0,
104 SMB_SUPER_MAGIC = 0x517B,
105 NCP_SUPER_MAGIC = 0x564c,
106 USBDEVICE_SUPER_MAGIC = 0x9fa2
107 } filegui_nonattrs_fs_t;
109 /* Hack: the vfs code should not rely on this */
110 #define WITH_FULL_PATHS 1
112 /* This structure describes the UI and internal data required by a file
113 * operation context.
115 typedef struct {
116 /* ETA and bps */
118 int showing_eta;
119 int showing_bps;
120 int eta_extra;
122 /* Dialog and widgets for the operation progress window */
124 Dlg_head *op_dlg;
126 WLabel *file_label[2];
127 WLabel *file_string[2];
128 WLabel *progress_label[3];
129 WGauge *progress_gauge[3];
130 WLabel *eta_label;
131 WLabel *bps_label;
132 WLabel *stalled_label;
134 /* Query replace dialog */
136 Dlg_head *replace_dlg;
137 const char *replace_filename;
138 int replace_result;
139 struct stat *s_stat, *d_stat;
140 } FileOpContextUI;
143 /* Used to save the hint line */
144 static int last_hint_line;
146 /* File operate window sizes */
147 #define WX 62
148 #define WY 10
149 #define BY 10
150 #define WX_ETA_EXTRA 12
152 #define FCOPY_GAUGE_X 14
153 #define FCOPY_LABEL_X 5
155 /* Used for button result values */
156 enum {
157 REPLACE_YES = B_USER,
158 REPLACE_NO,
159 REPLACE_APPEND,
160 REPLACE_ALWAYS,
161 REPLACE_UPDATE,
162 REPLACE_NEVER,
163 REPLACE_ABORT,
164 REPLACE_SIZE,
165 REPLACE_REGET
168 static int
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 0;
177 if (STATFS(fs_path, &stfs)!=0)
178 return 1;
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 0;
191 break;
193 # elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) \
194 || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
195 if (!strcmp(stfs.f_fstypename, "msdos")
196 || !strcmp(stfs.f_fstypename, "msdosfs")
197 || !strcmp(stfs.f_fstypename, "ntfs")
198 || !strcmp(stfs.f_fstypename, "procfs")
199 || !strcmp(stfs.f_fstypename, "smbfs")
200 || strstr(stfs.f_fstypename, "fusefs"))
201 return 0;
202 # elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
203 if (!strcmp(stfs.f_basetype, "pcfs")
204 || !strcmp(stfs.f_basetype, "ntfs")
205 || !strcmp(stfs.f_basetype, "proc")
206 || !strcmp(stfs.f_basetype, "smbfs")
207 || !strcmp(stfs.f_basetype, "fuse"))
208 return 0;
209 # endif
210 #endif /* STATFS */
212 return 1;
215 static FileProgressStatus
216 check_progress_buttons (FileOpContext *ctx)
218 int c;
219 Gpm_Event event;
220 FileOpContextUI *ui;
222 if (ctx->ui == NULL)
223 return FILE_CONT;
225 ui = ctx->ui;
227 event.x = -1; /* Don't show the GPM cursor */
228 c = tty_get_event (&event, FALSE, FALSE);
229 if (c == EV_NONE)
230 return FILE_CONT;
232 /* Reinitialize to avoid old values after events other than
233 selecting a button */
234 ui->op_dlg->ret_value = FILE_CONT;
236 dlg_process_event (ui->op_dlg, c, &event);
237 switch (ui->op_dlg->ret_value) {
238 case FILE_SKIP:
239 return FILE_SKIP;
240 break;
241 case B_CANCEL:
242 case FILE_ABORT:
243 return FILE_ABORT;
244 break;
245 default:
246 return FILE_CONT;
250 /* {{{ File progress display routines */
252 void
253 file_op_context_create_ui_without_init (FileOpContext *ctx, int with_eta)
255 FileOpContextUI *ui;
256 int x_size;
257 int minus;
258 int eta_offset;
259 const char *sixty;
260 const char *fifteen;
262 g_return_if_fail (ctx != NULL);
263 g_return_if_fail (ctx->ui == NULL);
265 ui = g_new0 (FileOpContextUI, 1);
266 ctx->ui = ui;
268 minus = verbose ? 0 : 3;
269 eta_offset = with_eta ? (WX_ETA_EXTRA) / 2 : 0;
271 sixty = "";
272 fifteen = "";
274 ctx->recursive_result = 0;
276 ui->replace_result = 0;
277 ui->showing_eta = with_eta;
278 ui->showing_bps = with_eta;
279 ui->eta_extra = with_eta ? WX_ETA_EXTRA : 0;
280 x_size = (WX + 4) + ui->eta_extra;
282 ui->op_dlg =
283 create_dlg (0, 0, WY - minus + 4, x_size, dialog_colors, NULL,
284 NULL, op_names[ctx->operation],
285 DLG_CENTER | DLG_REVERSE);
287 last_hint_line = the_hint->widget.y;
288 if ((ui->op_dlg->y + ui->op_dlg->lines) > last_hint_line)
289 the_hint->widget.y = ui->op_dlg->y + ui->op_dlg->lines + 1;
291 add_widget (ui->op_dlg,
292 button_new (BY - minus, WX - 19 + eta_offset, FILE_ABORT,
293 NORMAL_BUTTON, _("&Abort"), 0));
294 add_widget (ui->op_dlg,
295 button_new (BY - minus, 14 + eta_offset, FILE_SKIP,
296 NORMAL_BUTTON, _("&Skip"), 0));
298 add_widget (ui->op_dlg, ui->progress_gauge[2] =
299 gauge_new (7, FCOPY_GAUGE_X, 0, 100, 0));
300 add_widget (ui->op_dlg, ui->progress_label[2] =
301 label_new (7, FCOPY_LABEL_X, fifteen));
302 add_widget (ui->op_dlg, ui->bps_label = label_new (7, WX, ""));
304 add_widget (ui->op_dlg, ui->progress_gauge[1] =
305 gauge_new (8, FCOPY_GAUGE_X, 0, 100, 0));
306 add_widget (ui->op_dlg, ui->progress_label[1] =
307 label_new (8, FCOPY_LABEL_X, fifteen));
308 add_widget (ui->op_dlg, ui->stalled_label = label_new (8, WX, ""));
310 add_widget (ui->op_dlg, ui->progress_gauge[0] =
311 gauge_new (6, FCOPY_GAUGE_X, 0, 100, 0));
312 add_widget (ui->op_dlg, ui->progress_label[0] =
313 label_new (6, FCOPY_LABEL_X, fifteen));
314 add_widget (ui->op_dlg, ui->eta_label = label_new (6, WX, ""));
316 add_widget (ui->op_dlg, ui->file_string[1] =
317 label_new (4, FCOPY_GAUGE_X, sixty));
318 add_widget (ui->op_dlg, ui->file_label[1] =
319 label_new (4, FCOPY_LABEL_X, fifteen));
320 add_widget (ui->op_dlg, ui->file_string[0] =
321 label_new (3, FCOPY_GAUGE_X, sixty));
322 add_widget (ui->op_dlg, ui->file_label[0] =
323 label_new (3, FCOPY_LABEL_X, fifteen));
326 void
327 file_op_context_create_ui (FileOpContext *ctx, int with_eta)
329 FileOpContextUI *ui;
331 g_return_if_fail (ctx != NULL);
332 g_return_if_fail (ctx->ui == NULL);
334 file_op_context_create_ui_without_init(ctx, with_eta);
335 ui = ctx->ui;
337 /* We will manage the dialog without any help, that's why
338 we have to call init_dlg */
339 init_dlg (ui->op_dlg);
340 ui->op_dlg->running = 1;
343 void
344 file_op_context_destroy_ui (FileOpContext *ctx)
346 FileOpContextUI *ui;
348 g_return_if_fail (ctx != NULL);
350 if (ctx->ui) {
351 ui = ctx->ui;
353 dlg_run_done (ui->op_dlg);
354 destroy_dlg (ui->op_dlg);
355 g_free (ui);
358 the_hint->widget.y = last_hint_line;
360 ctx->ui = NULL;
363 static FileProgressStatus
364 show_no_bar (FileOpContext *ctx, int n)
366 FileOpContextUI *ui;
368 if (ctx->ui == NULL)
369 return FILE_CONT;
371 ui = ctx->ui;
373 if (n >= 0) {
374 label_set_text (ui->progress_label[n], "");
375 gauge_show (ui->progress_gauge[n], 0);
377 return check_progress_buttons (ctx);
380 static FileProgressStatus
381 show_bar (FileOpContext *ctx, int n, double done, double total)
383 FileOpContextUI *ui;
385 if (ctx->ui == NULL)
386 return FILE_CONT;
388 ui = ctx->ui;
391 * Gauge needs integers, so give it with integers between 0 and 1023.
392 * This precision should be quite reasonable.
394 gauge_set_value (ui->progress_gauge[n], 1024,
395 (int) (1024 * done / total));
396 gauge_show (ui->progress_gauge[n], 1);
397 return check_progress_buttons (ctx);
400 static void
401 file_eta_show (FileOpContext *ctx)
403 int eta_hours, eta_mins, eta_s;
404 char eta_buffer[BUF_TINY];
405 FileOpContextUI *ui;
407 if (ctx->ui == NULL)
408 return;
410 ui = ctx->ui;
412 if (!ui->showing_eta)
413 return;
415 if (ctx->eta_secs > 0.5) {
416 eta_hours = ctx->eta_secs / (60 * 60);
417 eta_mins = (ctx->eta_secs - (eta_hours * 60 * 60)) / 60;
418 eta_s = ctx->eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
419 g_snprintf (eta_buffer, sizeof (eta_buffer), _("ETA %d:%02d.%02d"),
420 eta_hours, eta_mins, eta_s);
421 } else
422 *eta_buffer = 0;
424 label_set_text (ui->eta_label, eta_buffer);
427 static void
428 file_bps_show (FileOpContext *ctx)
430 char bps_buffer[BUF_TINY];
431 FileOpContextUI *ui;
433 if (ctx->ui == NULL)
434 return;
436 ui = ctx->ui;
438 if (!ui->showing_bps)
439 return;
441 if (ctx->bps > 1024 * 1024) {
442 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%.2f MB/s"),
443 ctx->bps / (1024 * 1024.0));
444 } else if (ctx->bps > 1024) {
445 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%.2f KB/s"),
446 ctx->bps / 1024.0);
447 } else if (ctx->bps > 1) {
448 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%ld B/s"),
449 ctx->bps);
450 } else
451 *bps_buffer = 0;
453 label_set_text (ui->bps_label, bps_buffer);
456 FileProgressStatus
457 file_progress_show (FileOpContext *ctx, off_t done, off_t total)
459 FileOpContextUI *ui;
461 g_return_val_if_fail (ctx != NULL, FILE_CONT);
463 if (ctx->ui == NULL)
464 return FILE_CONT;
466 ui = ctx->ui;
468 if (!verbose)
469 return check_progress_buttons (ctx);
470 if (total > 0) {
471 label_set_text (ui->progress_label[0], _("File"));
472 file_eta_show (ctx);
473 file_bps_show (ctx);
474 return show_bar (ctx, 0, done, total);
475 } else
476 return show_no_bar (ctx, 0);
479 FileProgressStatus
480 file_progress_show_count (FileOpContext *ctx, off_t done, off_t total)
482 FileOpContextUI *ui;
484 g_return_val_if_fail (ctx != NULL, FILE_CONT);
486 if (ctx->ui == NULL)
487 return FILE_CONT;
489 ui = ctx->ui;
491 if (!verbose)
492 return check_progress_buttons (ctx);
493 if (total > 0) {
494 label_set_text (ui->progress_label[1], _("Count"));
495 return show_bar (ctx, 1, done, total);
496 } else
497 return show_no_bar (ctx, 1);
500 FileProgressStatus
501 file_progress_show_bytes (FileOpContext *ctx, double done, double total)
503 FileOpContextUI *ui;
505 g_return_val_if_fail (ctx != NULL, FILE_CONT);
507 if (ctx->ui == NULL)
508 return FILE_CONT;
510 ui = ctx->ui;
512 if (!verbose)
513 return check_progress_buttons (ctx);
514 if (total > 0) {
515 label_set_text (ui->progress_label[2], _("Bytes"));
516 return show_bar (ctx, 2, done, total);
517 } else
518 return show_no_bar (ctx, 2);
521 /* }}} */
523 #define truncFileString(ui, s) str_trunc (s, ui->eta_extra + 47)
524 #define truncFileStringSecure(ui, s) path_trunc (s, ui->eta_extra + 47)
526 FileProgressStatus
527 file_progress_show_source (FileOpContext *ctx, const char *s)
529 FileOpContextUI *ui;
531 g_return_val_if_fail (ctx != NULL, FILE_CONT);
533 if (ctx->ui == NULL)
534 return FILE_CONT;
536 ui = ctx->ui;
538 if (s != NULL) {
539 #ifdef WITH_FULL_PATHS
540 int i = strlen (current_panel->cwd);
542 /* We remove the full path we have added before */
543 if (!strncmp (s, current_panel->cwd, i)) {
544 if (s[i] == PATH_SEP)
545 s += i + 1;
547 #endif /* WITH_FULL_PATHS */
549 label_set_text (ui->file_label[0], _("Source"));
550 label_set_text (ui->file_string[0], truncFileString (ui, s));
551 return check_progress_buttons (ctx);
552 } else {
553 label_set_text (ui->file_label[0], "");
554 label_set_text (ui->file_string[0], "");
555 return check_progress_buttons (ctx);
559 FileProgressStatus
560 file_progress_show_target (FileOpContext *ctx, const char *s)
562 FileOpContextUI *ui;
564 g_return_val_if_fail (ctx != NULL, FILE_CONT);
566 if (ctx->ui == NULL)
567 return FILE_CONT;
569 ui = ctx->ui;
571 if (s != NULL) {
572 label_set_text (ui->file_label[1], _("Target"));
573 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
574 return check_progress_buttons (ctx);
575 } else {
576 label_set_text (ui->file_label[1], "");
577 label_set_text (ui->file_string[1], "");
578 return check_progress_buttons (ctx);
582 FileProgressStatus
583 file_progress_show_deleting (FileOpContext *ctx, const char *s)
585 FileOpContextUI *ui;
587 g_return_val_if_fail (ctx != NULL, FILE_CONT);
589 if (ctx->ui == NULL)
590 return FILE_CONT;
592 ui = ctx->ui;
594 label_set_text (ui->file_label[0], _("Deleting"));
595 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
596 return check_progress_buttons (ctx);
600 * FIXME: probably it is better to replace this with quick dialog machinery,
601 * but actually I'm not familiar with it and have not much time :(
602 * alex
606 static int
607 overwrite_query_dialog (FileOpContext *ctx, enum OperationMode mode)
609 #define ADD_RD_BUTTON(i)\
610 add_widget (ui->replace_dlg,\
611 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value,\
612 NORMAL_BUTTON, rd_widgets [i].text, 0))
614 #define ADD_RD_LABEL(i, p1, p2)\
615 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2);\
616 add_widget (ui->replace_dlg,\
617 label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer))
619 /* dialog sizes */
620 const int rd_ylen = 17;
621 int rd_xlen = 60;
623 struct {
624 const char *text;
625 int ypos, xpos;
626 int value; /* 0 for labels */
627 } rd_widgets[] = {
628 /* 0 */ { N_("Target file already exists!"), 3, 4, 0 },
629 /* 1 */ { "%s", 4, 4, 0 },
630 #if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || (defined _LARGE_FILES && _LARGE_FILES)
631 /* 2 */ { N_("Source date: %s, size %llu"), 6, 4, 0 },
632 /* 3 */ { N_("Target date: %s, size %llu"), 7, 4, 0 },
633 #else
634 /* 2 */ { N_("Source date: %s, size %u"), 6, 4, 0 },
635 /* 3 */ { N_("Target date: %s, size %u"), 7, 4, 0 },
636 #endif
637 /* 4 */ { N_("&Abort"), 14, 25, REPLACE_ABORT },
638 /* 5 */ { N_("If &size differs"), 12, 28, REPLACE_SIZE },
639 /* 6 */ { N_("Non&e"), 11, 47, REPLACE_NEVER },
640 /* 7 */ { N_("&Update"), 11, 36, REPLACE_UPDATE },
641 /* 8 */ { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
642 /* 9 */ { N_("Overwrite all targets?"), 11, 4, 0 },
643 /* 10 */ { N_("&Reget"), 10, 28, REPLACE_REGET },
644 /* 11 */ { N_("A&ppend"), 9, 45, REPLACE_APPEND },
645 /* 12 */ { N_("&No"), 9, 37, REPLACE_NO },
646 /* 13 */ { N_("&Yes"), 9, 28, REPLACE_YES },
647 /* 14 */ { N_("Overwrite this target?"), 9, 4, 0 }
650 const int num = sizeof (rd_widgets) / sizeof (rd_widgets[0]);
651 int *widgets_len;
653 FileOpContextUI *ui = ctx->ui;
655 char buffer[BUF_SMALL];
656 const char *title;
657 const char *stripped_name = strip_home_and_password (ui->replace_filename);
658 int stripped_name_len;
660 int result;
662 widgets_len = g_new0 (int, num);
664 if (mode == Foreground)
665 title = _(" File exists ");
666 else
667 title = _(" Background process: File exists ");
669 stripped_name_len = str_term_width1 (stripped_name);
672 int i, l1, l2, l, row;
674 for (i = 0; i < num; i++) {
675 #ifdef ENABLE_NLS
676 if (i != 1) /* skip filename */
677 rd_widgets[i].text = _(rd_widgets[i].text);
678 #endif /* ENABLE_NLS */
679 widgets_len [i] = str_term_width1 (rd_widgets[i].text);
683 * longest of "Overwrite..." labels
684 * (assume "Target date..." are short enough)
686 l1 = max (widgets_len[9], widgets_len[14]);
688 /* longest of button rows */
689 i = num;
690 for (row = l = l2 = 0; i--;)
691 if (rd_widgets[i].value != 0) {
692 if (row != rd_widgets[i].ypos) {
693 row = rd_widgets[i].ypos;
694 l2 = max (l2, l);
695 l = 0;
697 l += widgets_len[i] + 4;
700 l2 = max (l2, l); /* last row */
701 rd_xlen = max (rd_xlen, l1 + l2 + 8);
702 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
703 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
705 /* Now place widgets */
706 l1 += 5; /* start of first button in the row */
707 i = num;
708 for (l = l1, row = 0; --i > 1;)
709 if (rd_widgets[i].value != 0) {
710 if (row != rd_widgets[i].ypos) {
711 row = rd_widgets[i].ypos;
712 l = l1;
714 rd_widgets[i].xpos = l;
715 l += widgets_len[i] + 4;
718 /* Abort button is centered */
719 rd_widgets[4].xpos = (rd_xlen - widgets_len[4] - 3) / 2;
722 /* FIXME - missing help node */
723 ui->replace_dlg =
724 create_dlg (0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, "[Replace]",
725 title, DLG_CENTER | DLG_REVERSE);
727 /* prompt -- centered */
728 add_widget (ui->replace_dlg,
729 label_new (rd_widgets [0].ypos,
730 (rd_xlen - widgets_len [0]) / 2,
731 rd_widgets [0].text));
732 /* file name -- centered */
733 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
734 stripped_name_len = str_term_width1 (stripped_name);
735 add_widget (ui->replace_dlg,
736 label_new (rd_widgets [1].ypos,
737 (rd_xlen - stripped_name_len) / 2,
738 stripped_name));
740 /* source date */
741 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime),
742 (off_t) ui->s_stat->st_size);
743 /* destination date */
744 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime),
745 (off_t) ui->d_stat->st_size);
747 ADD_RD_BUTTON (4); /* Abort */
748 ADD_RD_BUTTON (5); /* If size differs */
749 ADD_RD_BUTTON (6); /* None */
750 ADD_RD_BUTTON (7); /* Update */
751 ADD_RD_BUTTON (8); /* All" */
752 ADD_RD_LABEL (9, 0, 0); /* Overwrite all targets? */
754 /* "this target..." widgets */
755 if (!S_ISDIR (ui->d_stat->st_mode)) {
756 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
757 && (ui->s_stat->st_size > ui->d_stat->st_size))
758 ADD_RD_BUTTON (10); /* Reget */
760 ADD_RD_BUTTON (11); /* Append */
762 ADD_RD_BUTTON (12); /* No */
763 ADD_RD_BUTTON (13); /* Yes */
764 ADD_RD_LABEL (14, 0, 0); /* Overwrite this target? */
766 result = run_dlg (ui->replace_dlg);
767 destroy_dlg (ui->replace_dlg);
769 g_free (widgets_len);
771 return result;
772 #undef ADD_RD_LABEL
773 #undef ADD_RD_BUTTON
776 void
777 file_progress_set_stalled_label (FileOpContext *ctx, const char *stalled_msg)
779 FileOpContextUI *ui;
781 g_return_if_fail (ctx != NULL);
782 g_return_if_fail (ctx->ui != NULL);
784 ui = ctx->ui;
785 label_set_text (ui->stalled_label, stalled_msg);
788 FileProgressStatus
789 file_progress_real_query_replace (FileOpContext *ctx,
790 enum OperationMode mode, const char *destname,
791 struct stat *_s_stat,
792 struct stat *_d_stat)
794 FileOpContextUI *ui;
796 g_return_val_if_fail (ctx != NULL, FILE_CONT);
797 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
799 ui = ctx->ui;
801 if (ui->replace_result < REPLACE_ALWAYS) {
802 ui->replace_filename = destname;
803 ui->s_stat = _s_stat;
804 ui->d_stat = _d_stat;
805 ui->replace_result = overwrite_query_dialog (ctx, mode);
806 if (ui->replace_result == B_CANCEL)
807 ui->replace_result = REPLACE_ABORT;
810 switch (ui->replace_result) {
811 case REPLACE_UPDATE:
812 do_refresh ();
813 if (_s_stat->st_mtime > _d_stat->st_mtime)
814 return FILE_CONT;
815 else
816 return FILE_SKIP;
818 case REPLACE_SIZE:
819 do_refresh ();
820 if (_s_stat->st_size == _d_stat->st_size)
821 return FILE_SKIP;
822 else
823 return FILE_CONT;
825 case REPLACE_REGET:
826 /* Careful: we fall through and set do_append */
827 ctx->do_reget = _d_stat->st_size;
829 case REPLACE_APPEND:
830 ctx->do_append = 1;
832 case REPLACE_YES:
833 case REPLACE_ALWAYS:
834 do_refresh ();
835 return FILE_CONT;
836 case REPLACE_NO:
837 case REPLACE_NEVER:
838 do_refresh ();
839 return FILE_SKIP;
840 case REPLACE_ABORT:
841 default:
842 return FILE_ABORT;
846 static gboolean
847 is_wildcarded (char *p)
849 for (; *p; p++) {
850 if (*p == '*')
851 return TRUE;
852 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
853 return TRUE;
855 return FALSE;
858 char *
859 file_mask_dialog (FileOpContext *ctx, FileOperation operation,
860 gboolean only_one,
861 const char *format, const void *text,
862 const char *def_text, gboolean *do_background)
864 const size_t FMDY = 13;
865 const size_t FMDX = 68;
866 size_t fmd_xlen;
868 /* buttons */
869 const size_t gap = 1;
870 size_t b0_len, b2_len;
871 size_t b1_len = 0;
873 int source_easy_patterns = easy_patterns;
874 size_t i, len;
875 char fmd_buf [BUF_MEDIUM];
876 char *source_mask, *orig_mask, *dest_dir, *tmp;
877 char *def_text_secure;
878 int val;
880 #ifdef ENABLE_NLS
881 static gboolean i18n = FALSE;
882 #endif /* !ENABLE_NLS */
884 QuickWidget fmd_widgets[] =
886 /* 0 */ QUICK_BUTTON (42, 64, 10, FMDY, N_("&Cancel"), B_CANCEL, NULL),
887 #ifdef WITH_BACKGROUND
888 /* 1 */ QUICK_BUTTON (25, 64, 10, FMDY, N_("&Background"), B_USER, NULL),
889 #define OFFSET 0
890 #else
891 #define OFFSET 1
892 #endif /* WITH_BACKGROUND */
893 /* 2 - OFFSET */
894 QUICK_BUTTON (14, FMDX, 10, FMDY, N_("&OK"), B_ENTER, NULL),
895 /* 3 - OFFSET */
896 QUICK_CHECKBOX (42, FMDX, 8, FMDY, N_("&Stable Symlinks"), &ctx->stable_symlinks),
897 /* 4 - OFFSET */
898 QUICK_CHECKBOX (31, FMDX, 7, FMDY, N_("di&Ve into subdir if exists"), &ctx->dive_into_subdirs),
899 /* 5 - OFFSET */
900 QUICK_CHECKBOX (3, FMDX, 8, FMDY, N_("preserve &Attributes"), &ctx->op_preserve),
901 /* 6 - OFFSET */
902 QUICK_CHECKBOX (3, FMDX, 7, FMDY, N_("follow &Links"), &ctx->follow_links),
903 /* 7 - OFFSET */
904 QUICK_INPUT (3, FMDX, 6, FMDY, "", 58, 0, "input2", &dest_dir),
905 /* 8 - OFFSET */
906 QUICK_LABEL (3, FMDX, 5, FMDY, N_("to:")),
907 /* 9 - OFFSET */
908 QUICK_CHECKBOX (37, FMDX, 4, FMDY, N_("&Using shell patterns"), &source_easy_patterns),
909 /* 10 - OFFSET */
910 QUICK_INPUT (3, FMDX, 3, FMDY, easy_patterns ? "*" : "^\\(.*\\)$", 58, 0, "input-def", &source_mask),
911 /* 11 - OFFSET */
912 QUICK_LABEL (3, FMDX, 2, FMDY, fmd_buf),
913 QUICK_END
916 g_return_val_if_fail (ctx != NULL, NULL);
918 #ifdef ENABLE_NLS
919 if (!i18n) {
920 for (i = sizeof (op_names) / sizeof (op_names[0]); i--;)
921 op_names[i] = Q_(op_names[i]);
923 i18n = TRUE;
926 /* buttons */
927 for (i = 0; i <= 2 - OFFSET; i++)
928 fmd_widgets[i].u.button.text = _(fmd_widgets[i].u.button.text);
930 /* checkboxes */
931 for (i = 3 - OFFSET; i <= 9 - OFFSET; i++)
932 if (i != 7 - OFFSET)
933 fmd_widgets[i].u.checkbox.text = _(fmd_widgets[i].u.checkbox.text);
934 #endif /* !ENABLE_NLS */
936 fmd_xlen = max (FMDX, (size_t) COLS * 2/3);
938 len = str_term_width1 (fmd_widgets[6 - OFFSET].u.checkbox.text)
939 + str_term_width1 (fmd_widgets[4 - OFFSET].u.checkbox.text) + 15;
940 fmd_xlen = max (fmd_xlen, len);
942 len = str_term_width1 (fmd_widgets[5 - OFFSET].u.checkbox.text)
943 + str_term_width1 (fmd_widgets[3 - OFFSET].u.checkbox.text) + 15;
944 fmd_xlen = max (fmd_xlen, len);
946 /* buttons */
947 b2_len = str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text) + 6 + gap; /* OK */
948 #ifdef WITH_BACKGROUND
949 b1_len = str_term_width1 (fmd_widgets[1].u.button.text) + 4 + gap; /* Background */
950 #endif
951 b0_len = str_term_width1 (fmd_widgets[0].u.button.text) + 4; /* Cancel */
952 len = b0_len + b1_len + b2_len;
953 fmd_xlen = min (max (fmd_xlen, len + 6), (size_t) COLS);
955 if (only_one) {
956 int flen;
958 flen = str_term_width1 (format);
959 i = fmd_xlen - flen - 4; /* FIXME */
960 g_snprintf (fmd_buf, sizeof (fmd_buf),
961 format, str_trunc ((const char *) text, i));
962 } else {
963 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
964 fmd_xlen = max (fmd_xlen, (size_t) str_term_width1 (fmd_buf) + 6);
967 for (i = sizeof (fmd_widgets) / sizeof (fmd_widgets[0]); i > 0; )
968 fmd_widgets[--i].x_divisions = fmd_xlen;
970 i = (fmd_xlen - len)/2;
971 /* OK button */
972 fmd_widgets[2 - OFFSET].relative_x = i;
973 i += b2_len;
974 #ifdef WITH_BACKGROUND
975 /* Background button */
976 fmd_widgets[1].relative_x = i;
977 i += b1_len;
978 #endif
979 /* Cancel button */
980 fmd_widgets[0].relative_x = i;
982 #define chkbox_xpos(i) \
983 fmd_widgets [i].relative_x = fmd_xlen - str_term_width1 (fmd_widgets [i].u.checkbox.text) - 6
984 chkbox_xpos (3 - OFFSET);
985 chkbox_xpos (4 - OFFSET);
986 chkbox_xpos (9 - OFFSET);
987 #undef chkbox_xpos
989 /* inputs */
990 fmd_widgets[ 7 - OFFSET].u.input.len =
991 fmd_widgets[10 - OFFSET].u.input.len = fmd_xlen - 6;
993 /* unselect checkbox if target filesystem don't support attributes */
994 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
996 /* filter out a possible password from def_text */
997 tmp = strip_password (g_strdup (def_text), 1);
998 if (source_easy_patterns)
999 def_text_secure = strutils_glob_escape (tmp);
1000 else
1001 def_text_secure = strutils_regex_escape (tmp);
1002 g_free (tmp);
1004 /* destination */
1005 fmd_widgets[7 - OFFSET].u.input.text = def_text_secure;
1007 ctx->stable_symlinks = 0;
1008 *do_background = FALSE;
1011 struct stat buf;
1013 QuickDialog Quick_input =
1015 fmd_xlen, FMDY, -1, -1, op_names [operation],
1016 "[Mask Copy/Rename]", fmd_widgets, TRUE
1019 ask_file_mask:
1020 val = quick_dialog_skip (&Quick_input, 4);
1022 if (val == B_CANCEL) {
1023 g_free (def_text_secure);
1024 return NULL;
1027 if (ctx->follow_links)
1028 ctx->stat_func = mc_stat;
1029 else
1030 ctx->stat_func = mc_lstat;
1032 if (ctx->op_preserve) {
1033 ctx->preserve = 1;
1034 ctx->umask_kill = 0777777;
1035 ctx->preserve_uidgid = (geteuid () == 0) ? 1 : 0;
1036 } else {
1037 int i2;
1038 ctx->preserve = ctx->preserve_uidgid = 0;
1039 i2 = umask (0);
1040 umask (i2);
1041 ctx->umask_kill = i2 ^ 0777777;
1044 if (!dest_dir || !*dest_dir) {
1045 g_free (def_text_secure);
1046 g_free (source_mask);
1047 return dest_dir;
1050 ctx->search_handle = mc_search_new(source_mask,-1);
1052 if (ctx->search_handle == NULL) {
1053 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"),
1054 source_mask);
1055 g_free (dest_dir);
1056 g_free (source_mask);
1057 goto ask_file_mask;
1060 g_free (def_text_secure);
1061 g_free (source_mask);
1063 ctx->search_handle->is_case_sentitive = TRUE;
1064 if (source_easy_patterns)
1065 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1066 else
1067 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1069 tmp = dest_dir;
1070 dest_dir = tilde_expand (tmp);
1071 g_free (tmp);
1073 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1074 if (ctx->dest_mask == NULL)
1075 ctx->dest_mask = dest_dir;
1076 else
1077 ctx->dest_mask++;
1078 orig_mask = ctx->dest_mask;
1079 if (!*ctx->dest_mask
1080 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1081 && (!only_one
1082 || (!mc_stat (dest_dir, &buf) && S_ISDIR (buf.st_mode))))
1083 || (ctx->dive_into_subdirs
1084 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1085 || (only_one && !mc_stat (dest_dir, &buf)
1086 && S_ISDIR (buf.st_mode)))))
1087 ctx->dest_mask = g_strdup ("\\0");
1088 else {
1089 ctx->dest_mask = g_strdup (ctx->dest_mask);
1090 *orig_mask = '\0';
1092 if (!*dest_dir) {
1093 g_free (dest_dir);
1094 dest_dir = g_strdup ("./");
1096 if (val == B_USER)
1097 *do_background = TRUE;
1100 return dest_dir;