Move widget add/del API from WDialog to WGroup.
[midnight-commander.git] / src / filemanager / filegui.c
blob163d8dcee0b4233b3f230d9e461bef7626e202ba
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-2020
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, 2010, 2011, 2012, 2013
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/strescape.h"
151 #include "lib/strutil.h"
152 #include "lib/timefmt.h" /* file_date() */
153 #include "lib/util.h"
154 #include "lib/widget.h"
156 #include "src/setup.h" /* verbose, safe_overwrite */
158 #include "midnight.h"
159 #include "fileopctx.h" /* FILE_CONT */
161 #include "filegui.h"
163 /* }}} */
165 /*** global variables ****************************************************************************/
167 gboolean classic_progressbar = TRUE;
169 /*** file scope macro definitions ****************************************************************/
171 #define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->cols - 10)
172 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->cols - 10)
174 /*** file scope type declarations ****************************************************************/
176 /* *INDENT-OFF* */
177 typedef enum {
178 MSDOS_SUPER_MAGIC = 0x4d44,
179 NTFS_SB_MAGIC = 0x5346544e,
180 FUSE_MAGIC = 0x65735546,
181 PROC_SUPER_MAGIC = 0x9fa0,
182 SMB_SUPER_MAGIC = 0x517B,
183 NCP_SUPER_MAGIC = 0x564c,
184 USBDEVICE_SUPER_MAGIC = 0x9fa2
185 } filegui_nonattrs_fs_t;
186 /* *INDENT-ON* */
188 /* Used for button result values */
189 typedef enum
191 REPLACE_YES = B_USER,
192 REPLACE_NO,
193 REPLACE_APPEND,
194 REPLACE_REGET,
195 REPLACE_ALL,
196 REPLACE_OLDER,
197 REPLACE_NONE,
198 REPLACE_SMALLER,
199 REPLACE_SIZE,
200 REPLACE_ABORT
201 } replace_action_t;
203 /* This structure describes the UI and internal data required by a file
204 * operation context.
206 typedef struct
208 /* ETA and bps */
209 gboolean showing_eta;
210 gboolean showing_bps;
212 /* Dialog and widgets for the operation progress window */
213 WDialog *op_dlg;
214 /* Source file: label and name */
215 WLabel *src_file_label;
216 WLabel *src_file;
217 /* Target file: label and name */
218 WLabel *tgt_file_label;
219 WLabel *tgt_file;
221 WGauge *progress_file_gauge;
222 WLabel *progress_file_label;
224 WGauge *progress_total_gauge;
226 WLabel *total_files_processed_label;
227 WLabel *time_label;
228 WHLine *total_bytes_label;
230 /* Query replace dialog */
231 WDialog *replace_dlg;
232 const char *src_filename;
233 const char *tgt_filename;
234 replace_action_t replace_result;
235 gboolean dont_overwrite_with_zero;
237 struct stat *src_stat, *dst_stat;
238 } file_op_context_ui_t;
240 /*** file scope variables ************************************************************************/
242 static struct
244 Widget *w;
245 FileProgressStatus action;
246 const char *text;
247 button_flags_t flags;
248 int len;
249 } progress_buttons[] =
251 /* *INDENT-OFF* */
252 { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
253 { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
254 { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
255 { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
256 /* *INDENT-ON* */
259 /* --------------------------------------------------------------------------------------------- */
260 /*** file scope functions ************************************************************************/
261 /* --------------------------------------------------------------------------------------------- */
263 /* Return true if statvfs works. This is false for statvfs on systems
264 with GNU libc on Linux kernels before 2.6.36, which stats all
265 preceding entries in /proc/mounts; that makes df hang if even one
266 of the corresponding file systems is hard-mounted but not available. */
268 #if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
269 static int
270 statvfs_works (void)
272 #if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
273 return 1;
274 #else
275 static int statvfs_works_cache = -1;
276 struct utsname name;
278 if (statvfs_works_cache < 0)
279 statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
280 return statvfs_works_cache;
281 #endif
283 #endif
285 /* --------------------------------------------------------------------------------------------- */
287 static gboolean
288 filegui__check_attrs_on_fs (const char *fs_path)
290 STRUCT_STATVFS stfs;
292 #if USE_STATVFS && defined(STAT_STATVFS)
293 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
294 return TRUE;
295 #else
296 if (STATFS (fs_path, &stfs) != 0)
297 return TRUE;
298 #endif
300 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
301 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
302 switch ((filegui_nonattrs_fs_t) stfs.f_type)
304 case MSDOS_SUPER_MAGIC:
305 case NTFS_SB_MAGIC:
306 case PROC_SUPER_MAGIC:
307 case SMB_SUPER_MAGIC:
308 case NCP_SUPER_MAGIC:
309 case USBDEVICE_SUPER_MAGIC:
310 return FALSE;
311 default:
312 break;
314 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
315 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
316 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
317 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
318 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
319 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
320 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
321 return FALSE;
322 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
323 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
324 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
325 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
326 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
327 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
328 return FALSE;
329 #endif
331 return TRUE;
334 /* --------------------------------------------------------------------------------------------- */
336 static void
337 file_frmt_time (char *buffer, double eta_secs)
339 int eta_hours, eta_mins, eta_s;
341 eta_hours = (int) (eta_secs / (60 * 60));
342 eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
343 eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
344 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
347 /* --------------------------------------------------------------------------------------------- */
349 static void
350 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
352 char _fmt_buff[BUF_TINY];
354 if (eta_secs <= 0.5 && !always_show)
356 *buffer = '\0';
357 return;
360 if (eta_secs <= 0.5)
361 eta_secs = 1;
362 file_frmt_time (_fmt_buff, eta_secs);
363 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
366 /* --------------------------------------------------------------------------------------------- */
368 static void
369 file_bps_prepare_for_show (char *buffer, long bps)
371 if (bps > 1024 * 1024)
372 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
373 else if (bps > 1024)
374 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
375 else if (bps > 1)
376 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
377 else
378 *buffer = '\0';
381 /* --------------------------------------------------------------------------------------------- */
383 /* The dialog layout:
385 * +---------------------- File exists -----------------------+
386 * | New : /path/to/original_file_name | // 0, 1
387 * | 1234567 feb 4 2017 13:38 | // 2, 3
388 * | Existing: /path/to/target_file_name | // 4, 5
389 * | 1234567890 feb 4 2017 13:37 | // 6, 7
390 * +----------------------------------------------------------+
391 * | Overwrite this file? | // 8
392 * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
393 * +----------------------------------------------------------+
394 * | Overwrite all files? | // 13
395 * | [ ] Don't overwrite with zero length file | // 14
396 * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
397 * +----------------------------------------------------------|
398 * | [ Abort ] | // 20
399 * +----------------------------------------------------------+
402 static replace_action_t
403 overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
405 #define W(i) dlg_widgets[i].widget
406 #define WX(i) W(i)->x
407 #define WCOLS(i) W(i)->cols
409 #define NEW_LABEL(i, text) \
410 W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
412 #define ADD_LABEL(i) \
413 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
414 g->current != NULL ? g->current->data : NULL)
416 #define NEW_BUTTON(i) \
417 W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
418 dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
420 #define ADD_BUTTON(i) \
421 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
423 /* dialog sizes */
424 const int dlg_height = 17;
425 int dlg_width = 60;
427 struct
429 Widget *widget;
430 const char *text;
431 int y;
432 int x;
433 widget_pos_flags_t pos_flags;
434 int value; /* 0 for labels and checkbox */
435 } dlg_widgets[] =
437 /* *INDENT-OFF* */
438 /* 0 - label */
439 { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
440 /* 1 - label - name */
441 { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
442 /* 2 - label - size */
443 { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
444 /* 3 - label - date & time */
445 { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
446 /* 4 - label */
447 { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
448 /* 5 - label - name */
449 { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
450 /* 6 - label - size */
451 { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
452 /* 7 - label - date & time */
453 { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
454 /* --------------------------------------------------- */
455 /* 8 - label */
456 { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
457 /* 9 - button */
458 { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
459 /* 10 - button */
460 { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
461 /* 11 - button */
462 { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
463 /* 12 - button */
464 { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
465 /* --------------------------------------------------- */
466 /* 13 - label */
467 { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
468 /* 14 - checkbox */
469 { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
470 /* 15 - button */
471 { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
472 /* 16 - button */
473 { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
474 /* 17 - button */
475 { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
476 /* 18 - button */
477 { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
478 /* 19 - button */
479 { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
480 /* --------------------------------------------------- */
481 /* 20 - button */
482 { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
483 /* *INDENT-ON* */
486 const int gap = 1;
488 file_op_context_ui_t *ui = ctx->ui;
489 Widget *wd;
490 WGroup *g;
491 const char *title;
493 vfs_path_t *p;
494 char *s1;
495 char s2[BUF_SMALL];
496 int w, bw1, bw2;
497 unsigned short i;
499 gboolean do_append = FALSE, do_reget = FALSE;
500 unsigned long yes_id, no_id;
501 int result;
503 if (mode == Foreground)
504 title = _("File exists");
505 else
506 title = _("Background process: File exists");
508 #ifdef ENABLE_NLS
510 const unsigned short num = G_N_ELEMENTS (dlg_widgets);
512 for (i = 0; i < num; i++)
513 if (dlg_widgets[i].text != NULL)
514 dlg_widgets[i].text = _(dlg_widgets[i].text);
516 #endif /* ENABLE_NLS */
518 /* create widgets to get their real widths */
519 /* new file */
520 NEW_LABEL (0, dlg_widgets[0].text);
521 /* new file name */
522 p = vfs_path_from_str (ui->src_filename);
523 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
524 NEW_LABEL (1, s1);
525 vfs_path_free (p);
526 g_free (s1);
527 /* new file size */
528 size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
529 NEW_LABEL (2, s2);
530 /* new file modification date & time */
531 s1 = (char *) file_date (ui->src_stat->st_mtime);
532 NEW_LABEL (3, s1);
534 /* existing file */
535 NEW_LABEL (4, dlg_widgets[4].text);
536 /* existing file name */
537 p = vfs_path_from_str (ui->tgt_filename);
538 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
539 NEW_LABEL (5, s1);
540 vfs_path_free (p);
541 g_free (s1);
542 /* existing file size */
543 size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
544 NEW_LABEL (6, s2);
545 /* existing file modification date & time */
546 s1 = (char *) file_date (ui->dst_stat->st_mtime);
547 NEW_LABEL (7, s1);
549 /* will "Append" and "Reget" buttons be in the dialog? */
550 do_append = !S_ISDIR (ui->dst_stat->st_mode);
551 do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0
552 && ui->src_stat->st_size > ui->dst_stat->st_size;
554 NEW_LABEL (8, dlg_widgets[8].text);
555 NEW_BUTTON (9);
556 NEW_BUTTON (10);
557 if (do_append)
558 NEW_BUTTON (11);
559 if (do_reget)
560 NEW_BUTTON (12);
562 NEW_LABEL (13, dlg_widgets[13].text);
563 dlg_widgets[14].widget =
564 WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
565 for (i = 15; i <= 20; i++)
566 NEW_BUTTON (i);
568 /* place widgets */
569 dlg_width -= 2 * (2 + gap); /* inside frame */
571 /* perhaps longest line is buttons */
572 bw1 = WCOLS (9) + gap + WCOLS (10);
573 if (do_append)
574 bw1 += gap + WCOLS (11);
575 if (do_reget)
576 bw1 += gap + WCOLS (12);
577 dlg_width = MAX (dlg_width, bw1);
579 bw2 = WCOLS (15);
580 for (i = 16; i <= 19; i++)
581 bw2 += gap + WCOLS (i);
582 dlg_width = MAX (dlg_width, bw2);
584 dlg_width = MAX (dlg_width, WCOLS (8));
585 dlg_width = MAX (dlg_width, WCOLS (13));
586 dlg_width = MAX (dlg_width, WCOLS (14));
588 /* truncate file names */
589 w = WCOLS (0) + gap + WCOLS (1);
590 if (w > dlg_width)
592 WLabel *l = LABEL (W (1));
594 w = dlg_width - gap - WCOLS (0);
595 label_set_text (l, str_trunc (l->text, w));
598 w = WCOLS (4) + gap + WCOLS (5);
599 if (w > dlg_width)
601 WLabel *l = LABEL (W (5));
603 w = dlg_width - gap - WCOLS (4);
604 label_set_text (l, str_trunc (l->text, w));
607 /* real dlalog width */
608 dlg_width += 2 * (2 + gap);
610 WX (1) = WX (0) + WCOLS (0) + gap;
611 WX (5) = WX (4) + WCOLS (4) + gap;
613 /* sizes: right alignment */
614 WX (2) = dlg_width / 2 - WCOLS (2);
615 WX (6) = dlg_width / 2 - WCOLS (6);
617 w = dlg_width - (2 + gap); /* right bound */
619 /* date & time */
620 WX (3) = w - WCOLS (3);
621 WX (7) = w - WCOLS (7);
623 /* buttons: center alignment */
624 WX (9) = dlg_width / 2 - bw1 / 2;
625 WX (10) = WX (9) + WCOLS (9) + gap;
626 if (do_append)
627 WX (11) = WX (10) + WCOLS (10) + gap;
628 if (do_reget)
629 WX (12) = WX (11) + WCOLS (11) + gap;
631 WX (15) = dlg_width / 2 - bw2 / 2;
632 for (i = 16; i <= 19; i++)
633 WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
635 /* TODO: write help (ticket #3970) */
636 ui->replace_dlg =
637 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
638 "[Replace]", title);
639 wd = WIDGET (ui->replace_dlg);
640 g = GROUP (ui->replace_dlg);
642 /* file info */
643 for (i = 0; i <= 7; i++)
644 ADD_LABEL (i);
645 group_add_widget (g, hline_new (W (7)->y - wd->y + 1, -1, -1));
647 /* label & buttons */
648 ADD_LABEL (8); /* Overwrite this file? */
649 yes_id = ADD_BUTTON (9); /* Yes */
650 no_id = ADD_BUTTON (10); /* No */
651 if (do_append)
652 ADD_BUTTON (11); /* Append */
653 if (do_reget)
654 ADD_BUTTON (12); /* Reget */
655 group_add_widget (g, hline_new (W (10)->y - wd->y + 1, -1, -1));
657 /* label & buttons */
658 ADD_LABEL (13); /* Overwrite all files? */
659 group_add_widget (g, dlg_widgets[14].widget);
660 for (i = 15; i <= 19; i++)
661 ADD_BUTTON (i);
662 group_add_widget (g, hline_new (W (19)->y - wd->y + 1, -1, -1));
664 ADD_BUTTON (20); /* Abort */
666 group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
668 result = dlg_run (ui->replace_dlg);
670 if (result != B_CANCEL)
671 ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
673 dlg_destroy (ui->replace_dlg);
675 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
677 #undef ADD_BUTTON
678 #undef NEW_BUTTON
679 #undef ADD_LABEL
680 #undef NEW_LABEL
681 #undef WCOLS
682 #undef WX
683 #undef W
686 /* --------------------------------------------------------------------------------------------- */
688 static gboolean
689 is_wildcarded (const char *p)
691 gboolean escaped = FALSE;
693 for (; *p != '\0'; p++)
695 if (*p == '\\')
697 if (p[1] >= '1' && p[1] <= '9' && !escaped)
698 return TRUE;
699 escaped = !escaped;
701 else
703 if ((*p == '*' || *p == '?') && !escaped)
704 return TRUE;
705 escaped = FALSE;
708 return FALSE;
711 /* --------------------------------------------------------------------------------------------- */
713 static void
714 place_progress_buttons (WDialog * h, gboolean suspended)
716 const size_t i = suspended ? 2 : 1;
717 Widget *w = WIDGET (h);
718 int buttons_width;
720 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
721 buttons_width += progress_buttons[i].len;
722 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
724 progress_buttons[0].w->x = w->x + (w->cols - buttons_width) / 2;
725 progress_buttons[i].w->x = progress_buttons[0].w->x + progress_buttons[0].len + 1;
726 progress_buttons[3].w->x = progress_buttons[i].w->x + progress_buttons[i].len + 1;
729 /* --------------------------------------------------------------------------------------------- */
731 static int
732 progress_button_callback (WButton * button, int action)
734 (void) button;
735 (void) action;
737 /* don't close dialog in any case */
738 return 0;
741 /* --------------------------------------------------------------------------------------------- */
742 /*** public functions ****************************************************************************/
743 /* --------------------------------------------------------------------------------------------- */
745 FileProgressStatus
746 check_progress_buttons (file_op_context_t * ctx)
748 int c;
749 Gpm_Event event;
750 file_op_context_ui_t *ui;
752 if (ctx == NULL || ctx->ui == NULL)
753 return FILE_CONT;
755 ui = ctx->ui;
757 get_event:
758 event.x = -1; /* Don't show the GPM cursor */
759 c = tty_get_event (&event, FALSE, ctx->suspended);
760 if (c == EV_NONE)
761 return FILE_CONT;
763 /* Reinitialize to avoid old values after events other than selecting a button */
764 ui->op_dlg->ret_value = FILE_CONT;
766 dlg_process_event (ui->op_dlg, c, &event);
767 switch (ui->op_dlg->ret_value)
769 case FILE_SKIP:
770 if (ctx->suspended)
772 /* redraw dialog in case of Skip after Suspend */
773 place_progress_buttons (ui->op_dlg, FALSE);
774 dlg_draw (ui->op_dlg);
776 ctx->suspended = FALSE;
777 return FILE_SKIP;
778 case B_CANCEL:
779 case FILE_ABORT:
780 ctx->suspended = FALSE;
781 return FILE_ABORT;
782 case FILE_SUSPEND:
783 ctx->suspended = !ctx->suspended;
784 place_progress_buttons (ui->op_dlg, ctx->suspended);
785 dlg_draw (ui->op_dlg);
786 MC_FALLTHROUGH;
787 default:
788 if (ctx->suspended)
789 goto get_event;
790 return FILE_CONT;
794 /* --------------------------------------------------------------------------------------------- */
795 /* {{{ File progress display routines */
797 void
798 file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
799 filegui_dialog_type_t dialog_type)
801 file_op_context_ui_t *ui;
802 WGroup *g;
803 int buttons_width;
804 int dlg_width = 58, dlg_height = 17;
805 int y = 2, x = 3;
807 if (ctx == NULL || ctx->ui != NULL)
808 return;
810 #ifdef ENABLE_NLS
811 if (progress_buttons[0].len == -1)
813 size_t i;
815 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
816 progress_buttons[i].text = _(progress_buttons[i].text);
818 #endif
820 ctx->dialog_type = dialog_type;
821 ctx->recursive_result = RECURSIVE_YES;
822 ctx->ui = g_new0 (file_op_context_ui_t, 1);
824 ui = ctx->ui;
825 ui->replace_result = REPLACE_YES;
827 ui->op_dlg =
828 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors, NULL,
829 NULL, NULL, op_names[ctx->operation]);
830 g = GROUP (ui->op_dlg);
832 if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
834 ui->showing_eta = with_eta && ctx->progress_totals_computed;
835 ui->showing_bps = with_eta;
837 ui->src_file_label = label_new (y++, x, "");
838 group_add_widget (g, ui->src_file_label);
840 ui->src_file = label_new (y++, x, "");
841 group_add_widget (g, ui->src_file);
843 ui->tgt_file_label = label_new (y++, x, "");
844 group_add_widget (g, ui->tgt_file_label);
846 ui->tgt_file = label_new (y++, x, "");
847 group_add_widget (g, ui->tgt_file);
849 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
850 if (!classic_progressbar && (current_panel == right_panel))
851 ui->progress_file_gauge->from_left_to_right = FALSE;
852 group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
854 ui->progress_file_label = label_new (y++, x, "");
855 group_add_widget (g, ui->progress_file_label);
857 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
859 ui->total_bytes_label = hline_new (y++, -1, -1);
860 group_add_widget (g, ui->total_bytes_label);
862 if (ctx->progress_totals_computed)
864 ui->progress_total_gauge =
865 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
866 if (!classic_progressbar && (current_panel == right_panel))
867 ui->progress_total_gauge->from_left_to_right = FALSE;
868 group_add_widget_autopos (g, ui->progress_total_gauge,
869 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
872 ui->total_files_processed_label = label_new (y++, x, "");
873 group_add_widget (g, ui->total_files_processed_label);
875 ui->time_label = label_new (y++, x, "");
876 group_add_widget (g, ui->time_label);
879 else
881 ui->src_file = label_new (y++, x, "");
882 group_add_widget (g, ui->src_file);
884 ui->total_files_processed_label = label_new (y++, x, "");
885 group_add_widget (g, ui->total_files_processed_label);
888 group_add_widget (g, hline_new (y++, -1, -1));
890 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
891 progress_buttons[0].flags, progress_buttons[0].text,
892 progress_button_callback));
893 if (progress_buttons[0].len == -1)
894 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
896 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
897 progress_buttons[1].flags, progress_buttons[1].text,
898 progress_button_callback));
899 if (progress_buttons[1].len == -1)
900 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
902 if (progress_buttons[2].len == -1)
904 /* create and destroy button to get it length */
905 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
906 progress_buttons[2].flags,
907 progress_buttons[2].text,
908 progress_button_callback));
909 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
910 widget_destroy (progress_buttons[2].w);
912 progress_buttons[2].w = progress_buttons[1].w;
914 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
915 progress_buttons[3].flags, progress_buttons[3].text,
916 NULL));
917 if (progress_buttons[3].len == -1)
918 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
920 group_add_widget (g, progress_buttons[0].w);
921 group_add_widget (g, progress_buttons[1].w);
922 group_add_widget (g, progress_buttons[3].w);
924 buttons_width = 2 +
925 progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
926 progress_buttons[3].len;
928 /* adjust dialog sizes */
929 dlg_set_size (ui->op_dlg, y + 3, MAX (COLS * 2 / 3, buttons_width + 6));
931 place_progress_buttons (ui->op_dlg, FALSE);
933 widget_select (progress_buttons[0].w);
935 /* We will manage the dialog without any help, that's why
936 we have to call dlg_init */
937 dlg_init (ui->op_dlg);
940 /* --------------------------------------------------------------------------------------------- */
942 void
943 file_op_context_destroy_ui (file_op_context_t * ctx)
945 if (ctx != NULL && ctx->ui != NULL)
947 file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
949 dlg_run_done (ui->op_dlg);
950 dlg_destroy (ui->op_dlg);
951 MC_PTR_FREE (ctx->ui);
955 /* --------------------------------------------------------------------------------------------- */
957 show progressbar for file
960 void
961 file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
962 const char *stalled_msg, gboolean force_update)
964 file_op_context_ui_t *ui;
965 char buffer[BUF_TINY];
967 if (!verbose || ctx == NULL || ctx->ui == NULL)
968 return;
970 ui = ctx->ui;
972 if (total == 0)
974 gauge_show (ui->progress_file_gauge, FALSE);
975 return;
978 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
979 gauge_show (ui->progress_file_gauge, TRUE);
981 if (!force_update)
982 return;
984 if (ui->showing_eta && ctx->eta_secs > 0.5)
986 char buffer2[BUF_TINY];
988 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
989 if (ctx->bps == 0)
990 g_snprintf (buffer, sizeof (buffer), "%s %s", buffer2, stalled_msg);
991 else
993 char buffer3[BUF_TINY];
995 file_bps_prepare_for_show (buffer3, ctx->bps);
996 g_snprintf (buffer, sizeof (buffer), "%s (%s) %s", buffer2, buffer3, stalled_msg);
999 else
1000 g_snprintf (buffer, sizeof (buffer), "%s", stalled_msg);
1002 label_set_text (ui->progress_file_label, buffer);
1005 /* --------------------------------------------------------------------------------------------- */
1007 void
1008 file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
1010 char buffer[BUF_TINY];
1011 file_op_context_ui_t *ui;
1013 if (ctx == NULL || ctx->ui == NULL)
1014 return;
1016 ui = ctx->ui;
1018 if (ui->total_files_processed_label == NULL)
1019 return;
1021 if (ctx->progress_totals_computed)
1022 g_snprintf (buffer, sizeof (buffer), _("Files processed: %zu/%zu"), done, total);
1023 else
1024 g_snprintf (buffer, sizeof (buffer), _("Files processed: %zu"), done);
1025 label_set_text (ui->total_files_processed_label, buffer);
1028 /* --------------------------------------------------------------------------------------------- */
1030 void
1031 file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
1032 uintmax_t copied_bytes, gboolean show_summary)
1034 char buffer[BUF_TINY];
1035 char buffer2[BUF_TINY];
1036 char buffer3[BUF_TINY];
1037 file_op_context_ui_t *ui;
1039 if (ctx == NULL || ctx->ui == NULL)
1040 return;
1042 ui = ctx->ui;
1044 if (ui->progress_total_gauge != NULL)
1046 if (ctx->progress_bytes == 0)
1047 gauge_show (ui->progress_total_gauge, FALSE);
1048 else
1050 gauge_set_value (ui->progress_total_gauge, 1024,
1051 (int) (1024 * copied_bytes / ctx->progress_bytes));
1052 gauge_show (ui->progress_total_gauge, TRUE);
1056 if (!show_summary && tctx->bps == 0)
1057 return;
1059 if (ui->time_label != NULL)
1061 struct timeval tv_current;
1062 char buffer4[BUF_TINY];
1064 gettimeofday (&tv_current, NULL);
1065 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
1067 if (ctx->progress_totals_computed)
1069 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
1070 if (tctx->bps == 0)
1071 g_snprintf (buffer, sizeof (buffer), _("Time: %s %s"), buffer2, buffer3);
1072 else
1074 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1075 g_snprintf (buffer, sizeof (buffer), _("Time: %s %s (%s)"), buffer2, buffer3,
1076 buffer4);
1079 else
1081 if (tctx->bps == 0)
1082 g_snprintf (buffer, sizeof (buffer), _("Time: %s"), buffer2);
1083 else
1085 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1086 g_snprintf (buffer, sizeof (buffer), _("Time: %s (%s)"), buffer2, buffer4);
1090 label_set_text (ui->time_label, buffer);
1093 if (ui->total_bytes_label != NULL)
1095 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
1097 if (!ctx->progress_totals_computed)
1098 g_snprintf (buffer, sizeof (buffer), _(" Total: %s "), buffer2);
1099 else
1101 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
1102 g_snprintf (buffer, sizeof (buffer), _(" Total: %s/%s "), buffer2, buffer3);
1105 hline_set_text (ui->total_bytes_label, buffer);
1109 /* }}} */
1111 /* --------------------------------------------------------------------------------------------- */
1113 void
1114 file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath)
1116 file_op_context_ui_t *ui;
1118 if (ctx == NULL || ctx->ui == NULL)
1119 return;
1121 ui = ctx->ui;
1123 if (vpath != NULL)
1125 char *s;
1127 s = vfs_path_tokens_get (vpath, -1, 1);
1128 label_set_text (ui->src_file_label, _("Source"));
1129 label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
1130 g_free (s);
1132 else
1134 label_set_text (ui->src_file_label, "");
1135 label_set_text (ui->src_file, "");
1139 /* --------------------------------------------------------------------------------------------- */
1141 void
1142 file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath)
1144 file_op_context_ui_t *ui;
1146 if (ctx == NULL || ctx->ui == NULL)
1147 return;
1149 ui = ctx->ui;
1151 if (vpath != NULL)
1153 label_set_text (ui->tgt_file_label, _("Target"));
1154 label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1156 else
1158 label_set_text (ui->tgt_file_label, "");
1159 label_set_text (ui->tgt_file, "");
1163 /* --------------------------------------------------------------------------------------------- */
1165 gboolean
1166 file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
1168 static guint64 timestamp = 0;
1169 /* update with 25 FPS rate */
1170 static const guint64 delay = G_USEC_PER_SEC / 25;
1172 gboolean ret;
1174 if (ctx == NULL || ctx->ui == NULL)
1175 return FALSE;
1177 ret = mc_time_elapsed (&timestamp, delay);
1179 if (ret)
1181 file_op_context_ui_t *ui;
1183 ui = ctx->ui;
1185 if (ui->src_file_label != NULL)
1186 label_set_text (ui->src_file_label, _("Deleting"));
1188 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1191 if (count != NULL)
1192 (*count)++;
1194 return ret;
1197 /* --------------------------------------------------------------------------------------------- */
1199 FileProgressStatus
1200 file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode,
1201 const char *src, struct stat * src_stat,
1202 const char *dst, struct stat * dst_stat)
1204 file_op_context_ui_t *ui;
1205 FileProgressStatus replace_with_zero;
1207 if (ctx == NULL || ctx->ui == NULL)
1208 return FILE_CONT;
1210 ui = ctx->ui;
1212 if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1213 || ui->replace_result == REPLACE_APPEND)
1215 ui->src_filename = src;
1216 ui->src_stat = src_stat;
1217 ui->tgt_filename = dst;
1218 ui->dst_stat = dst_stat;
1219 ui->replace_result = overwrite_query_dialog (ctx, mode);
1222 replace_with_zero = (src_stat->st_size == 0
1223 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1225 switch (ui->replace_result)
1227 case REPLACE_OLDER:
1228 do_refresh ();
1229 if (src_stat->st_mtime > dst_stat->st_mtime)
1230 return replace_with_zero;
1231 else
1232 return FILE_SKIP;
1234 case REPLACE_SIZE:
1235 do_refresh ();
1236 if (src_stat->st_size == dst_stat->st_size)
1237 return FILE_SKIP;
1238 else
1239 return replace_with_zero;
1241 case REPLACE_SMALLER:
1242 do_refresh ();
1243 if (src_stat->st_size > dst_stat->st_size)
1244 return FILE_CONT;
1245 else
1246 return FILE_SKIP;
1248 case REPLACE_ALL:
1249 do_refresh ();
1250 return replace_with_zero;
1252 case REPLACE_REGET:
1253 /* Careful: we fall through and set do_append */
1254 ctx->do_reget = dst_stat->st_size;
1255 MC_FALLTHROUGH;
1257 case REPLACE_APPEND:
1258 ctx->do_append = TRUE;
1259 MC_FALLTHROUGH;
1261 case REPLACE_YES:
1262 do_refresh ();
1263 return FILE_CONT;
1265 case REPLACE_NO:
1266 case REPLACE_NONE:
1267 do_refresh ();
1268 return FILE_SKIP;
1270 case REPLACE_ABORT:
1271 default:
1272 return FILE_ABORT;
1276 /* --------------------------------------------------------------------------------------------- */
1278 char *
1279 file_mask_dialog (file_op_context_t * ctx, FileOperation operation,
1280 gboolean only_one,
1281 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1283 size_t fmd_xlen;
1284 vfs_path_t *vpath;
1285 gboolean source_easy_patterns = easy_patterns;
1286 char fmd_buf[BUF_MEDIUM];
1287 char *dest_dir, *tmp;
1288 char *def_text_secure;
1290 if (ctx == NULL)
1291 return NULL;
1293 /* unselect checkbox if target filesystem doesn't support attributes */
1294 ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1295 ctx->stable_symlinks = FALSE;
1296 *do_bg = FALSE;
1298 /* filter out a possible password from def_text */
1299 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1300 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1301 vfs_path_free (vpath);
1303 if (source_easy_patterns)
1304 def_text_secure = strutils_glob_escape (tmp);
1305 else
1306 def_text_secure = strutils_regex_escape (tmp);
1307 g_free (tmp);
1309 if (only_one)
1311 int format_len, text_len;
1312 int max_len;
1314 format_len = str_term_width1 (format);
1315 text_len = str_term_width1 (text);
1316 max_len = COLS - 2 - 6;
1318 if (format_len + text_len <= max_len)
1320 fmd_xlen = format_len + text_len + 6;
1321 fmd_xlen = MAX (fmd_xlen, 68);
1323 else
1325 text = str_trunc ((const char *) text, max_len - format_len);
1326 fmd_xlen = max_len + 6;
1329 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1331 else
1333 fmd_xlen = COLS * 2 / 3;
1334 fmd_xlen = MAX (fmd_xlen, 68);
1335 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1339 char *source_mask, *orig_mask;
1340 int val;
1341 struct stat buf;
1343 quick_widget_t quick_widgets[] = {
1344 /* *INDENT-OFF* */
1345 QUICK_LABELED_INPUT (fmd_buf, input_label_above,
1346 easy_patterns ? "*" : "^(.*)$", "input-def", &source_mask,
1347 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1348 QUICK_START_COLUMNS,
1349 QUICK_SEPARATOR (FALSE),
1350 QUICK_NEXT_COLUMN,
1351 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1352 QUICK_STOP_COLUMNS,
1353 QUICK_LABELED_INPUT (N_("to:"), input_label_above,
1354 def_text_secure, "input2", &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1355 QUICK_SEPARATOR (TRUE),
1356 QUICK_START_COLUMNS,
1357 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1358 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1359 QUICK_NEXT_COLUMN,
1360 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1361 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1362 QUICK_STOP_COLUMNS,
1363 QUICK_START_BUTTONS (TRUE, TRUE),
1364 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1365 #ifdef ENABLE_BACKGROUND
1366 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1367 #endif /* ENABLE_BACKGROUND */
1368 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1369 QUICK_END
1370 /* *INDENT-ON* */
1373 quick_dialog_t qdlg = {
1374 -1, -1, fmd_xlen,
1375 op_names[operation], "[Mask Copy/Rename]",
1376 quick_widgets, NULL, NULL
1379 ask_file_mask:
1380 val = quick_dialog_skip (&qdlg, 4);
1382 if (val == B_CANCEL)
1384 g_free (def_text_secure);
1385 return NULL;
1388 ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1390 if (ctx->op_preserve)
1392 ctx->preserve = TRUE;
1393 ctx->umask_kill = 0777777;
1394 ctx->preserve_uidgid = (geteuid () == 0);
1396 else
1398 mode_t i2;
1400 ctx->preserve = ctx->preserve_uidgid = FALSE;
1401 i2 = umask (0);
1402 umask (i2);
1403 ctx->umask_kill = i2 ^ 0777777;
1406 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1408 g_free (def_text_secure);
1409 g_free (source_mask);
1410 g_free (dest_dir);
1411 return NULL;
1414 ctx->search_handle = mc_search_new (source_mask, NULL);
1416 if (ctx->search_handle == NULL)
1418 message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1419 g_free (dest_dir);
1420 g_free (source_mask);
1421 goto ask_file_mask;
1424 g_free (def_text_secure);
1425 g_free (source_mask);
1427 ctx->search_handle->is_case_sensitive = TRUE;
1428 if (source_easy_patterns)
1429 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1430 else
1431 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1433 tmp = dest_dir;
1434 dest_dir = tilde_expand (tmp);
1435 g_free (tmp);
1436 vpath = vfs_path_from_str (dest_dir);
1438 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1439 if (ctx->dest_mask == NULL)
1440 ctx->dest_mask = dest_dir;
1441 else
1442 ctx->dest_mask++;
1444 orig_mask = ctx->dest_mask;
1446 if (*ctx->dest_mask == '\0'
1447 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1448 && (!only_one
1449 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1450 || (ctx->dive_into_subdirs
1451 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1452 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1453 ctx->dest_mask = g_strdup ("\\0");
1454 else
1456 ctx->dest_mask = g_strdup (ctx->dest_mask);
1457 *orig_mask = '\0';
1460 if (*dest_dir == '\0')
1462 g_free (dest_dir);
1463 dest_dir = g_strdup ("./");
1466 vfs_path_free (vpath);
1468 if (val == B_USER)
1469 *do_bg = TRUE;
1472 return dest_dir;
1475 /* --------------------------------------------------------------------------------------------- */