(file_progress_show_total): use 'copied_bytes' to show numeric value.
[midnight-commander.git] / src / filemanager / filegui.c
blob3d189babbc7a85dc68af1dbf9373e7a7564ed50e
1 /*
2 File management GUI for the text mode edition
4 The copy code was based in GNU's cp, and was written by:
5 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
7 The move code was based in GNU's mv, and was written by:
8 Mike Parker and David MacKenzie.
10 Janne Kukonlehto added much error recovery to them for being used
11 in an interactive program.
13 Copyright (C) 1994-2024
14 Free Software Foundation, Inc.
16 Written by:
17 Janne Kukonlehto, 1994, 1995
18 Fred Leeflang, 1994, 1995
19 Miguel de Icaza, 1994, 1995, 1996
20 Jakub Jelinek, 1995, 1996
21 Norbert Warmuth, 1997
22 Pavel Machek, 1998
23 Slava Zanko, 2009, 2010, 2011, 2012, 2013
24 Andrew Borodin <aborodin@vmail.ru>, 2009-2023
26 This file is part of the Midnight Commander.
28 The Midnight Commander is free software: you can redistribute it
29 and/or modify it under the terms of the GNU General Public License as
30 published by the Free Software Foundation, either version 3 of the License,
31 or (at your option) any later version.
33 The Midnight Commander is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
38 You should have received a copy of the GNU General Public License
39 along with this program. If not, see <http://www.gnu.org/licenses/>.
43 * Please note that all dialogs used here must be safe for background
44 * operations.
47 /** \file filegui.c
48 * \brief Source: file management GUI for the text mode edition
51 /* {{{ Include files */
53 #include <config.h>
55 #if ((defined STAT_STATVFS || defined STAT_STATVFS64) \
56 && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \
57 || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME)))
58 #define USE_STATVFS 1
59 #else
60 #define USE_STATVFS 0
61 #endif
63 #include <errno.h>
64 #include <ctype.h>
65 #include <stdio.h>
66 #include <string.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
70 #if USE_STATVFS
71 #include <sys/statvfs.h>
72 #elif defined HAVE_SYS_VFS_H
73 #include <sys/vfs.h>
74 #elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
75 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
76 It does have statvfs.h, but shouldn't use it, since it doesn't
77 HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
78 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
79 #include <sys/param.h>
80 #include <sys/mount.h>
81 #elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
82 #include <fs_info.h>
83 #endif
85 #if USE_STATVFS
86 #if ! defined STAT_STATVFS && defined STAT_STATVFS64
87 #define STRUCT_STATVFS struct statvfs64
88 #define STATFS statvfs64
89 #else
90 #define STRUCT_STATVFS struct statvfs
91 #define STATFS statvfs
93 #if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
94 #include <sys/utsname.h>
95 #include <sys/statfs.h>
96 #define STAT_STATFS2_BSIZE 1
97 #endif
98 #endif
100 #else
101 #define STATFS statfs
102 #define STRUCT_STATVFS struct statfs
103 #ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */
104 /* BeOS has a statvfs function, but it does not return sensible values
105 for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
106 f_fstypename. Use 'struct fs_info' instead. */
107 static int
108 statfs (char const *filename, struct fs_info *buf)
110 dev_t device;
112 device = dev_for_path (filename);
114 if (device < 0)
116 errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
117 : device == B_BAD_VALUE ? EINVAL
118 : device == B_NAME_TOO_LONG ? ENAMETOOLONG
119 : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
120 return -1;
122 /* If successful, buf->dev will be == device. */
123 return fs_stat_dev (device, buf);
126 #define STRUCT_STATVFS struct fs_info
127 #else
128 #define STRUCT_STATVFS struct statfs
129 #endif
130 #endif
132 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
133 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
134 #else
135 #if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
136 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
137 #elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
138 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
139 #endif
140 #endif
142 #include <unistd.h>
144 #include "lib/global.h"
146 #include "lib/tty/key.h" /* tty_get_event */
147 #include "lib/mcconfig.h"
148 #include "lib/search.h"
149 #include "lib/vfs/vfs.h"
150 #include "lib/strutil.h"
151 #include "lib/timefmt.h" /* file_date() */
152 #include "lib/util.h"
153 #include "lib/widget.h"
155 #include "src/setup.h" /* verbose, safe_overwrite */
157 #include "filemanager.h"
159 #include "filegui.h"
161 /* }}} */
163 /*** global variables ****************************************************************************/
165 gboolean classic_progressbar = TRUE;
167 /*** file scope macro definitions ****************************************************************/
169 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
171 /*** file scope type declarations ****************************************************************/
173 /* *INDENT-OFF* */
174 typedef enum {
175 MSDOS_SUPER_MAGIC = 0x4d44,
176 NTFS_SB_MAGIC = 0x5346544e,
177 FUSE_MAGIC = 0x65735546,
178 PROC_SUPER_MAGIC = 0x9fa0,
179 SMB_SUPER_MAGIC = 0x517B,
180 NCP_SUPER_MAGIC = 0x564c,
181 USBDEVICE_SUPER_MAGIC = 0x9fa2
182 } filegui_nonattrs_fs_t;
183 /* *INDENT-ON* */
185 /* Used for button result values */
186 typedef enum
188 REPLACE_YES = B_USER,
189 REPLACE_NO,
190 REPLACE_APPEND,
191 REPLACE_REGET,
192 REPLACE_ALL,
193 REPLACE_OLDER,
194 REPLACE_NONE,
195 REPLACE_SMALLER,
196 REPLACE_SIZE,
197 REPLACE_ABORT
198 } replace_action_t;
200 /* This structure describes the UI and internal data required by a file
201 * operation context.
203 typedef struct
205 /* ETA and bps */
206 gboolean showing_eta;
207 gboolean showing_bps;
209 /* Dialog and widgets for the operation progress window */
210 WDialog *op_dlg;
211 /* Source file: label and name */
212 WLabel *src_file_label;
213 WLabel *src_file;
214 /* Target file: label and name */
215 WLabel *tgt_file_label;
216 WLabel *tgt_file;
217 WGauge *progress_file_gauge;
218 WLabel *progress_file_label;
220 WHLine *total_bytes_label;
221 WGauge *progress_total_gauge;
222 WLabel *total_files_processed_label;
223 WLabel *time_label;
225 /* Query replace dialog */
226 WDialog *replace_dlg;
227 const char *src_filename;
228 const char *tgt_filename;
229 replace_action_t replace_result;
230 gboolean dont_overwrite_with_zero;
232 struct stat *src_stat, *dst_stat;
233 } file_progress_ui_t;
235 /*** forward declarations (file scope functions) *************************************************/
237 /*** file scope variables ************************************************************************/
239 static struct
241 Widget *w;
242 FileProgressStatus action;
243 const char *text;
244 button_flags_t flags;
245 int len;
246 } progress_buttons[] = {
247 /* *INDENT-OFF* */
248 { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
249 { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
250 { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
251 { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
252 /* *INDENT-ON* */
255 /* --------------------------------------------------------------------------------------------- */
256 /*** file scope functions ************************************************************************/
257 /* --------------------------------------------------------------------------------------------- */
259 /* Return true if statvfs works. This is false for statvfs on systems
260 with GNU libc on Linux kernels before 2.6.36, which stats all
261 preceding entries in /proc/mounts; that makes df hang if even one
262 of the corresponding file systems is hard-mounted but not available. */
264 #if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
265 static int
266 statvfs_works (void)
268 #if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
269 return 1;
270 #else
271 static int statvfs_works_cache = -1;
272 struct utsname name;
274 if (statvfs_works_cache < 0)
275 statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
276 return statvfs_works_cache;
277 #endif
279 #endif
281 /* --------------------------------------------------------------------------------------------- */
283 static gboolean
284 filegui__check_attrs_on_fs (const char *fs_path)
286 STRUCT_STATVFS stfs;
288 #if USE_STATVFS && defined(STAT_STATVFS)
289 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
290 return TRUE;
291 #else
292 if (STATFS (fs_path, &stfs) != 0)
293 return TRUE;
294 #endif
296 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
297 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
298 switch ((filegui_nonattrs_fs_t) stfs.f_type)
300 case MSDOS_SUPER_MAGIC:
301 case NTFS_SB_MAGIC:
302 case PROC_SUPER_MAGIC:
303 case SMB_SUPER_MAGIC:
304 case NCP_SUPER_MAGIC:
305 case USBDEVICE_SUPER_MAGIC:
306 return FALSE;
307 default:
308 break;
310 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
311 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
312 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
313 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
314 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
315 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
316 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
317 return FALSE;
318 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
319 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
320 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
321 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
322 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
323 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
324 return FALSE;
325 #endif
327 return TRUE;
330 /* --------------------------------------------------------------------------------------------- */
332 static void
333 file_frmt_time (char *buffer, double eta_secs)
335 int eta_hours, eta_mins, eta_s;
337 eta_hours = (int) (eta_secs / (60 * 60));
338 eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
339 eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
340 g_snprintf (buffer, BUF_TINY, _("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
343 /* --------------------------------------------------------------------------------------------- */
345 static void
346 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
348 char _fmt_buff[BUF_TINY];
350 if (eta_secs <= 0.5)
352 if (!always_show)
354 *buffer = '\0';
355 return;
358 eta_secs = 1.0;
361 file_frmt_time (_fmt_buff, eta_secs);
362 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
365 /* --------------------------------------------------------------------------------------------- */
367 static void
368 file_bps_prepare_for_show (char *buffer, long bps)
370 if (bps > 1024 * 1024)
371 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
372 else if (bps > 1024)
373 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
374 else if (bps > 1)
375 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
376 else
377 *buffer = '\0';
380 /* --------------------------------------------------------------------------------------------- */
382 static cb_ret_t
383 file_ui_op_dlg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
385 switch (msg)
387 case MSG_ACTION:
388 /* Do not close the dialog because the query dialog will be shown */
389 if (parm == CK_Cancel)
391 DIALOG (w)->ret_value = FILE_ABORT; /* for file_progress_check_buttons() */
392 return MSG_HANDLED;
394 return MSG_NOT_HANDLED;
396 default:
397 return dlg_default_callback (w, sender, msg, parm, data);
401 /* --------------------------------------------------------------------------------------------- */
403 /* The dialog layout:
405 * +---------------------- File exists -----------------------+
406 * | New : /path/to/original_file_name | // 0, 1
407 * | 1234567 feb 4 2017 13:38 | // 2, 3
408 * | Existing: /path/to/target_file_name | // 4, 5
409 * | 1234567890 feb 4 2017 13:37 | // 6, 7
410 * +----------------------------------------------------------+
411 * | Overwrite this file? | // 8
412 * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
413 * +----------------------------------------------------------+
414 * | Overwrite all files? | // 13
415 * | [ ] Don't overwrite with zero length file | // 14
416 * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
417 * +----------------------------------------------------------|
418 * | [ Abort ] | // 20
419 * +----------------------------------------------------------+
422 static replace_action_t
423 overwrite_query_dialog (file_op_context_t *ctx, enum OperationMode mode)
425 #define W(i) dlg_widgets[i].widget
426 #define WX(i) W(i)->rect.x
427 #define WY(i) W(i)->rect.y
428 #define WCOLS(i) W(i)->rect.cols
430 #define NEW_LABEL(i, text) \
431 W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
433 #define ADD_LABEL(i) \
434 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
435 g->current != NULL ? g->current->data : NULL)
437 #define NEW_BUTTON(i) \
438 W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
439 dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
441 #define ADD_BUTTON(i) \
442 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
444 /* dialog sizes */
445 const int dlg_height = 17;
446 int dlg_width = 60;
448 struct
450 Widget *widget;
451 const char *text;
452 int y;
453 int x;
454 widget_pos_flags_t pos_flags;
455 int value; /* 0 for labels and checkbox */
456 } dlg_widgets[] = {
457 /* *INDENT-OFF* */
458 /* 0 - label */
459 { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
460 /* 1 - label - name */
461 { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
462 /* 2 - label - size */
463 { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
464 /* 3 - label - date & time */
465 { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
466 /* 4 - label */
467 { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
468 /* 5 - label - name */
469 { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
470 /* 6 - label - size */
471 { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
472 /* 7 - label - date & time */
473 { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
474 /* --------------------------------------------------- */
475 /* 8 - label */
476 { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
477 /* 9 - button */
478 { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
479 /* 10 - button */
480 { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
481 /* 11 - button */
482 { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
483 /* 12 - button */
484 { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
485 /* --------------------------------------------------- */
486 /* 13 - label */
487 { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
488 /* 14 - checkbox */
489 { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
490 /* 15 - button */
491 { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
492 /* 16 - button */
493 { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
494 /* 17 - button */
495 { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
496 /* 18 - button */
497 { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
498 /* 19 - button */
499 { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
500 /* --------------------------------------------------- */
501 /* 20 - button */
502 { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
503 /* *INDENT-ON* */
506 const int gap = 1;
508 file_progress_ui_t *ui = ctx->ui;
509 Widget *wd;
510 WGroup *g;
511 const char *title;
513 vfs_path_t *p;
514 char *s1;
515 const char *cs1;
516 char s2[BUF_SMALL];
517 int w, bw1, bw2;
518 unsigned short i;
520 gboolean do_append = FALSE, do_reget = FALSE;
521 unsigned long yes_id, no_id;
522 int result;
524 if (mode == Foreground)
525 title = _("File exists");
526 else
527 title = _("Background process: File exists");
529 #ifdef ENABLE_NLS
531 const unsigned short num = G_N_ELEMENTS (dlg_widgets);
533 for (i = 0; i < num; i++)
534 if (dlg_widgets[i].text != NULL)
535 dlg_widgets[i].text = _(dlg_widgets[i].text);
537 #endif /* ENABLE_NLS */
539 /* create widgets to get their real widths */
540 /* new file */
541 NEW_LABEL (0, dlg_widgets[0].text);
542 /* new file name */
543 p = vfs_path_from_str (ui->src_filename);
544 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
545 NEW_LABEL (1, s1);
546 vfs_path_free (p, TRUE);
547 g_free (s1);
548 /* new file size */
549 size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
550 NEW_LABEL (2, s2);
551 /* new file modification date & time */
552 cs1 = file_date (ui->src_stat->st_mtime);
553 NEW_LABEL (3, cs1);
555 /* existing file */
556 NEW_LABEL (4, dlg_widgets[4].text);
557 /* existing file name */
558 p = vfs_path_from_str (ui->tgt_filename);
559 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
560 NEW_LABEL (5, s1);
561 vfs_path_free (p, TRUE);
562 g_free (s1);
563 /* existing file size */
564 size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
565 NEW_LABEL (6, s2);
566 /* existing file modification date & time */
567 cs1 = file_date (ui->dst_stat->st_mtime);
568 NEW_LABEL (7, cs1);
570 /* will "Append" and "Reget" buttons be in the dialog? */
571 do_append = !S_ISDIR (ui->dst_stat->st_mode);
572 do_reget = do_append && ui->dst_stat->st_size != 0
573 && ui->src_stat->st_size > ui->dst_stat->st_size;
575 NEW_LABEL (8, dlg_widgets[8].text);
576 NEW_BUTTON (9);
577 NEW_BUTTON (10);
578 if (do_append)
579 NEW_BUTTON (11);
580 if (do_reget)
581 NEW_BUTTON (12);
583 NEW_LABEL (13, dlg_widgets[13].text);
584 dlg_widgets[14].widget =
585 WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
586 for (i = 15; i <= 20; i++)
587 NEW_BUTTON (i);
589 /* place widgets */
590 dlg_width -= 2 * (2 + gap); /* inside frame */
592 /* perhaps longest line is buttons */
593 bw1 = WCOLS (9) + gap + WCOLS (10);
594 if (do_append)
595 bw1 += gap + WCOLS (11);
596 if (do_reget)
597 bw1 += gap + WCOLS (12);
598 dlg_width = MAX (dlg_width, bw1);
600 bw2 = WCOLS (15);
601 for (i = 16; i <= 19; i++)
602 bw2 += gap + WCOLS (i);
603 dlg_width = MAX (dlg_width, bw2);
605 dlg_width = MAX (dlg_width, WCOLS (8));
606 dlg_width = MAX (dlg_width, WCOLS (13));
607 dlg_width = MAX (dlg_width, WCOLS (14));
609 /* truncate file names */
610 w = WCOLS (0) + gap + WCOLS (1);
611 if (w > dlg_width)
613 WLabel *l = LABEL (W (1));
615 w = dlg_width - gap - WCOLS (0);
616 label_set_text (l, str_trunc (l->text, w));
619 w = WCOLS (4) + gap + WCOLS (5);
620 if (w > dlg_width)
622 WLabel *l = LABEL (W (5));
624 w = dlg_width - gap - WCOLS (4);
625 label_set_text (l, str_trunc (l->text, w));
628 /* real dlalog width */
629 dlg_width += 2 * (2 + gap);
631 WX (1) = WX (0) + WCOLS (0) + gap;
632 WX (5) = WX (4) + WCOLS (4) + gap;
634 /* sizes: right alignment */
635 WX (2) = dlg_width / 2 - WCOLS (2);
636 WX (6) = dlg_width / 2 - WCOLS (6);
638 w = dlg_width - (2 + gap); /* right bound */
640 /* date & time */
641 WX (3) = w - WCOLS (3);
642 WX (7) = w - WCOLS (7);
644 /* buttons: center alignment */
645 WX (9) = dlg_width / 2 - bw1 / 2;
646 WX (10) = WX (9) + WCOLS (9) + gap;
647 if (do_append)
648 WX (11) = WX (10) + WCOLS (10) + gap;
649 if (do_reget)
650 WX (12) = WX (11) + WCOLS (11) + gap;
652 WX (15) = dlg_width / 2 - bw2 / 2;
653 for (i = 16; i <= 19; i++)
654 WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
656 /* TODO: write help (ticket #3970) */
657 ui->replace_dlg =
658 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
659 "[Replace]", title);
660 wd = WIDGET (ui->replace_dlg);
661 g = GROUP (ui->replace_dlg);
663 /* file info */
664 for (i = 0; i <= 7; i++)
665 ADD_LABEL (i);
666 group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
668 /* label & buttons */
669 ADD_LABEL (8); /* Overwrite this file? */
670 yes_id = ADD_BUTTON (9); /* Yes */
671 no_id = ADD_BUTTON (10); /* No */
672 if (do_append)
673 ADD_BUTTON (11); /* Append */
674 if (do_reget)
675 ADD_BUTTON (12); /* Reget */
676 group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
678 /* label & buttons */
679 ADD_LABEL (13); /* Overwrite all files? */
680 group_add_widget (g, dlg_widgets[14].widget);
681 for (i = 15; i <= 19; i++)
682 ADD_BUTTON (i);
683 group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
685 ADD_BUTTON (20); /* Abort */
687 group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
689 result = dlg_run (ui->replace_dlg);
691 if (result != B_CANCEL)
692 ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
694 widget_destroy (wd);
696 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
698 #undef ADD_BUTTON
699 #undef NEW_BUTTON
700 #undef ADD_LABEL
701 #undef NEW_LABEL
702 #undef WCOLS
703 #undef WX
704 #undef W
707 /* --------------------------------------------------------------------------------------------- */
709 static gboolean
710 is_wildcarded (const char *p)
712 gboolean escaped = FALSE;
714 for (; *p != '\0'; p++)
716 if (*p == '\\')
718 if (p[1] >= '1' && p[1] <= '9' && !escaped)
719 return TRUE;
720 escaped = !escaped;
722 else
724 if ((*p == '*' || *p == '?') && !escaped)
725 return TRUE;
726 escaped = FALSE;
729 return FALSE;
732 /* --------------------------------------------------------------------------------------------- */
734 static void
735 place_progress_buttons (WDialog *h, gboolean suspended)
737 const size_t i = suspended ? 2 : 1;
738 Widget *w = WIDGET (h);
739 int buttons_width;
741 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
742 buttons_width += progress_buttons[i].len;
743 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
745 progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
746 progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
747 progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
750 /* --------------------------------------------------------------------------------------------- */
752 static int
753 progress_button_callback (WButton *button, int action)
755 (void) button;
756 (void) action;
758 /* don't close dialog in any case */
759 return 0;
762 /* --------------------------------------------------------------------------------------------- */
763 /*** public functions ****************************************************************************/
764 /* --------------------------------------------------------------------------------------------- */
766 * \fn file_op_context_t * file_op_context_new (FileOperation op)
767 * \param op file operation struct
768 * \return The newly-created context, filled with the default file mask values.
770 * Creates a new file operation context with the default values. If you later want
771 * to have a user interface for this, call file_progress_ui_create().
774 file_op_context_t *
775 file_op_context_new (FileOperation op)
777 file_op_context_t *ctx;
779 ctx = g_new0 (file_op_context_t, 1);
780 ctx->operation = op;
781 ctx->preserve = TRUE;
782 ctx->preserve_uidgid = (geteuid () == 0);
783 ctx->umask_kill = (mode_t) (~0);
784 ctx->erase_at_end = TRUE;
785 ctx->do_reget = -1;
786 ctx->stat_func = mc_lstat;
787 ctx->ask_overwrite = TRUE;
789 return ctx;
792 /* --------------------------------------------------------------------------------------------- */
794 * \fn void file_op_context_destroy (file_op_context_t *ctx)
795 * \param ctx The file operation context to destroy.
797 * Destroys the specified file operation context and its associated UI data, if
798 * it exists.
801 void
802 file_op_context_destroy (file_op_context_t *ctx)
804 if (ctx != NULL)
806 file_progress_ui_destroy (ctx);
807 mc_search_free (ctx->search_handle);
808 g_free (ctx);
812 /* --------------------------------------------------------------------------------------------- */
814 FileProgressStatus
815 file_progress_check_buttons (file_op_context_t *ctx)
817 int c;
818 Gpm_Event event;
819 file_progress_ui_t *ui;
821 if (ctx == NULL || ctx->ui == NULL)
822 return FILE_CONT;
824 ui = ctx->ui;
826 get_event:
827 event.x = -1; /* Don't show the GPM cursor */
828 c = tty_get_event (&event, FALSE, ctx->suspended);
829 if (c == EV_NONE)
830 return FILE_CONT;
832 /* Reinitialize to avoid old values after events other than selecting a button */
833 ui->op_dlg->ret_value = FILE_CONT;
835 dlg_process_event (ui->op_dlg, c, &event);
836 switch (ui->op_dlg->ret_value)
838 case FILE_SKIP:
839 if (ctx->suspended)
841 /* redraw dialog in case of Skip after Suspend */
842 place_progress_buttons (ui->op_dlg, FALSE);
843 widget_draw (WIDGET (ui->op_dlg));
845 ctx->suspended = FALSE;
846 return FILE_SKIP;
847 case B_CANCEL:
848 case FILE_ABORT:
849 ctx->suspended = FALSE;
850 return FILE_ABORT;
851 case FILE_SUSPEND:
852 ctx->suspended = !ctx->suspended;
853 place_progress_buttons (ui->op_dlg, ctx->suspended);
854 widget_draw (WIDGET (ui->op_dlg));
855 MC_FALLTHROUGH;
856 default:
857 if (ctx->suspended)
858 goto get_event;
859 return FILE_CONT;
863 /* --------------------------------------------------------------------------------------------- */
864 /* {{{ File progress display routines */
866 void
867 file_progress_ui_create (file_op_context_t *ctx, gboolean with_eta,
868 filegui_dialog_type_t dialog_type)
870 file_progress_ui_t *ui;
871 Widget *w;
872 WGroup *g;
873 int buttons_width;
874 int dlg_width = 58, dlg_height = 17;
875 int y = 2, x = 3;
876 WRect r;
878 if (ctx == NULL || ctx->ui != NULL)
879 return;
881 #ifdef ENABLE_NLS
882 if (progress_buttons[0].len == -1)
884 size_t i;
886 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
887 progress_buttons[i].text = _(progress_buttons[i].text);
889 #endif
891 ctx->dialog_type = dialog_type;
892 ctx->recursive_result = RECURSIVE_YES;
893 ctx->ui = g_new0 (file_progress_ui_t, 1);
895 ui = ctx->ui;
896 ui->replace_result = REPLACE_YES;
898 ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
899 file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
900 w = WIDGET (ui->op_dlg);
901 g = GROUP (ui->op_dlg);
903 if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
905 ui->showing_eta = with_eta && ctx->totals_computed;
906 ui->showing_bps = with_eta;
908 ui->src_file_label = label_new (y++, x, NULL);
909 group_add_widget (g, ui->src_file_label);
911 ui->src_file = label_new (y++, x, NULL);
912 group_add_widget (g, ui->src_file);
914 ui->tgt_file_label = label_new (y++, x, NULL);
915 group_add_widget (g, ui->tgt_file_label);
917 ui->tgt_file = label_new (y++, x, NULL);
918 group_add_widget (g, ui->tgt_file);
920 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
921 if (!classic_progressbar && (current_panel == right_panel))
922 ui->progress_file_gauge->from_left_to_right = FALSE;
923 group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
925 ui->progress_file_label = label_new (y++, x, NULL);
926 group_add_widget (g, ui->progress_file_label);
928 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
930 ui->total_bytes_label = hline_new (y++, -1, -1);
931 group_add_widget (g, ui->total_bytes_label);
933 if (ctx->totals_computed)
935 ui->progress_total_gauge =
936 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
937 if (!classic_progressbar && (current_panel == right_panel))
938 ui->progress_total_gauge->from_left_to_right = FALSE;
939 group_add_widget_autopos (g, ui->progress_total_gauge,
940 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
943 ui->total_files_processed_label = label_new (y++, x, NULL);
944 group_add_widget (g, ui->total_files_processed_label);
946 ui->time_label = label_new (y++, x, NULL);
947 group_add_widget (g, ui->time_label);
950 else
952 ui->src_file = label_new (y++, x, NULL);
953 group_add_widget (g, ui->src_file);
955 ui->total_files_processed_label = label_new (y++, x, NULL);
956 group_add_widget (g, ui->total_files_processed_label);
959 group_add_widget (g, hline_new (y++, -1, -1));
961 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
962 progress_buttons[0].flags, progress_buttons[0].text,
963 progress_button_callback));
964 if (progress_buttons[0].len == -1)
965 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
967 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
968 progress_buttons[1].flags, progress_buttons[1].text,
969 progress_button_callback));
970 if (progress_buttons[1].len == -1)
971 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
973 if (progress_buttons[2].len == -1)
975 /* create and destroy button to get it length */
976 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
977 progress_buttons[2].flags,
978 progress_buttons[2].text,
979 progress_button_callback));
980 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
981 widget_destroy (progress_buttons[2].w);
983 progress_buttons[2].w = progress_buttons[1].w;
985 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
986 progress_buttons[3].flags, progress_buttons[3].text,
987 progress_button_callback));
988 if (progress_buttons[3].len == -1)
989 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
991 group_add_widget (g, progress_buttons[0].w);
992 group_add_widget (g, progress_buttons[1].w);
993 group_add_widget (g, progress_buttons[3].w);
995 buttons_width = 2 +
996 progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
997 progress_buttons[3].len;
999 /* adjust dialog sizes */
1000 r = w->rect;
1001 r.lines = y + 3;
1002 r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
1003 widget_set_size_rect (w, &r);
1005 place_progress_buttons (ui->op_dlg, FALSE);
1007 widget_select (progress_buttons[0].w);
1009 /* We will manage the dialog without any help, that's why
1010 we have to call dlg_init */
1011 dlg_init (ui->op_dlg);
1014 /* --------------------------------------------------------------------------------------------- */
1016 void
1017 file_progress_ui_destroy (file_op_context_t *ctx)
1019 if (ctx != NULL && ctx->ui != NULL)
1021 file_progress_ui_t *ui = (file_progress_ui_t *) ctx->ui;
1023 dlg_run_done (ui->op_dlg);
1024 widget_destroy (WIDGET (ui->op_dlg));
1025 MC_PTR_FREE (ctx->ui);
1029 /* --------------------------------------------------------------------------------------------- */
1031 show progressbar for file
1034 void
1035 file_progress_show (file_op_context_t *ctx, off_t done, off_t total,
1036 const char *stalled_msg, gboolean force_update)
1038 file_progress_ui_t *ui;
1040 if (ctx == NULL || ctx->ui == NULL)
1041 return;
1043 ui = ctx->ui;
1045 if (total == 0)
1047 gauge_show (ui->progress_file_gauge, FALSE);
1048 return;
1051 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
1052 gauge_show (ui->progress_file_gauge, TRUE);
1054 if (!force_update)
1055 return;
1057 if (!ui->showing_eta || ctx->eta_secs <= 0.5)
1058 label_set_text (ui->progress_file_label, stalled_msg);
1059 else
1061 char buffer2[BUF_TINY];
1063 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
1064 if (ctx->bps == 0)
1065 label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1066 else
1068 char buffer3[BUF_TINY];
1070 file_bps_prepare_for_show (buffer3, ctx->bps);
1071 label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1077 /* --------------------------------------------------------------------------------------------- */
1079 void
1080 file_progress_show_count (file_op_context_t *ctx)
1082 file_progress_ui_t *ui;
1084 if (ctx == NULL || ctx->ui == NULL)
1085 return;
1087 ui = ctx->ui;
1089 if (ui->total_files_processed_label == NULL)
1090 return;
1092 if (ctx->totals_computed)
1093 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"),
1094 ctx->total_progress_count, ctx->total_count);
1095 else
1096 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"),
1097 ctx->total_progress_count);
1100 /* --------------------------------------------------------------------------------------------- */
1102 void
1103 file_progress_show_total (file_op_context_t *ctx, uintmax_t copied_bytes, gboolean show_summary)
1105 char buffer2[BUF_TINY];
1106 char buffer3[BUF_TINY];
1107 file_progress_ui_t *ui;
1109 if (ctx == NULL || ctx->ui == NULL)
1110 return;
1112 ui = ctx->ui;
1114 if (ui->progress_total_gauge != NULL)
1116 if (ctx->total_bytes == 0)
1117 gauge_show (ui->progress_total_gauge, FALSE);
1118 else
1120 gauge_set_value (ui->progress_total_gauge, 1024,
1121 (int) (1024 * copied_bytes / ctx->total_bytes));
1122 gauge_show (ui->progress_total_gauge, TRUE);
1126 if (!show_summary && ctx->total_bps == 0)
1127 return;
1129 if (ui->time_label != NULL)
1131 gint64 tv_current;
1132 char buffer4[BUF_TINY];
1134 tv_current = g_get_monotonic_time ();
1135 file_frmt_time (buffer2, (tv_current - ctx->total_transfer_start) / G_USEC_PER_SEC);
1137 if (ctx->totals_computed)
1139 file_eta_prepare_for_show (buffer3, ctx->total_eta_secs, TRUE);
1140 if (ctx->total_bps == 0)
1141 label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
1142 else
1144 file_bps_prepare_for_show (buffer4, (long) ctx->total_bps);
1145 label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1148 else
1150 if (ctx->total_bps == 0)
1151 label_set_textv (ui->time_label, _("Time: %s"), buffer2);
1152 else
1154 file_bps_prepare_for_show (buffer4, (long) ctx->total_bps);
1155 label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
1160 if (ui->total_bytes_label != NULL)
1162 size_trunc_len (buffer2, 5, copied_bytes, 0, panels_options.kilobyte_si);
1164 if (!ctx->totals_computed)
1165 hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
1166 else
1168 size_trunc_len (buffer3, 5, ctx->total_bytes, 0, panels_options.kilobyte_si);
1169 hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
1174 /* }}} */
1176 /* --------------------------------------------------------------------------------------------- */
1178 void
1179 file_progress_show_source (file_op_context_t *ctx, const vfs_path_t *vpath)
1181 file_progress_ui_t *ui;
1183 if (ctx == NULL || ctx->ui == NULL)
1184 return;
1186 ui = ctx->ui;
1188 if (vpath != NULL)
1190 label_set_text (ui->src_file_label, _("Source"));
1191 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1193 else
1195 label_set_text (ui->src_file_label, NULL);
1196 label_set_text (ui->src_file, NULL);
1200 /* --------------------------------------------------------------------------------------------- */
1202 void
1203 file_progress_show_target (file_op_context_t *ctx, const vfs_path_t *vpath)
1205 file_progress_ui_t *ui;
1207 if (ctx == NULL || ctx->ui == NULL)
1208 return;
1210 ui = ctx->ui;
1212 if (vpath != NULL)
1214 label_set_text (ui->tgt_file_label, _("Target"));
1215 label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1217 else
1219 label_set_text (ui->tgt_file_label, NULL);
1220 label_set_text (ui->tgt_file, NULL);
1224 /* --------------------------------------------------------------------------------------------- */
1226 gboolean
1227 file_progress_show_deleting (file_op_context_t *ctx, const vfs_path_t *vpath, size_t *count)
1229 static gint64 timestamp = 0;
1230 /* update with 25 FPS rate */
1231 static const gint64 delay = G_USEC_PER_SEC / 25;
1233 gboolean ret;
1235 if (ctx == NULL || ctx->ui == NULL)
1236 return FALSE;
1238 ret = mc_time_elapsed (&timestamp, delay);
1240 if (ret)
1242 file_progress_ui_t *ui;
1243 const char *s;
1245 ui = ctx->ui;
1247 if (ui->src_file_label != NULL)
1248 label_set_text (ui->src_file_label, _("Deleting"));
1250 s = vfs_path_as_str (vpath);
1251 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1254 if (count != NULL)
1255 (*count)++;
1257 return ret;
1260 /* --------------------------------------------------------------------------------------------- */
1262 FileProgressStatus
1263 file_progress_real_query_replace (file_op_context_t *ctx, enum OperationMode mode,
1264 const char *src, struct stat *src_stat,
1265 const char *dst, struct stat *dst_stat)
1267 file_progress_ui_t *ui;
1268 FileProgressStatus replace_with_zero;
1270 if (ctx == NULL || ctx->ui == NULL)
1271 return FILE_CONT;
1273 ui = ctx->ui;
1275 if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1276 || ui->replace_result == REPLACE_APPEND)
1278 ui->src_filename = src;
1279 ui->src_stat = src_stat;
1280 ui->tgt_filename = dst;
1281 ui->dst_stat = dst_stat;
1282 ui->replace_result = overwrite_query_dialog (ctx, mode);
1285 replace_with_zero = (src_stat->st_size == 0
1286 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1288 switch (ui->replace_result)
1290 case REPLACE_OLDER:
1291 do_refresh ();
1292 if (src_stat->st_mtime > dst_stat->st_mtime)
1293 return replace_with_zero;
1294 else
1295 return FILE_SKIP;
1297 case REPLACE_SIZE:
1298 do_refresh ();
1299 if (src_stat->st_size == dst_stat->st_size)
1300 return FILE_SKIP;
1301 else
1302 return replace_with_zero;
1304 case REPLACE_SMALLER:
1305 do_refresh ();
1306 if (src_stat->st_size > dst_stat->st_size)
1307 return FILE_CONT;
1308 else
1309 return FILE_SKIP;
1311 case REPLACE_ALL:
1312 do_refresh ();
1313 return replace_with_zero;
1315 case REPLACE_REGET:
1316 /* Careful: we fall through and set do_append */
1317 ctx->do_reget = dst_stat->st_size;
1318 MC_FALLTHROUGH;
1320 case REPLACE_APPEND:
1321 ctx->do_append = TRUE;
1322 MC_FALLTHROUGH;
1324 case REPLACE_YES:
1325 do_refresh ();
1326 return FILE_CONT;
1328 case REPLACE_NO:
1329 case REPLACE_NONE:
1330 do_refresh ();
1331 return FILE_SKIP;
1333 case REPLACE_ABORT:
1334 default:
1335 return FILE_ABORT;
1339 /* --------------------------------------------------------------------------------------------- */
1341 char *
1342 file_mask_dialog (file_op_context_t *ctx, gboolean only_one, const char *format, const void *text,
1343 const char *def_text, gboolean *do_bg)
1345 gboolean preserve;
1346 size_t fmd_xlen;
1347 vfs_path_t *vpath;
1348 gboolean source_easy_patterns = easy_patterns;
1349 char fmd_buf[BUF_MEDIUM];
1350 char *dest_dir = NULL;
1351 char *tmp;
1352 char *def_text_secure;
1354 if (ctx == NULL)
1355 return NULL;
1357 /* unselect checkbox if target filesystem doesn't support attributes */
1358 preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1360 ctx->stable_symlinks = FALSE;
1361 *do_bg = FALSE;
1363 /* filter out a possible password from def_text */
1364 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1365 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1366 vfs_path_free (vpath, TRUE);
1368 if (source_easy_patterns)
1369 def_text_secure = str_glob_escape (tmp);
1370 else
1371 def_text_secure = str_regex_escape (tmp);
1372 g_free (tmp);
1374 if (only_one)
1376 int format_len, text_len;
1377 int max_len;
1379 format_len = str_term_width1 (format);
1380 text_len = str_term_width1 (text);
1381 max_len = COLS - 2 - 6;
1383 if (format_len + text_len <= max_len)
1385 fmd_xlen = format_len + text_len + 6;
1386 fmd_xlen = MAX (fmd_xlen, 68);
1388 else
1390 text = str_trunc ((const char *) text, max_len - format_len);
1391 fmd_xlen = max_len + 6;
1394 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1396 else
1398 fmd_xlen = COLS * 2 / 3;
1399 fmd_xlen = MAX (fmd_xlen, 68);
1400 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1404 char *source_mask = NULL;
1405 char *orig_mask;
1406 int val;
1407 struct stat buf;
1409 quick_widget_t quick_widgets[] = {
1410 /* *INDENT-OFF* */
1411 QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1412 "input-def", &source_mask, NULL, FALSE, FALSE,
1413 INPUT_COMPLETE_FILENAMES),
1414 QUICK_START_COLUMNS,
1415 QUICK_SEPARATOR (FALSE),
1416 QUICK_NEXT_COLUMN,
1417 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1418 QUICK_STOP_COLUMNS,
1419 QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
1420 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1421 QUICK_SEPARATOR (TRUE),
1422 QUICK_START_COLUMNS,
1423 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1424 QUICK_CHECKBOX (N_("Preserve &attributes"), &preserve, NULL),
1425 QUICK_NEXT_COLUMN,
1426 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1427 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1428 QUICK_STOP_COLUMNS,
1429 QUICK_START_BUTTONS (TRUE, TRUE),
1430 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1431 #ifdef ENABLE_BACKGROUND
1432 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1433 #endif /* ENABLE_BACKGROUND */
1434 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1435 QUICK_END
1436 /* *INDENT-ON* */
1439 WRect r = { -1, -1, 0, fmd_xlen };
1441 quick_dialog_t qdlg = {
1442 r, op_names[ctx->operation], "[Mask Copy/Rename]",
1443 quick_widgets, NULL, NULL
1446 while (TRUE)
1448 val = quick_dialog_skip (&qdlg, 4);
1450 if (val == B_CANCEL)
1452 g_free (def_text_secure);
1453 return NULL;
1456 ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1458 if (preserve)
1460 ctx->preserve = TRUE;
1461 ctx->umask_kill = (mode_t) (~0);
1462 ctx->preserve_uidgid = (geteuid () == 0);
1464 else
1466 mode_t i2;
1468 ctx->preserve = ctx->preserve_uidgid = FALSE;
1469 i2 = umask (0);
1470 umask (i2);
1471 ctx->umask_kill = i2 ^ ((mode_t) (~0));
1474 if (*dest_dir == '\0')
1476 g_free (def_text_secure);
1477 g_free (source_mask);
1478 g_free (dest_dir);
1479 return NULL;
1482 ctx->search_handle = mc_search_new (source_mask, NULL);
1483 if (ctx->search_handle != NULL)
1484 break;
1486 message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1487 MC_PTR_FREE (dest_dir);
1488 MC_PTR_FREE (source_mask);
1491 g_free (def_text_secure);
1492 g_free (source_mask);
1494 ctx->search_handle->is_case_sensitive = TRUE;
1495 if (source_easy_patterns)
1496 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1497 else
1498 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1500 tmp = dest_dir;
1501 dest_dir = tilde_expand (tmp);
1502 g_free (tmp);
1503 vpath = vfs_path_from_str (dest_dir);
1505 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1506 if (ctx->dest_mask == NULL)
1507 ctx->dest_mask = dest_dir;
1508 else
1509 ctx->dest_mask++;
1511 orig_mask = ctx->dest_mask;
1513 if (*ctx->dest_mask == '\0'
1514 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1515 && (!only_one
1516 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1517 || (ctx->dive_into_subdirs
1518 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1519 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1520 ctx->dest_mask = g_strdup ("\\0");
1521 else
1523 ctx->dest_mask = g_strdup (ctx->dest_mask);
1524 *orig_mask = '\0';
1527 if (*dest_dir == '\0')
1529 g_free (dest_dir);
1530 dest_dir = g_strdup ("./");
1533 vfs_path_free (vpath, TRUE);
1535 if (val == B_USER)
1536 *do_bg = TRUE;
1539 return dest_dir;
1542 /* --------------------------------------------------------------------------------------------- */