(file_eta_prepare_for_show): join checks of eta_secs.
[midnight-commander.git] / src / filemanager / filegui.c
blob907d08f77a4df3edb4d41ca36de8f2f165df06d5
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"
158 #include "fileopctx.h" /* FILE_CONT */
160 #include "filegui.h"
162 /* }}} */
164 /*** global variables ****************************************************************************/
166 gboolean classic_progressbar = TRUE;
168 /*** file scope macro definitions ****************************************************************/
170 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
172 /*** file scope type declarations ****************************************************************/
174 /* *INDENT-OFF* */
175 typedef enum {
176 MSDOS_SUPER_MAGIC = 0x4d44,
177 NTFS_SB_MAGIC = 0x5346544e,
178 FUSE_MAGIC = 0x65735546,
179 PROC_SUPER_MAGIC = 0x9fa0,
180 SMB_SUPER_MAGIC = 0x517B,
181 NCP_SUPER_MAGIC = 0x564c,
182 USBDEVICE_SUPER_MAGIC = 0x9fa2
183 } filegui_nonattrs_fs_t;
184 /* *INDENT-ON* */
186 /* Used for button result values */
187 typedef enum
189 REPLACE_YES = B_USER,
190 REPLACE_NO,
191 REPLACE_APPEND,
192 REPLACE_REGET,
193 REPLACE_ALL,
194 REPLACE_OLDER,
195 REPLACE_NONE,
196 REPLACE_SMALLER,
197 REPLACE_SIZE,
198 REPLACE_ABORT
199 } replace_action_t;
201 /* This structure describes the UI and internal data required by a file
202 * operation context.
204 typedef struct
206 /* ETA and bps */
207 gboolean showing_eta;
208 gboolean showing_bps;
210 /* Dialog and widgets for the operation progress window */
211 WDialog *op_dlg;
212 /* Source file: label and name */
213 WLabel *src_file_label;
214 WLabel *src_file;
215 /* Target file: label and name */
216 WLabel *tgt_file_label;
217 WLabel *tgt_file;
219 WGauge *progress_file_gauge;
220 WLabel *progress_file_label;
222 WGauge *progress_total_gauge;
224 WLabel *total_files_processed_label;
225 WLabel *time_label;
226 WHLine *total_bytes_label;
228 /* Query replace dialog */
229 WDialog *replace_dlg;
230 const char *src_filename;
231 const char *tgt_filename;
232 replace_action_t replace_result;
233 gboolean dont_overwrite_with_zero;
235 struct stat *src_stat, *dst_stat;
236 } file_op_context_ui_t;
238 /*** forward declarations (file scope functions) *************************************************/
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[] = {
250 /* *INDENT-OFF* */
251 { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
252 { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
253 { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
254 { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
255 /* *INDENT-ON* */
258 /* --------------------------------------------------------------------------------------------- */
259 /*** file scope functions ************************************************************************/
260 /* --------------------------------------------------------------------------------------------- */
262 /* Return true if statvfs works. This is false for statvfs on systems
263 with GNU libc on Linux kernels before 2.6.36, which stats all
264 preceding entries in /proc/mounts; that makes df hang if even one
265 of the corresponding file systems is hard-mounted but not available. */
267 #if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
268 static int
269 statvfs_works (void)
271 #if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
272 return 1;
273 #else
274 static int statvfs_works_cache = -1;
275 struct utsname name;
277 if (statvfs_works_cache < 0)
278 statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
279 return statvfs_works_cache;
280 #endif
282 #endif
284 /* --------------------------------------------------------------------------------------------- */
286 static gboolean
287 filegui__check_attrs_on_fs (const char *fs_path)
289 STRUCT_STATVFS stfs;
291 #if USE_STATVFS && defined(STAT_STATVFS)
292 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
293 return TRUE;
294 #else
295 if (STATFS (fs_path, &stfs) != 0)
296 return TRUE;
297 #endif
299 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
300 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
301 switch ((filegui_nonattrs_fs_t) stfs.f_type)
303 case MSDOS_SUPER_MAGIC:
304 case NTFS_SB_MAGIC:
305 case PROC_SUPER_MAGIC:
306 case SMB_SUPER_MAGIC:
307 case NCP_SUPER_MAGIC:
308 case USBDEVICE_SUPER_MAGIC:
309 return FALSE;
310 default:
311 break;
313 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
314 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
315 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
316 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
317 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
318 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
319 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
320 return FALSE;
321 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
322 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
323 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
324 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
325 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
326 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
327 return FALSE;
328 #endif
330 return TRUE;
333 /* --------------------------------------------------------------------------------------------- */
335 static void
336 file_frmt_time (char *buffer, double eta_secs)
338 int eta_hours, eta_mins, eta_s;
340 eta_hours = (int) (eta_secs / (60 * 60));
341 eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
342 eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
343 g_snprintf (buffer, BUF_TINY, _("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
346 /* --------------------------------------------------------------------------------------------- */
348 static void
349 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
351 char _fmt_buff[BUF_TINY];
353 if (eta_secs <= 0.5)
355 if (!always_show)
357 *buffer = '\0';
358 return;
361 eta_secs = 1.0;
364 file_frmt_time (_fmt_buff, eta_secs);
365 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
368 /* --------------------------------------------------------------------------------------------- */
370 static void
371 file_bps_prepare_for_show (char *buffer, long bps)
373 if (bps > 1024 * 1024)
374 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
375 else if (bps > 1024)
376 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
377 else if (bps > 1)
378 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
379 else
380 *buffer = '\0';
383 /* --------------------------------------------------------------------------------------------- */
385 static cb_ret_t
386 file_ui_op_dlg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
388 switch (msg)
390 case MSG_ACTION:
391 /* Do not close the dialog because the query dialog will be shown */
392 if (parm == CK_Cancel)
394 DIALOG (w)->ret_value = FILE_ABORT; /* for check_progress_buttons() */
395 return MSG_HANDLED;
397 return MSG_NOT_HANDLED;
399 default:
400 return dlg_default_callback (w, sender, msg, parm, data);
404 /* --------------------------------------------------------------------------------------------- */
406 /* The dialog layout:
408 * +---------------------- File exists -----------------------+
409 * | New : /path/to/original_file_name | // 0, 1
410 * | 1234567 feb 4 2017 13:38 | // 2, 3
411 * | Existing: /path/to/target_file_name | // 4, 5
412 * | 1234567890 feb 4 2017 13:37 | // 6, 7
413 * +----------------------------------------------------------+
414 * | Overwrite this file? | // 8
415 * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
416 * +----------------------------------------------------------+
417 * | Overwrite all files? | // 13
418 * | [ ] Don't overwrite with zero length file | // 14
419 * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
420 * +----------------------------------------------------------|
421 * | [ Abort ] | // 20
422 * +----------------------------------------------------------+
425 static replace_action_t
426 overwrite_query_dialog (file_op_context_t *ctx, enum OperationMode mode)
428 #define W(i) dlg_widgets[i].widget
429 #define WX(i) W(i)->rect.x
430 #define WY(i) W(i)->rect.y
431 #define WCOLS(i) W(i)->rect.cols
433 #define NEW_LABEL(i, text) \
434 W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
436 #define ADD_LABEL(i) \
437 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
438 g->current != NULL ? g->current->data : NULL)
440 #define NEW_BUTTON(i) \
441 W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
442 dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
444 #define ADD_BUTTON(i) \
445 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
447 /* dialog sizes */
448 const int dlg_height = 17;
449 int dlg_width = 60;
451 struct
453 Widget *widget;
454 const char *text;
455 int y;
456 int x;
457 widget_pos_flags_t pos_flags;
458 int value; /* 0 for labels and checkbox */
459 } dlg_widgets[] = {
460 /* *INDENT-OFF* */
461 /* 0 - label */
462 { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
463 /* 1 - label - name */
464 { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
465 /* 2 - label - size */
466 { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
467 /* 3 - label - date & time */
468 { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
469 /* 4 - label */
470 { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
471 /* 5 - label - name */
472 { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
473 /* 6 - label - size */
474 { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
475 /* 7 - label - date & time */
476 { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
477 /* --------------------------------------------------- */
478 /* 8 - label */
479 { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
480 /* 9 - button */
481 { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
482 /* 10 - button */
483 { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
484 /* 11 - button */
485 { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
486 /* 12 - button */
487 { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
488 /* --------------------------------------------------- */
489 /* 13 - label */
490 { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
491 /* 14 - checkbox */
492 { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
493 /* 15 - button */
494 { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
495 /* 16 - button */
496 { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
497 /* 17 - button */
498 { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
499 /* 18 - button */
500 { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
501 /* 19 - button */
502 { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
503 /* --------------------------------------------------- */
504 /* 20 - button */
505 { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
506 /* *INDENT-ON* */
509 const int gap = 1;
511 file_op_context_ui_t *ui = ctx->ui;
512 Widget *wd;
513 WGroup *g;
514 const char *title;
516 vfs_path_t *p;
517 char *s1;
518 const char *cs1;
519 char s2[BUF_SMALL];
520 int w, bw1, bw2;
521 unsigned short i;
523 gboolean do_append = FALSE, do_reget = FALSE;
524 unsigned long yes_id, no_id;
525 int result;
527 if (mode == Foreground)
528 title = _("File exists");
529 else
530 title = _("Background process: File exists");
532 #ifdef ENABLE_NLS
534 const unsigned short num = G_N_ELEMENTS (dlg_widgets);
536 for (i = 0; i < num; i++)
537 if (dlg_widgets[i].text != NULL)
538 dlg_widgets[i].text = _(dlg_widgets[i].text);
540 #endif /* ENABLE_NLS */
542 /* create widgets to get their real widths */
543 /* new file */
544 NEW_LABEL (0, dlg_widgets[0].text);
545 /* new file name */
546 p = vfs_path_from_str (ui->src_filename);
547 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
548 NEW_LABEL (1, s1);
549 vfs_path_free (p, TRUE);
550 g_free (s1);
551 /* new file size */
552 size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
553 NEW_LABEL (2, s2);
554 /* new file modification date & time */
555 cs1 = file_date (ui->src_stat->st_mtime);
556 NEW_LABEL (3, cs1);
558 /* existing file */
559 NEW_LABEL (4, dlg_widgets[4].text);
560 /* existing file name */
561 p = vfs_path_from_str (ui->tgt_filename);
562 s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
563 NEW_LABEL (5, s1);
564 vfs_path_free (p, TRUE);
565 g_free (s1);
566 /* existing file size */
567 size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
568 NEW_LABEL (6, s2);
569 /* existing file modification date & time */
570 cs1 = file_date (ui->dst_stat->st_mtime);
571 NEW_LABEL (7, cs1);
573 /* will "Append" and "Reget" buttons be in the dialog? */
574 do_append = !S_ISDIR (ui->dst_stat->st_mode);
575 do_reget = do_append && ui->dst_stat->st_size != 0
576 && ui->src_stat->st_size > ui->dst_stat->st_size;
578 NEW_LABEL (8, dlg_widgets[8].text);
579 NEW_BUTTON (9);
580 NEW_BUTTON (10);
581 if (do_append)
582 NEW_BUTTON (11);
583 if (do_reget)
584 NEW_BUTTON (12);
586 NEW_LABEL (13, dlg_widgets[13].text);
587 dlg_widgets[14].widget =
588 WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
589 for (i = 15; i <= 20; i++)
590 NEW_BUTTON (i);
592 /* place widgets */
593 dlg_width -= 2 * (2 + gap); /* inside frame */
595 /* perhaps longest line is buttons */
596 bw1 = WCOLS (9) + gap + WCOLS (10);
597 if (do_append)
598 bw1 += gap + WCOLS (11);
599 if (do_reget)
600 bw1 += gap + WCOLS (12);
601 dlg_width = MAX (dlg_width, bw1);
603 bw2 = WCOLS (15);
604 for (i = 16; i <= 19; i++)
605 bw2 += gap + WCOLS (i);
606 dlg_width = MAX (dlg_width, bw2);
608 dlg_width = MAX (dlg_width, WCOLS (8));
609 dlg_width = MAX (dlg_width, WCOLS (13));
610 dlg_width = MAX (dlg_width, WCOLS (14));
612 /* truncate file names */
613 w = WCOLS (0) + gap + WCOLS (1);
614 if (w > dlg_width)
616 WLabel *l = LABEL (W (1));
618 w = dlg_width - gap - WCOLS (0);
619 label_set_text (l, str_trunc (l->text, w));
622 w = WCOLS (4) + gap + WCOLS (5);
623 if (w > dlg_width)
625 WLabel *l = LABEL (W (5));
627 w = dlg_width - gap - WCOLS (4);
628 label_set_text (l, str_trunc (l->text, w));
631 /* real dlalog width */
632 dlg_width += 2 * (2 + gap);
634 WX (1) = WX (0) + WCOLS (0) + gap;
635 WX (5) = WX (4) + WCOLS (4) + gap;
637 /* sizes: right alignment */
638 WX (2) = dlg_width / 2 - WCOLS (2);
639 WX (6) = dlg_width / 2 - WCOLS (6);
641 w = dlg_width - (2 + gap); /* right bound */
643 /* date & time */
644 WX (3) = w - WCOLS (3);
645 WX (7) = w - WCOLS (7);
647 /* buttons: center alignment */
648 WX (9) = dlg_width / 2 - bw1 / 2;
649 WX (10) = WX (9) + WCOLS (9) + gap;
650 if (do_append)
651 WX (11) = WX (10) + WCOLS (10) + gap;
652 if (do_reget)
653 WX (12) = WX (11) + WCOLS (11) + gap;
655 WX (15) = dlg_width / 2 - bw2 / 2;
656 for (i = 16; i <= 19; i++)
657 WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
659 /* TODO: write help (ticket #3970) */
660 ui->replace_dlg =
661 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
662 "[Replace]", title);
663 wd = WIDGET (ui->replace_dlg);
664 g = GROUP (ui->replace_dlg);
666 /* file info */
667 for (i = 0; i <= 7; i++)
668 ADD_LABEL (i);
669 group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
671 /* label & buttons */
672 ADD_LABEL (8); /* Overwrite this file? */
673 yes_id = ADD_BUTTON (9); /* Yes */
674 no_id = ADD_BUTTON (10); /* No */
675 if (do_append)
676 ADD_BUTTON (11); /* Append */
677 if (do_reget)
678 ADD_BUTTON (12); /* Reget */
679 group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
681 /* label & buttons */
682 ADD_LABEL (13); /* Overwrite all files? */
683 group_add_widget (g, dlg_widgets[14].widget);
684 for (i = 15; i <= 19; i++)
685 ADD_BUTTON (i);
686 group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
688 ADD_BUTTON (20); /* Abort */
690 group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
692 result = dlg_run (ui->replace_dlg);
694 if (result != B_CANCEL)
695 ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
697 widget_destroy (wd);
699 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
701 #undef ADD_BUTTON
702 #undef NEW_BUTTON
703 #undef ADD_LABEL
704 #undef NEW_LABEL
705 #undef WCOLS
706 #undef WX
707 #undef W
710 /* --------------------------------------------------------------------------------------------- */
712 static gboolean
713 is_wildcarded (const char *p)
715 gboolean escaped = FALSE;
717 for (; *p != '\0'; p++)
719 if (*p == '\\')
721 if (p[1] >= '1' && p[1] <= '9' && !escaped)
722 return TRUE;
723 escaped = !escaped;
725 else
727 if ((*p == '*' || *p == '?') && !escaped)
728 return TRUE;
729 escaped = FALSE;
732 return FALSE;
735 /* --------------------------------------------------------------------------------------------- */
737 static void
738 place_progress_buttons (WDialog *h, gboolean suspended)
740 const size_t i = suspended ? 2 : 1;
741 Widget *w = WIDGET (h);
742 int buttons_width;
744 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
745 buttons_width += progress_buttons[i].len;
746 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
748 progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
749 progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
750 progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
753 /* --------------------------------------------------------------------------------------------- */
755 static int
756 progress_button_callback (WButton *button, int action)
758 (void) button;
759 (void) action;
761 /* don't close dialog in any case */
762 return 0;
765 /* --------------------------------------------------------------------------------------------- */
766 /*** public functions ****************************************************************************/
767 /* --------------------------------------------------------------------------------------------- */
769 FileProgressStatus
770 check_progress_buttons (file_op_context_t *ctx)
772 int c;
773 Gpm_Event event;
774 file_op_context_ui_t *ui;
776 if (ctx == NULL || ctx->ui == NULL)
777 return FILE_CONT;
779 ui = ctx->ui;
781 get_event:
782 event.x = -1; /* Don't show the GPM cursor */
783 c = tty_get_event (&event, FALSE, ctx->suspended);
784 if (c == EV_NONE)
785 return FILE_CONT;
787 /* Reinitialize to avoid old values after events other than selecting a button */
788 ui->op_dlg->ret_value = FILE_CONT;
790 dlg_process_event (ui->op_dlg, c, &event);
791 switch (ui->op_dlg->ret_value)
793 case FILE_SKIP:
794 if (ctx->suspended)
796 /* redraw dialog in case of Skip after Suspend */
797 place_progress_buttons (ui->op_dlg, FALSE);
798 widget_draw (WIDGET (ui->op_dlg));
800 ctx->suspended = FALSE;
801 return FILE_SKIP;
802 case B_CANCEL:
803 case FILE_ABORT:
804 ctx->suspended = FALSE;
805 return FILE_ABORT;
806 case FILE_SUSPEND:
807 ctx->suspended = !ctx->suspended;
808 place_progress_buttons (ui->op_dlg, ctx->suspended);
809 widget_draw (WIDGET (ui->op_dlg));
810 MC_FALLTHROUGH;
811 default:
812 if (ctx->suspended)
813 goto get_event;
814 return FILE_CONT;
818 /* --------------------------------------------------------------------------------------------- */
819 /* {{{ File progress display routines */
821 void
822 file_op_context_create_ui (file_op_context_t *ctx, gboolean with_eta,
823 filegui_dialog_type_t dialog_type)
825 file_op_context_ui_t *ui;
826 Widget *w;
827 WGroup *g;
828 int buttons_width;
829 int dlg_width = 58, dlg_height = 17;
830 int y = 2, x = 3;
831 WRect r;
833 if (ctx == NULL || ctx->ui != NULL)
834 return;
836 #ifdef ENABLE_NLS
837 if (progress_buttons[0].len == -1)
839 size_t i;
841 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
842 progress_buttons[i].text = _(progress_buttons[i].text);
844 #endif
846 ctx->dialog_type = dialog_type;
847 ctx->recursive_result = RECURSIVE_YES;
848 ctx->ui = g_new0 (file_op_context_ui_t, 1);
850 ui = ctx->ui;
851 ui->replace_result = REPLACE_YES;
853 ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
854 file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
855 w = WIDGET (ui->op_dlg);
856 g = GROUP (ui->op_dlg);
858 if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
860 ui->showing_eta = with_eta && ctx->progress_totals_computed;
861 ui->showing_bps = with_eta;
863 ui->src_file_label = label_new (y++, x, NULL);
864 group_add_widget (g, ui->src_file_label);
866 ui->src_file = label_new (y++, x, NULL);
867 group_add_widget (g, ui->src_file);
869 ui->tgt_file_label = label_new (y++, x, NULL);
870 group_add_widget (g, ui->tgt_file_label);
872 ui->tgt_file = label_new (y++, x, NULL);
873 group_add_widget (g, ui->tgt_file);
875 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
876 if (!classic_progressbar && (current_panel == right_panel))
877 ui->progress_file_gauge->from_left_to_right = FALSE;
878 group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
880 ui->progress_file_label = label_new (y++, x, NULL);
881 group_add_widget (g, ui->progress_file_label);
883 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
885 ui->total_bytes_label = hline_new (y++, -1, -1);
886 group_add_widget (g, ui->total_bytes_label);
888 if (ctx->progress_totals_computed)
890 ui->progress_total_gauge =
891 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
892 if (!classic_progressbar && (current_panel == right_panel))
893 ui->progress_total_gauge->from_left_to_right = FALSE;
894 group_add_widget_autopos (g, ui->progress_total_gauge,
895 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
898 ui->total_files_processed_label = label_new (y++, x, NULL);
899 group_add_widget (g, ui->total_files_processed_label);
901 ui->time_label = label_new (y++, x, NULL);
902 group_add_widget (g, ui->time_label);
905 else
907 ui->src_file = label_new (y++, x, NULL);
908 group_add_widget (g, ui->src_file);
910 ui->total_files_processed_label = label_new (y++, x, NULL);
911 group_add_widget (g, ui->total_files_processed_label);
914 group_add_widget (g, hline_new (y++, -1, -1));
916 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
917 progress_buttons[0].flags, progress_buttons[0].text,
918 progress_button_callback));
919 if (progress_buttons[0].len == -1)
920 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
922 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
923 progress_buttons[1].flags, progress_buttons[1].text,
924 progress_button_callback));
925 if (progress_buttons[1].len == -1)
926 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
928 if (progress_buttons[2].len == -1)
930 /* create and destroy button to get it length */
931 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
932 progress_buttons[2].flags,
933 progress_buttons[2].text,
934 progress_button_callback));
935 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
936 widget_destroy (progress_buttons[2].w);
938 progress_buttons[2].w = progress_buttons[1].w;
940 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
941 progress_buttons[3].flags, progress_buttons[3].text,
942 progress_button_callback));
943 if (progress_buttons[3].len == -1)
944 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
946 group_add_widget (g, progress_buttons[0].w);
947 group_add_widget (g, progress_buttons[1].w);
948 group_add_widget (g, progress_buttons[3].w);
950 buttons_width = 2 +
951 progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
952 progress_buttons[3].len;
954 /* adjust dialog sizes */
955 r = w->rect;
956 r.lines = y + 3;
957 r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
958 widget_set_size_rect (w, &r);
960 place_progress_buttons (ui->op_dlg, FALSE);
962 widget_select (progress_buttons[0].w);
964 /* We will manage the dialog without any help, that's why
965 we have to call dlg_init */
966 dlg_init (ui->op_dlg);
969 /* --------------------------------------------------------------------------------------------- */
971 void
972 file_op_context_destroy_ui (file_op_context_t *ctx)
974 if (ctx != NULL && ctx->ui != NULL)
976 file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
978 dlg_run_done (ui->op_dlg);
979 widget_destroy (WIDGET (ui->op_dlg));
980 MC_PTR_FREE (ctx->ui);
984 /* --------------------------------------------------------------------------------------------- */
986 show progressbar for file
989 void
990 file_progress_show (file_op_context_t *ctx, off_t done, off_t total,
991 const char *stalled_msg, gboolean force_update)
993 file_op_context_ui_t *ui;
995 if (ctx == NULL || ctx->ui == NULL)
996 return;
998 ui = ctx->ui;
1000 if (total == 0)
1002 gauge_show (ui->progress_file_gauge, FALSE);
1003 return;
1006 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
1007 gauge_show (ui->progress_file_gauge, TRUE);
1009 if (!force_update)
1010 return;
1012 if (!ui->showing_eta || ctx->eta_secs <= 0.5)
1013 label_set_text (ui->progress_file_label, stalled_msg);
1014 else
1016 char buffer2[BUF_TINY];
1018 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
1019 if (ctx->bps == 0)
1020 label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1021 else
1023 char buffer3[BUF_TINY];
1025 file_bps_prepare_for_show (buffer3, ctx->bps);
1026 label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1032 /* --------------------------------------------------------------------------------------------- */
1034 void
1035 file_progress_show_count (file_op_context_t *ctx, size_t done, size_t total)
1037 file_op_context_ui_t *ui;
1039 if (ctx == NULL || ctx->ui == NULL)
1040 return;
1042 ui = ctx->ui;
1044 if (ui->total_files_processed_label == NULL)
1045 return;
1047 if (ctx->progress_totals_computed)
1048 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"), done,
1049 total);
1050 else
1051 label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done);
1054 /* --------------------------------------------------------------------------------------------- */
1056 void
1057 file_progress_show_total (file_op_total_context_t *tctx, file_op_context_t *ctx,
1058 uintmax_t copied_bytes, gboolean show_summary)
1060 char buffer2[BUF_TINY];
1061 char buffer3[BUF_TINY];
1062 file_op_context_ui_t *ui;
1064 if (ctx == NULL || ctx->ui == NULL)
1065 return;
1067 ui = ctx->ui;
1069 if (ui->progress_total_gauge != NULL)
1071 if (ctx->progress_bytes == 0)
1072 gauge_show (ui->progress_total_gauge, FALSE);
1073 else
1075 gauge_set_value (ui->progress_total_gauge, 1024,
1076 (int) (1024 * copied_bytes / ctx->progress_bytes));
1077 gauge_show (ui->progress_total_gauge, TRUE);
1081 if (!show_summary && tctx->bps == 0)
1082 return;
1084 if (ui->time_label != NULL)
1086 gint64 tv_current;
1087 char buffer4[BUF_TINY];
1089 tv_current = g_get_monotonic_time ();
1090 file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC);
1092 if (ctx->progress_totals_computed)
1094 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
1095 if (tctx->bps == 0)
1096 label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
1097 else
1099 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1100 label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1103 else
1105 if (tctx->bps == 0)
1106 label_set_textv (ui->time_label, _("Time: %s"), buffer2);
1107 else
1109 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1110 label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
1115 if (ui->total_bytes_label != NULL)
1117 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
1119 if (!ctx->progress_totals_computed)
1120 hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
1121 else
1123 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
1124 hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
1129 /* }}} */
1131 /* --------------------------------------------------------------------------------------------- */
1133 void
1134 file_progress_show_source (file_op_context_t *ctx, const vfs_path_t *vpath)
1136 file_op_context_ui_t *ui;
1138 if (ctx == NULL || ctx->ui == NULL)
1139 return;
1141 ui = ctx->ui;
1143 if (vpath != NULL)
1145 label_set_text (ui->src_file_label, _("Source"));
1146 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1148 else
1150 label_set_text (ui->src_file_label, NULL);
1151 label_set_text (ui->src_file, NULL);
1155 /* --------------------------------------------------------------------------------------------- */
1157 void
1158 file_progress_show_target (file_op_context_t *ctx, const vfs_path_t *vpath)
1160 file_op_context_ui_t *ui;
1162 if (ctx == NULL || ctx->ui == NULL)
1163 return;
1165 ui = ctx->ui;
1167 if (vpath != NULL)
1169 label_set_text (ui->tgt_file_label, _("Target"));
1170 label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1172 else
1174 label_set_text (ui->tgt_file_label, NULL);
1175 label_set_text (ui->tgt_file, NULL);
1179 /* --------------------------------------------------------------------------------------------- */
1181 gboolean
1182 file_progress_show_deleting (file_op_context_t *ctx, const vfs_path_t *vpath, size_t *count)
1184 static gint64 timestamp = 0;
1185 /* update with 25 FPS rate */
1186 static const gint64 delay = G_USEC_PER_SEC / 25;
1188 gboolean ret;
1190 if (ctx == NULL || ctx->ui == NULL)
1191 return FALSE;
1193 ret = mc_time_elapsed (&timestamp, delay);
1195 if (ret)
1197 file_op_context_ui_t *ui;
1198 const char *s;
1200 ui = ctx->ui;
1202 if (ui->src_file_label != NULL)
1203 label_set_text (ui->src_file_label, _("Deleting"));
1205 s = vfs_path_as_str (vpath);
1206 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1209 if (count != NULL)
1210 (*count)++;
1212 return ret;
1215 /* --------------------------------------------------------------------------------------------- */
1217 FileProgressStatus
1218 file_progress_real_query_replace (file_op_context_t *ctx, enum OperationMode mode,
1219 const char *src, struct stat *src_stat,
1220 const char *dst, struct stat *dst_stat)
1222 file_op_context_ui_t *ui;
1223 FileProgressStatus replace_with_zero;
1225 if (ctx == NULL || ctx->ui == NULL)
1226 return FILE_CONT;
1228 ui = ctx->ui;
1230 if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1231 || ui->replace_result == REPLACE_APPEND)
1233 ui->src_filename = src;
1234 ui->src_stat = src_stat;
1235 ui->tgt_filename = dst;
1236 ui->dst_stat = dst_stat;
1237 ui->replace_result = overwrite_query_dialog (ctx, mode);
1240 replace_with_zero = (src_stat->st_size == 0
1241 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1243 switch (ui->replace_result)
1245 case REPLACE_OLDER:
1246 do_refresh ();
1247 if (src_stat->st_mtime > dst_stat->st_mtime)
1248 return replace_with_zero;
1249 else
1250 return FILE_SKIP;
1252 case REPLACE_SIZE:
1253 do_refresh ();
1254 if (src_stat->st_size == dst_stat->st_size)
1255 return FILE_SKIP;
1256 else
1257 return replace_with_zero;
1259 case REPLACE_SMALLER:
1260 do_refresh ();
1261 if (src_stat->st_size > dst_stat->st_size)
1262 return FILE_CONT;
1263 else
1264 return FILE_SKIP;
1266 case REPLACE_ALL:
1267 do_refresh ();
1268 return replace_with_zero;
1270 case REPLACE_REGET:
1271 /* Careful: we fall through and set do_append */
1272 ctx->do_reget = dst_stat->st_size;
1273 MC_FALLTHROUGH;
1275 case REPLACE_APPEND:
1276 ctx->do_append = TRUE;
1277 MC_FALLTHROUGH;
1279 case REPLACE_YES:
1280 do_refresh ();
1281 return FILE_CONT;
1283 case REPLACE_NO:
1284 case REPLACE_NONE:
1285 do_refresh ();
1286 return FILE_SKIP;
1288 case REPLACE_ABORT:
1289 default:
1290 return FILE_ABORT;
1294 /* --------------------------------------------------------------------------------------------- */
1296 char *
1297 file_mask_dialog (file_op_context_t *ctx, gboolean only_one, const char *format, const void *text,
1298 const char *def_text, gboolean *do_bg)
1300 gboolean preserve;
1301 size_t fmd_xlen;
1302 vfs_path_t *vpath;
1303 gboolean source_easy_patterns = easy_patterns;
1304 char fmd_buf[BUF_MEDIUM];
1305 char *dest_dir = NULL;
1306 char *tmp;
1307 char *def_text_secure;
1309 if (ctx == NULL)
1310 return NULL;
1312 /* unselect checkbox if target filesystem doesn't support attributes */
1313 preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1315 ctx->stable_symlinks = FALSE;
1316 *do_bg = FALSE;
1318 /* filter out a possible password from def_text */
1319 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1320 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1321 vfs_path_free (vpath, TRUE);
1323 if (source_easy_patterns)
1324 def_text_secure = str_glob_escape (tmp);
1325 else
1326 def_text_secure = str_regex_escape (tmp);
1327 g_free (tmp);
1329 if (only_one)
1331 int format_len, text_len;
1332 int max_len;
1334 format_len = str_term_width1 (format);
1335 text_len = str_term_width1 (text);
1336 max_len = COLS - 2 - 6;
1338 if (format_len + text_len <= max_len)
1340 fmd_xlen = format_len + text_len + 6;
1341 fmd_xlen = MAX (fmd_xlen, 68);
1343 else
1345 text = str_trunc ((const char *) text, max_len - format_len);
1346 fmd_xlen = max_len + 6;
1349 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1351 else
1353 fmd_xlen = COLS * 2 / 3;
1354 fmd_xlen = MAX (fmd_xlen, 68);
1355 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1359 char *source_mask = NULL;
1360 char *orig_mask;
1361 int val;
1362 struct stat buf;
1364 quick_widget_t quick_widgets[] = {
1365 /* *INDENT-OFF* */
1366 QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1367 "input-def", &source_mask, NULL, FALSE, FALSE,
1368 INPUT_COMPLETE_FILENAMES),
1369 QUICK_START_COLUMNS,
1370 QUICK_SEPARATOR (FALSE),
1371 QUICK_NEXT_COLUMN,
1372 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1373 QUICK_STOP_COLUMNS,
1374 QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
1375 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1376 QUICK_SEPARATOR (TRUE),
1377 QUICK_START_COLUMNS,
1378 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1379 QUICK_CHECKBOX (N_("Preserve &attributes"), &preserve, NULL),
1380 QUICK_NEXT_COLUMN,
1381 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1382 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1383 QUICK_STOP_COLUMNS,
1384 QUICK_START_BUTTONS (TRUE, TRUE),
1385 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1386 #ifdef ENABLE_BACKGROUND
1387 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1388 #endif /* ENABLE_BACKGROUND */
1389 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1390 QUICK_END
1391 /* *INDENT-ON* */
1394 WRect r = { -1, -1, 0, fmd_xlen };
1396 quick_dialog_t qdlg = {
1397 r, op_names[ctx->operation], "[Mask Copy/Rename]",
1398 quick_widgets, NULL, NULL
1401 while (TRUE)
1403 val = quick_dialog_skip (&qdlg, 4);
1405 if (val == B_CANCEL)
1407 g_free (def_text_secure);
1408 return NULL;
1411 ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1413 if (preserve)
1415 ctx->preserve = TRUE;
1416 ctx->umask_kill = (mode_t) (~0);
1417 ctx->preserve_uidgid = (geteuid () == 0);
1419 else
1421 mode_t i2;
1423 ctx->preserve = ctx->preserve_uidgid = FALSE;
1424 i2 = umask (0);
1425 umask (i2);
1426 ctx->umask_kill = i2 ^ ((mode_t) (~0));
1429 if (*dest_dir == '\0')
1431 g_free (def_text_secure);
1432 g_free (source_mask);
1433 g_free (dest_dir);
1434 return NULL;
1437 ctx->search_handle = mc_search_new (source_mask, NULL);
1438 if (ctx->search_handle != NULL)
1439 break;
1441 message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1442 MC_PTR_FREE (dest_dir);
1443 MC_PTR_FREE (source_mask);
1446 g_free (def_text_secure);
1447 g_free (source_mask);
1449 ctx->search_handle->is_case_sensitive = TRUE;
1450 if (source_easy_patterns)
1451 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1452 else
1453 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1455 tmp = dest_dir;
1456 dest_dir = tilde_expand (tmp);
1457 g_free (tmp);
1458 vpath = vfs_path_from_str (dest_dir);
1460 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1461 if (ctx->dest_mask == NULL)
1462 ctx->dest_mask = dest_dir;
1463 else
1464 ctx->dest_mask++;
1466 orig_mask = ctx->dest_mask;
1468 if (*ctx->dest_mask == '\0'
1469 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1470 && (!only_one
1471 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1472 || (ctx->dive_into_subdirs
1473 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1474 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1475 ctx->dest_mask = g_strdup ("\\0");
1476 else
1478 ctx->dest_mask = g_strdup (ctx->dest_mask);
1479 *orig_mask = '\0';
1482 if (*dest_dir == '\0')
1484 g_free (dest_dir);
1485 dest_dir = g_strdup ("./");
1488 vfs_path_free (vpath, TRUE);
1490 if (val == B_USER)
1491 *do_bg = TRUE;
1494 return dest_dir;
1497 /* --------------------------------------------------------------------------------------------- */