Calculate pauses in file operations.
[midnight-commander.git] / src / filemanager / filegui.c
blob5b76b649e79edd3cb7c985d4b6a8a31792d4d303
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 const gint64 t = g_get_monotonic_time ();
526 if (mode == Foreground)
527 title = _("File exists");
528 else
529 title = _("Background process: File exists");
531 #ifdef ENABLE_NLS
533 const unsigned short num = G_N_ELEMENTS (dlg_widgets);
535 for (i = 0; i < num; i++)
536 if (dlg_widgets[i].text != NULL)
537 dlg_widgets[i].text = _(dlg_widgets[i].text);
539 #endif /* ENABLE_NLS */
541 /* create widgets to get their real widths */
542 /* new file */
543 NEW_LABEL (0, dlg_widgets[0].text);
544 /* new file name */
545 p = vfs_path_from_str (ui->src_filename);
546 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
547 NEW_LABEL (1, s1);
548 vfs_path_free (p, TRUE);
549 g_free (s1);
550 /* new file size */
551 size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
552 NEW_LABEL (2, s2);
553 /* new file modification date & time */
554 cs1 = file_date (ui->src_stat->st_mtime);
555 NEW_LABEL (3, cs1);
557 /* existing file */
558 NEW_LABEL (4, dlg_widgets[4].text);
559 /* existing file name */
560 p = vfs_path_from_str (ui->tgt_filename);
561 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
562 NEW_LABEL (5, s1);
563 vfs_path_free (p, TRUE);
564 g_free (s1);
565 /* existing file size */
566 size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
567 NEW_LABEL (6, s2);
568 /* existing file modification date & time */
569 cs1 = file_date (ui->dst_stat->st_mtime);
570 NEW_LABEL (7, cs1);
572 /* will "Append" and "Reget" buttons be in the dialog? */
573 do_append = !S_ISDIR (ui->dst_stat->st_mode);
574 do_reget = do_append && ui->dst_stat->st_size != 0
575 && ui->src_stat->st_size > ui->dst_stat->st_size;
577 NEW_LABEL (8, dlg_widgets[8].text);
578 NEW_BUTTON (9);
579 NEW_BUTTON (10);
580 if (do_append)
581 NEW_BUTTON (11);
582 if (do_reget)
583 NEW_BUTTON (12);
585 NEW_LABEL (13, dlg_widgets[13].text);
586 dlg_widgets[14].widget =
587 WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
588 for (i = 15; i <= 20; i++)
589 NEW_BUTTON (i);
591 /* place widgets */
592 dlg_width -= 2 * (2 + gap); /* inside frame */
594 /* perhaps longest line is buttons */
595 bw1 = WCOLS (9) + gap + WCOLS (10);
596 if (do_append)
597 bw1 += gap + WCOLS (11);
598 if (do_reget)
599 bw1 += gap + WCOLS (12);
600 dlg_width = MAX (dlg_width, bw1);
602 bw2 = WCOLS (15);
603 for (i = 16; i <= 19; i++)
604 bw2 += gap + WCOLS (i);
605 dlg_width = MAX (dlg_width, bw2);
607 dlg_width = MAX (dlg_width, WCOLS (8));
608 dlg_width = MAX (dlg_width, WCOLS (13));
609 dlg_width = MAX (dlg_width, WCOLS (14));
611 /* truncate file names */
612 w = WCOLS (0) + gap + WCOLS (1);
613 if (w > dlg_width)
615 WLabel *l = LABEL (W (1));
617 w = dlg_width - gap - WCOLS (0);
618 label_set_text (l, str_trunc (l->text, w));
621 w = WCOLS (4) + gap + WCOLS (5);
622 if (w > dlg_width)
624 WLabel *l = LABEL (W (5));
626 w = dlg_width - gap - WCOLS (4);
627 label_set_text (l, str_trunc (l->text, w));
630 /* real dlalog width */
631 dlg_width += 2 * (2 + gap);
633 WX (1) = WX (0) + WCOLS (0) + gap;
634 WX (5) = WX (4) + WCOLS (4) + gap;
636 /* sizes: right alignment */
637 WX (2) = dlg_width / 2 - WCOLS (2);
638 WX (6) = dlg_width / 2 - WCOLS (6);
640 w = dlg_width - (2 + gap); /* right bound */
642 /* date & time */
643 WX (3) = w - WCOLS (3);
644 WX (7) = w - WCOLS (7);
646 /* buttons: center alignment */
647 WX (9) = dlg_width / 2 - bw1 / 2;
648 WX (10) = WX (9) + WCOLS (9) + gap;
649 if (do_append)
650 WX (11) = WX (10) + WCOLS (10) + gap;
651 if (do_reget)
652 WX (12) = WX (11) + WCOLS (11) + gap;
654 WX (15) = dlg_width / 2 - bw2 / 2;
655 for (i = 16; i <= 19; i++)
656 WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
658 /* TODO: write help (ticket #3970) */
659 ui->replace_dlg =
660 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
661 "[Replace]", title);
662 wd = WIDGET (ui->replace_dlg);
663 g = GROUP (ui->replace_dlg);
665 /* file info */
666 for (i = 0; i <= 7; i++)
667 ADD_LABEL (i);
668 group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
670 /* label & buttons */
671 ADD_LABEL (8); /* Overwrite this file? */
672 yes_id = ADD_BUTTON (9); /* Yes */
673 no_id = ADD_BUTTON (10); /* No */
674 if (do_append)
675 ADD_BUTTON (11); /* Append */
676 if (do_reget)
677 ADD_BUTTON (12); /* Reget */
678 group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
680 /* label & buttons */
681 ADD_LABEL (13); /* Overwrite all files? */
682 group_add_widget (g, dlg_widgets[14].widget);
683 for (i = 15; i <= 19; i++)
684 ADD_BUTTON (i);
685 group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
687 ADD_BUTTON (20); /* Abort */
689 group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
691 result = dlg_run (ui->replace_dlg);
693 if (result != B_CANCEL)
694 ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
696 widget_destroy (wd);
698 ctx->pauses += g_get_monotonic_time () - t;
700 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
702 #undef ADD_BUTTON
703 #undef NEW_BUTTON
704 #undef ADD_LABEL
705 #undef NEW_LABEL
706 #undef WCOLS
707 #undef WX
708 #undef W
711 /* --------------------------------------------------------------------------------------------- */
713 static gboolean
714 is_wildcarded (const char *p)
716 gboolean escaped = FALSE;
718 for (; *p != '\0'; p++)
720 if (*p == '\\')
722 if (p[1] >= '1' && p[1] <= '9' && !escaped)
723 return TRUE;
724 escaped = !escaped;
726 else
728 if ((*p == '*' || *p == '?') && !escaped)
729 return TRUE;
730 escaped = FALSE;
733 return FALSE;
736 /* --------------------------------------------------------------------------------------------- */
738 static void
739 place_progress_buttons (WDialog *h, gboolean suspended)
741 const size_t i = suspended ? 2 : 1;
742 Widget *w = WIDGET (h);
743 int buttons_width;
745 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
746 buttons_width += progress_buttons[i].len;
747 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
749 progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
750 progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
751 progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
754 /* --------------------------------------------------------------------------------------------- */
756 static int
757 progress_button_callback (WButton *button, int action)
759 (void) button;
760 (void) action;
762 /* don't close dialog in any case */
763 return 0;
766 /* --------------------------------------------------------------------------------------------- */
767 /*** public functions ****************************************************************************/
768 /* --------------------------------------------------------------------------------------------- */
770 * \fn file_op_context_t * file_op_context_new (FileOperation op)
771 * \param op file operation struct
772 * \return The newly-created context, filled with the default file mask values.
774 * Creates a new file operation context with the default values. If you later want
775 * to have a user interface for this, call file_progress_ui_create().
778 file_op_context_t *
779 file_op_context_new (FileOperation op)
781 file_op_context_t *ctx;
783 ctx = g_new0 (file_op_context_t, 1);
784 ctx->operation = op;
785 ctx->preserve = TRUE;
786 ctx->preserve_uidgid = (geteuid () == 0);
787 ctx->umask_kill = (mode_t) (~0);
788 ctx->erase_at_end = TRUE;
789 ctx->do_reget = -1;
790 ctx->stat_func = mc_lstat;
791 ctx->ask_overwrite = TRUE;
793 return ctx;
796 /* --------------------------------------------------------------------------------------------- */
798 * \fn void file_op_context_destroy (file_op_context_t *ctx)
799 * \param ctx The file operation context to destroy.
801 * Destroys the specified file operation context and its associated UI data, if
802 * it exists.
805 void
806 file_op_context_destroy (file_op_context_t *ctx)
808 if (ctx != NULL)
810 file_progress_ui_destroy (ctx);
811 mc_search_free (ctx->search_handle);
812 g_free (ctx);
816 /* --------------------------------------------------------------------------------------------- */
818 FileProgressStatus
819 file_progress_check_buttons (file_op_context_t *ctx)
821 int c;
822 Gpm_Event event;
823 file_progress_ui_t *ui;
825 if (ctx == NULL || ctx->ui == NULL)
826 return FILE_CONT;
828 ui = ctx->ui;
830 get_event:
831 event.x = -1; /* Don't show the GPM cursor */
832 c = tty_get_event (&event, FALSE, ctx->suspended);
833 if (c == EV_NONE)
834 return FILE_CONT;
836 /* Reinitialize to avoid old values after events other than selecting a button */
837 ui->op_dlg->ret_value = FILE_CONT;
839 dlg_process_event (ui->op_dlg, c, &event);
840 switch (ui->op_dlg->ret_value)
842 case FILE_SKIP:
843 if (ctx->suspended)
845 /* redraw dialog in case of Skip after Suspend */
846 place_progress_buttons (ui->op_dlg, FALSE);
847 widget_draw (WIDGET (ui->op_dlg));
849 ctx->suspended = FALSE;
850 return FILE_SKIP;
851 case B_CANCEL:
852 case FILE_ABORT:
853 ctx->suspended = FALSE;
854 return FILE_ABORT;
855 case FILE_SUSPEND:
856 ctx->suspended = !ctx->suspended;
857 place_progress_buttons (ui->op_dlg, ctx->suspended);
858 widget_draw (WIDGET (ui->op_dlg));
859 MC_FALLTHROUGH;
860 default:
861 if (ctx->suspended)
862 goto get_event;
863 return FILE_CONT;
867 /* --------------------------------------------------------------------------------------------- */
868 /* {{{ File progress display routines */
870 void
871 file_progress_ui_create (file_op_context_t *ctx, gboolean with_eta,
872 filegui_dialog_type_t dialog_type)
874 file_progress_ui_t *ui;
875 Widget *w;
876 WGroup *g;
877 int buttons_width;
878 int dlg_width = 58, dlg_height = 17;
879 int y = 2, x = 3;
880 WRect r;
882 if (ctx == NULL || ctx->ui != NULL)
883 return;
885 #ifdef ENABLE_NLS
886 if (progress_buttons[0].len == -1)
888 size_t i;
890 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
891 progress_buttons[i].text = _(progress_buttons[i].text);
893 #endif
895 ctx->dialog_type = dialog_type;
896 ctx->recursive_result = RECURSIVE_YES;
897 ctx->ui = g_new0 (file_progress_ui_t, 1);
899 ui = ctx->ui;
900 ui->replace_result = REPLACE_YES;
902 ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
903 file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
904 w = WIDGET (ui->op_dlg);
905 g = GROUP (ui->op_dlg);
907 if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
909 ui->showing_eta = with_eta && ctx->totals_computed;
910 ui->showing_bps = with_eta;
912 ui->src_file_label = label_new (y++, x, NULL);
913 group_add_widget (g, ui->src_file_label);
915 ui->src_file = label_new (y++, x, NULL);
916 group_add_widget (g, ui->src_file);
918 ui->tgt_file_label = label_new (y++, x, NULL);
919 group_add_widget (g, ui->tgt_file_label);
921 ui->tgt_file = label_new (y++, x, NULL);
922 group_add_widget (g, ui->tgt_file);
924 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
925 if (!classic_progressbar && (current_panel == right_panel))
926 ui->progress_file_gauge->from_left_to_right = FALSE;
927 group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
929 ui->progress_file_label = label_new (y++, x, NULL);
930 group_add_widget (g, ui->progress_file_label);
932 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
934 ui->total_bytes_label = hline_new (y++, -1, -1);
935 group_add_widget (g, ui->total_bytes_label);
937 if (ctx->totals_computed)
939 ui->progress_total_gauge =
940 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
941 if (!classic_progressbar && (current_panel == right_panel))
942 ui->progress_total_gauge->from_left_to_right = FALSE;
943 group_add_widget_autopos (g, ui->progress_total_gauge,
944 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
947 ui->total_files_processed_label = label_new (y++, x, NULL);
948 group_add_widget (g, ui->total_files_processed_label);
950 ui->time_label = label_new (y++, x, NULL);
951 group_add_widget (g, ui->time_label);
954 else
956 ui->src_file = label_new (y++, x, NULL);
957 group_add_widget (g, ui->src_file);
959 ui->total_files_processed_label = label_new (y++, x, NULL);
960 group_add_widget (g, ui->total_files_processed_label);
963 group_add_widget (g, hline_new (y++, -1, -1));
965 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
966 progress_buttons[0].flags, progress_buttons[0].text,
967 progress_button_callback));
968 if (progress_buttons[0].len == -1)
969 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
971 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
972 progress_buttons[1].flags, progress_buttons[1].text,
973 progress_button_callback));
974 if (progress_buttons[1].len == -1)
975 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
977 if (progress_buttons[2].len == -1)
979 /* create and destroy button to get it length */
980 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
981 progress_buttons[2].flags,
982 progress_buttons[2].text,
983 progress_button_callback));
984 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
985 widget_destroy (progress_buttons[2].w);
987 progress_buttons[2].w = progress_buttons[1].w;
989 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
990 progress_buttons[3].flags, progress_buttons[3].text,
991 progress_button_callback));
992 if (progress_buttons[3].len == -1)
993 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
995 group_add_widget (g, progress_buttons[0].w);
996 group_add_widget (g, progress_buttons[1].w);
997 group_add_widget (g, progress_buttons[3].w);
999 buttons_width = 2 +
1000 progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
1001 progress_buttons[3].len;
1003 /* adjust dialog sizes */
1004 r = w->rect;
1005 r.lines = y + 3;
1006 r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
1007 widget_set_size_rect (w, &r);
1009 place_progress_buttons (ui->op_dlg, FALSE);
1011 widget_select (progress_buttons[0].w);
1013 /* We will manage the dialog without any help, that's why
1014 we have to call dlg_init */
1015 dlg_init (ui->op_dlg);
1018 /* --------------------------------------------------------------------------------------------- */
1020 void
1021 file_progress_ui_destroy (file_op_context_t *ctx)
1023 if (ctx != NULL && ctx->ui != NULL)
1025 file_progress_ui_t *ui = (file_progress_ui_t *) ctx->ui;
1027 dlg_run_done (ui->op_dlg);
1028 widget_destroy (WIDGET (ui->op_dlg));
1029 MC_PTR_FREE (ctx->ui);
1033 /* --------------------------------------------------------------------------------------------- */
1035 show progressbar for file
1038 void
1039 file_progress_show (file_op_context_t *ctx, off_t done, off_t total,
1040 const char *stalled_msg, gboolean force_update)
1042 file_progress_ui_t *ui;
1044 if (ctx == NULL || ctx->ui == NULL)
1045 return;
1047 ui = ctx->ui;
1049 if (total == 0)
1051 gauge_show (ui->progress_file_gauge, FALSE);
1052 return;
1055 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
1056 gauge_show (ui->progress_file_gauge, TRUE);
1058 if (!force_update)
1059 return;
1061 if (!ui->showing_eta || ctx->eta_secs <= 0.5)
1062 label_set_text (ui->progress_file_label, stalled_msg);
1063 else
1065 char buffer2[BUF_TINY];
1067 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
1068 if (ctx->bps == 0)
1069 label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1070 else
1072 char buffer3[BUF_TINY];
1074 file_bps_prepare_for_show (buffer3, ctx->bps);
1075 label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1081 /* --------------------------------------------------------------------------------------------- */
1083 void
1084 file_progress_show_count (file_op_context_t *ctx)
1086 file_progress_ui_t *ui;
1088 if (ctx == NULL || ctx->ui == NULL)
1089 return;
1091 ui = ctx->ui;
1093 if (ui->total_files_processed_label == NULL)
1094 return;
1096 if (ctx->totals_computed)
1097 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"),
1098 ctx->total_progress_count, ctx->total_count);
1099 else
1100 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"),
1101 ctx->total_progress_count);
1104 /* --------------------------------------------------------------------------------------------- */
1106 void
1107 file_progress_show_total (file_op_context_t *ctx, uintmax_t copied_bytes, gint64 tv_current,
1108 gboolean show_summary)
1110 char buffer2[BUF_TINY];
1111 char buffer3[BUF_TINY];
1112 file_progress_ui_t *ui;
1114 if (ctx == NULL || ctx->ui == NULL)
1115 return;
1117 ui = ctx->ui;
1119 if (ui->progress_total_gauge != NULL)
1121 if (ctx->total_bytes == 0)
1122 gauge_show (ui->progress_total_gauge, FALSE);
1123 else
1125 gauge_set_value (ui->progress_total_gauge, 1024,
1126 (int) (1024 * copied_bytes / ctx->total_bytes));
1127 gauge_show (ui->progress_total_gauge, TRUE);
1131 if (!show_summary && ctx->total_bps == 0)
1132 return;
1134 if (ui->time_label != NULL)
1136 char buffer4[BUF_TINY];
1138 file_frmt_time (buffer2,
1139 (tv_current - ctx->pauses - ctx->total_transfer_start) / G_USEC_PER_SEC);
1141 if (ctx->totals_computed)
1143 file_eta_prepare_for_show (buffer3, ctx->total_eta_secs, TRUE);
1144 if (ctx->total_bps == 0)
1145 label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
1146 else
1148 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1149 label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1152 else
1154 if (ctx->total_bps == 0)
1155 label_set_textv (ui->time_label, _("Time: %s"), buffer2);
1156 else
1158 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1159 label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
1164 if (ui->total_bytes_label != NULL)
1166 size_trunc_len (buffer2, 5, copied_bytes, 0, panels_options.kilobyte_si);
1168 if (!ctx->totals_computed)
1169 hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
1170 else
1172 size_trunc_len (buffer3, 5, ctx->total_bytes, 0, panels_options.kilobyte_si);
1173 hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
1178 /* }}} */
1180 /* --------------------------------------------------------------------------------------------- */
1182 void
1183 file_progress_show_source (file_op_context_t *ctx, const vfs_path_t *vpath)
1185 file_progress_ui_t *ui;
1187 if (ctx == NULL || ctx->ui == NULL)
1188 return;
1190 ui = ctx->ui;
1192 if (vpath != NULL)
1194 label_set_text (ui->src_file_label, _("Source"));
1195 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1197 else
1199 label_set_text (ui->src_file_label, NULL);
1200 label_set_text (ui->src_file, NULL);
1204 /* --------------------------------------------------------------------------------------------- */
1206 void
1207 file_progress_show_target (file_op_context_t *ctx, const vfs_path_t *vpath)
1209 file_progress_ui_t *ui;
1211 if (ctx == NULL || ctx->ui == NULL)
1212 return;
1214 ui = ctx->ui;
1216 if (vpath != NULL)
1218 label_set_text (ui->tgt_file_label, _("Target"));
1219 label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1221 else
1223 label_set_text (ui->tgt_file_label, NULL);
1224 label_set_text (ui->tgt_file, NULL);
1228 /* --------------------------------------------------------------------------------------------- */
1230 gboolean
1231 file_progress_show_deleting (file_op_context_t *ctx, const vfs_path_t *vpath, size_t *count)
1233 static gint64 timestamp = 0;
1234 /* update with 25 FPS rate */
1235 static const gint64 delay = G_USEC_PER_SEC / 25;
1237 gboolean ret;
1239 if (ctx == NULL || ctx->ui == NULL)
1240 return FALSE;
1242 ret = mc_time_elapsed (&timestamp, delay);
1244 if (ret)
1246 file_progress_ui_t *ui;
1247 const char *s;
1249 ui = ctx->ui;
1251 if (ui->src_file_label != NULL)
1252 label_set_text (ui->src_file_label, _("Deleting"));
1254 s = vfs_path_as_str (vpath);
1255 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1258 if (count != NULL)
1259 (*count)++;
1261 return ret;
1264 /* --------------------------------------------------------------------------------------------- */
1266 FileProgressStatus
1267 file_progress_real_query_replace (file_op_context_t *ctx, enum OperationMode mode,
1268 const char *src, struct stat *src_stat,
1269 const char *dst, struct stat *dst_stat)
1271 file_progress_ui_t *ui;
1272 FileProgressStatus replace_with_zero;
1274 if (ctx == NULL || ctx->ui == NULL)
1275 return FILE_CONT;
1277 ui = ctx->ui;
1279 if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1280 || ui->replace_result == REPLACE_APPEND)
1282 ui->src_filename = src;
1283 ui->src_stat = src_stat;
1284 ui->tgt_filename = dst;
1285 ui->dst_stat = dst_stat;
1286 ui->replace_result = overwrite_query_dialog (ctx, mode);
1289 replace_with_zero = (src_stat->st_size == 0
1290 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1292 switch (ui->replace_result)
1294 case REPLACE_OLDER:
1295 do_refresh ();
1296 if (src_stat->st_mtime > dst_stat->st_mtime)
1297 return replace_with_zero;
1298 else
1299 return FILE_SKIP;
1301 case REPLACE_SIZE:
1302 do_refresh ();
1303 if (src_stat->st_size == dst_stat->st_size)
1304 return FILE_SKIP;
1305 else
1306 return replace_with_zero;
1308 case REPLACE_SMALLER:
1309 do_refresh ();
1310 if (src_stat->st_size > dst_stat->st_size)
1311 return FILE_CONT;
1312 else
1313 return FILE_SKIP;
1315 case REPLACE_ALL:
1316 do_refresh ();
1317 return replace_with_zero;
1319 case REPLACE_REGET:
1320 /* Careful: we fall through and set do_append */
1321 ctx->do_reget = dst_stat->st_size;
1322 MC_FALLTHROUGH;
1324 case REPLACE_APPEND:
1325 ctx->do_append = TRUE;
1326 MC_FALLTHROUGH;
1328 case REPLACE_YES:
1329 do_refresh ();
1330 return FILE_CONT;
1332 case REPLACE_NO:
1333 case REPLACE_NONE:
1334 do_refresh ();
1335 return FILE_SKIP;
1337 case REPLACE_ABORT:
1338 default:
1339 return FILE_ABORT;
1343 /* --------------------------------------------------------------------------------------------- */
1345 char *
1346 file_mask_dialog (file_op_context_t *ctx, gboolean only_one, const char *format, const void *text,
1347 const char *def_text, gboolean *do_bg)
1349 gboolean preserve;
1350 size_t fmd_xlen;
1351 vfs_path_t *vpath;
1352 gboolean source_easy_patterns = easy_patterns;
1353 char fmd_buf[BUF_MEDIUM];
1354 char *dest_dir = NULL;
1355 char *tmp;
1356 char *def_text_secure;
1358 if (ctx == NULL)
1359 return NULL;
1361 /* unselect checkbox if target filesystem doesn't support attributes */
1362 preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1364 ctx->stable_symlinks = FALSE;
1365 *do_bg = FALSE;
1367 /* filter out a possible password from def_text */
1368 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1369 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1370 vfs_path_free (vpath, TRUE);
1372 if (source_easy_patterns)
1373 def_text_secure = str_glob_escape (tmp);
1374 else
1375 def_text_secure = str_regex_escape (tmp);
1376 g_free (tmp);
1378 if (only_one)
1380 int format_len, text_len;
1381 int max_len;
1383 format_len = str_term_width1 (format);
1384 text_len = str_term_width1 (text);
1385 max_len = COLS - 2 - 6;
1387 if (format_len + text_len <= max_len)
1389 fmd_xlen = format_len + text_len + 6;
1390 fmd_xlen = MAX (fmd_xlen, 68);
1392 else
1394 text = str_trunc ((const char *) text, max_len - format_len);
1395 fmd_xlen = max_len + 6;
1398 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1400 else
1402 fmd_xlen = COLS * 2 / 3;
1403 fmd_xlen = MAX (fmd_xlen, 68);
1404 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1408 char *source_mask = NULL;
1409 char *orig_mask;
1410 int val;
1411 struct stat buf;
1413 quick_widget_t quick_widgets[] = {
1414 /* *INDENT-OFF* */
1415 QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1416 "input-def", &source_mask, NULL, FALSE, FALSE,
1417 INPUT_COMPLETE_FILENAMES),
1418 QUICK_START_COLUMNS,
1419 QUICK_SEPARATOR (FALSE),
1420 QUICK_NEXT_COLUMN,
1421 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1422 QUICK_STOP_COLUMNS,
1423 QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
1424 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1425 QUICK_SEPARATOR (TRUE),
1426 QUICK_START_COLUMNS,
1427 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1428 QUICK_CHECKBOX (N_("Preserve &attributes"), &preserve, NULL),
1429 QUICK_NEXT_COLUMN,
1430 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1431 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1432 QUICK_STOP_COLUMNS,
1433 QUICK_START_BUTTONS (TRUE, TRUE),
1434 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1435 #ifdef ENABLE_BACKGROUND
1436 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1437 #endif /* ENABLE_BACKGROUND */
1438 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1439 QUICK_END
1440 /* *INDENT-ON* */
1443 WRect r = { -1, -1, 0, fmd_xlen };
1445 quick_dialog_t qdlg = {
1446 r, op_names[ctx->operation], "[Mask Copy/Rename]",
1447 quick_widgets, NULL, NULL
1450 while (TRUE)
1452 val = quick_dialog_skip (&qdlg, 4);
1454 if (val == B_CANCEL)
1456 g_free (def_text_secure);
1457 return NULL;
1460 ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1462 if (preserve)
1464 ctx->preserve = TRUE;
1465 ctx->umask_kill = (mode_t) (~0);
1466 ctx->preserve_uidgid = (geteuid () == 0);
1468 else
1470 mode_t i2;
1472 ctx->preserve = ctx->preserve_uidgid = FALSE;
1473 i2 = umask (0);
1474 umask (i2);
1475 ctx->umask_kill = i2 ^ ((mode_t) (~0));
1478 if (*dest_dir == '\0')
1480 g_free (def_text_secure);
1481 g_free (source_mask);
1482 g_free (dest_dir);
1483 return NULL;
1486 ctx->search_handle = mc_search_new (source_mask, NULL);
1487 if (ctx->search_handle != NULL)
1488 break;
1490 message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1491 MC_PTR_FREE (dest_dir);
1492 MC_PTR_FREE (source_mask);
1495 g_free (def_text_secure);
1496 g_free (source_mask);
1498 ctx->search_handle->is_case_sensitive = TRUE;
1499 if (source_easy_patterns)
1500 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1501 else
1502 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1504 tmp = dest_dir;
1505 dest_dir = tilde_expand (tmp);
1506 g_free (tmp);
1507 vpath = vfs_path_from_str (dest_dir);
1509 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1510 if (ctx->dest_mask == NULL)
1511 ctx->dest_mask = dest_dir;
1512 else
1513 ctx->dest_mask++;
1515 orig_mask = ctx->dest_mask;
1517 if (*ctx->dest_mask == '\0'
1518 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1519 && (!only_one
1520 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1521 || (ctx->dive_into_subdirs
1522 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1523 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1524 ctx->dest_mask = g_strdup ("\\0");
1525 else
1527 ctx->dest_mask = g_strdup (ctx->dest_mask);
1528 *orig_mask = '\0';
1531 if (*dest_dir == '\0')
1533 g_free (dest_dir);
1534 dest_dir = g_strdup ("./");
1537 vfs_path_free (vpath, TRUE);
1539 if (val == B_USER)
1540 *do_bg = TRUE;
1543 return dest_dir;
1546 /* --------------------------------------------------------------------------------------------- */