summaryrefslogtreecommitdiffstats
path: root/src/filemanager/filegui.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager/filegui.c')
-rw-r--r--src/filemanager/filegui.c1498
1 files changed, 1498 insertions, 0 deletions
diff --git a/src/filemanager/filegui.c b/src/filemanager/filegui.c
new file mode 100644
index 0000000..abca598
--- /dev/null
+++ b/src/filemanager/filegui.c
@@ -0,0 +1,1498 @@
+/*
+ File management GUI for the text mode edition
+
+ The copy code was based in GNU's cp, and was written by:
+ Torbjorn Granlund, David MacKenzie, and Jim Meyering.
+
+ The move code was based in GNU's mv, and was written by:
+ Mike Parker and David MacKenzie.
+
+ Janne Kukonlehto added much error recovery to them for being used
+ in an interactive program.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1995
+ Fred Leeflang, 1994, 1995
+ Miguel de Icaza, 1994, 1995, 1996
+ Jakub Jelinek, 1995, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Slava Zanko, 2009, 2010, 2011, 2012, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Please note that all dialogs used here must be safe for background
+ * operations.
+ */
+
+/** \file filegui.c
+ * \brief Source: file management GUI for the text mode edition
+ */
+
+/* {{{ Include files */
+
+#include <config.h>
+
+#if ((defined STAT_STATVFS || defined STAT_STATVFS64) \
+ && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \
+ || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME)))
+#define USE_STATVFS 1
+#else
+#define USE_STATVFS 0
+#endif
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if USE_STATVFS
+#include <sys/statvfs.h>
+#elif defined HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
+/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
+ It does have statvfs.h, but shouldn't use it, since it doesn't
+ HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
+/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#include <fs_info.h>
+#endif
+
+#if USE_STATVFS
+#if ! defined STAT_STATVFS && defined STAT_STATVFS64
+#define STRUCT_STATVFS struct statvfs64
+#define STATFS statvfs64
+#else
+#define STRUCT_STATVFS struct statvfs
+#define STATFS statvfs
+
+#if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
+#include <sys/utsname.h>
+#include <sys/statfs.h>
+#define STAT_STATFS2_BSIZE 1
+#endif
+#endif
+
+#else
+#define STATFS statfs
+#define STRUCT_STATVFS struct statfs
+#ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+/* BeOS has a statvfs function, but it does not return sensible values
+ for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
+ f_fstypename. Use 'struct fs_info' instead. */
+static int
+statfs (char const *filename, struct fs_info *buf)
+{
+ dev_t device;
+
+ device = dev_for_path (filename);
+
+ if (device < 0)
+ {
+ errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
+ : device == B_BAD_VALUE ? EINVAL
+ : device == B_NAME_TOO_LONG ? ENAMETOOLONG
+ : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
+ return -1;
+ }
+ /* If successful, buf->dev will be == device. */
+ return fs_stat_dev (device, buf);
+}
+
+#define STRUCT_STATVFS struct fs_info
+#else
+#define STRUCT_STATVFS struct statfs
+#endif
+#endif
+
+#ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
+#else
+#if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
+#endif
+#endif
+
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/key.h" /* tty_get_event */
+#include "lib/mcconfig.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* verbose, safe_overwrite */
+
+#include "filemanager.h"
+#include "fileopctx.h" /* FILE_CONT */
+
+#include "filegui.h"
+
+/* }}} */
+
+/*** global variables ****************************************************************************/
+
+gboolean classic_progressbar = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->rect.cols - 10)
+#define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
+
+/*** file scope type declarations ****************************************************************/
+
+/* *INDENT-OFF* */
+typedef enum {
+ MSDOS_SUPER_MAGIC = 0x4d44,
+ NTFS_SB_MAGIC = 0x5346544e,
+ FUSE_MAGIC = 0x65735546,
+ PROC_SUPER_MAGIC = 0x9fa0,
+ SMB_SUPER_MAGIC = 0x517B,
+ NCP_SUPER_MAGIC = 0x564c,
+ USBDEVICE_SUPER_MAGIC = 0x9fa2
+} filegui_nonattrs_fs_t;
+/* *INDENT-ON* */
+
+/* Used for button result values */
+typedef enum
+{
+ REPLACE_YES = B_USER,
+ REPLACE_NO,
+ REPLACE_APPEND,
+ REPLACE_REGET,
+ REPLACE_ALL,
+ REPLACE_OLDER,
+ REPLACE_NONE,
+ REPLACE_SMALLER,
+ REPLACE_SIZE,
+ REPLACE_ABORT
+} replace_action_t;
+
+/* This structure describes the UI and internal data required by a file
+ * operation context.
+ */
+typedef struct
+{
+ /* ETA and bps */
+ gboolean showing_eta;
+ gboolean showing_bps;
+
+ /* Dialog and widgets for the operation progress window */
+ WDialog *op_dlg;
+ /* Source file: label and name */
+ WLabel *src_file_label;
+ WLabel *src_file;
+ /* Target file: label and name */
+ WLabel *tgt_file_label;
+ WLabel *tgt_file;
+
+ WGauge *progress_file_gauge;
+ WLabel *progress_file_label;
+
+ WGauge *progress_total_gauge;
+
+ WLabel *total_files_processed_label;
+ WLabel *time_label;
+ WHLine *total_bytes_label;
+
+ /* Query replace dialog */
+ WDialog *replace_dlg;
+ const char *src_filename;
+ const char *tgt_filename;
+ replace_action_t replace_result;
+ gboolean dont_overwrite_with_zero;
+
+ struct stat *src_stat, *dst_stat;
+} file_op_context_ui_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ Widget *w;
+ FileProgressStatus action;
+ const char *text;
+ button_flags_t flags;
+ int len;
+} progress_buttons[] =
+{
+ /* *INDENT-OFF* */
+ { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
+ /* *INDENT-ON* */
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return true if statvfs works. This is false for statvfs on systems
+ with GNU libc on Linux kernels before 2.6.36, which stats all
+ preceding entries in /proc/mounts; that makes df hang if even one
+ of the corresponding file systems is hard-mounted but not available. */
+
+#if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
+static int
+statvfs_works (void)
+{
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+ return 1;
+#else
+ static int statvfs_works_cache = -1;
+ struct utsname name;
+
+ if (statvfs_works_cache < 0)
+ statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
+ return statvfs_works_cache;
+#endif
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+filegui__check_attrs_on_fs (const char *fs_path)
+{
+ STRUCT_STATVFS stfs;
+
+#if USE_STATVFS && defined(STAT_STATVFS)
+ if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
+ return TRUE;
+#else
+ if (STATFS (fs_path, &stfs) != 0)
+ return TRUE;
+#endif
+
+#if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
+ (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
+ switch ((filegui_nonattrs_fs_t) stfs.f_type)
+ {
+ case MSDOS_SUPER_MAGIC:
+ case NTFS_SB_MAGIC:
+ case PROC_SUPER_MAGIC:
+ case SMB_SUPER_MAGIC:
+ case NCP_SUPER_MAGIC:
+ case USBDEVICE_SUPER_MAGIC:
+ return FALSE;
+ default:
+ break;
+ }
+#elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
+ return FALSE;
+#elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
+ return FALSE;
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_frmt_time (char *buffer, double eta_secs)
+{
+ int eta_hours, eta_mins, eta_s;
+
+ eta_hours = (int) (eta_secs / (60 * 60));
+ eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
+ eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
+ g_snprintf (buffer, BUF_TINY, _("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
+{
+ char _fmt_buff[BUF_TINY];
+
+ if (eta_secs <= 0.5 && !always_show)
+ {
+ *buffer = '\0';
+ return;
+ }
+
+ if (eta_secs <= 0.5)
+ eta_secs = 1;
+ file_frmt_time (_fmt_buff, eta_secs);
+ g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_bps_prepare_for_show (char *buffer, long bps)
+{
+ if (bps > 1024 * 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
+ else if (bps > 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
+ else if (bps > 1)
+ g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
+ else
+ *buffer = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+file_ui_op_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Do not close the dialog because the query dialog will be shown */
+ if (parm == CK_Cancel)
+ {
+ DIALOG (w)->ret_value = FILE_ABORT; /* for check_progress_buttons() */
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* The dialog layout:
+ *
+ * +---------------------- File exists -----------------------+
+ * | New : /path/to/original_file_name | // 0, 1
+ * | 1234567 feb 4 2017 13:38 | // 2, 3
+ * | Existing: /path/to/target_file_name | // 4, 5
+ * | 1234567890 feb 4 2017 13:37 | // 6, 7
+ * +----------------------------------------------------------+
+ * | Overwrite this file? | // 8
+ * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
+ * +----------------------------------------------------------+
+ * | Overwrite all files? | // 13
+ * | [ ] Don't overwrite with zero length file | // 14
+ * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
+ * +----------------------------------------------------------|
+ * | [ Abort ] | // 20
+ * +----------------------------------------------------------+
+ */
+
+static replace_action_t
+overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
+{
+#define W(i) dlg_widgets[i].widget
+#define WX(i) W(i)->rect.x
+#define WY(i) W(i)->rect.y
+#define WCOLS(i) W(i)->rect.cols
+
+#define NEW_LABEL(i, text) \
+ W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
+
+#define ADD_LABEL(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
+ g->current != NULL ? g->current->data : NULL)
+
+#define NEW_BUTTON(i) \
+ W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
+ dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
+
+#define ADD_BUTTON(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
+
+ /* dialog sizes */
+ const int dlg_height = 17;
+ int dlg_width = 60;
+
+ struct
+ {
+ Widget *widget;
+ const char *text;
+ int y;
+ int x;
+ widget_pos_flags_t pos_flags;
+ int value; /* 0 for labels and checkbox */
+ } dlg_widgets[] =
+ {
+ /* *INDENT-OFF* */
+ /* 0 - label */
+ { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 1 - label - name */
+ { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 2 - label - size */
+ { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 3 - label - date & time */
+ { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* 4 - label */
+ { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 5 - label - name */
+ { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 6 - label - size */
+ { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 7 - label - date & time */
+ { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* --------------------------------------------------- */
+ /* 8 - label */
+ { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 9 - button */
+ { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
+ /* 10 - button */
+ { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
+ /* 11 - button */
+ { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
+ /* 12 - button */
+ { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
+ /* --------------------------------------------------- */
+ /* 13 - label */
+ { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 14 - checkbox */
+ { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 15 - button */
+ { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
+ /* 16 - button */
+ { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
+ /* 17 - button */
+ { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
+ /* 18 - button */
+ { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
+ /* 19 - button */
+ { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
+ /* --------------------------------------------------- */
+ /* 20 - button */
+ { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
+ /* *INDENT-ON* */
+ };
+
+ const int gap = 1;
+
+ file_op_context_ui_t *ui = ctx->ui;
+ Widget *wd;
+ WGroup *g;
+ const char *title;
+
+ vfs_path_t *p;
+ char *s1;
+ const char *cs1;
+ char s2[BUF_SMALL];
+ int w, bw1, bw2;
+ unsigned short i;
+
+ gboolean do_append = FALSE, do_reget = FALSE;
+ unsigned long yes_id, no_id;
+ int result;
+
+ if (mode == Foreground)
+ title = _("File exists");
+ else
+ title = _("Background process: File exists");
+
+#ifdef ENABLE_NLS
+ {
+ const unsigned short num = G_N_ELEMENTS (dlg_widgets);
+
+ for (i = 0; i < num; i++)
+ if (dlg_widgets[i].text != NULL)
+ dlg_widgets[i].text = _(dlg_widgets[i].text);
+ }
+#endif /* ENABLE_NLS */
+
+ /* create widgets to get their real widths */
+ /* new file */
+ NEW_LABEL (0, dlg_widgets[0].text);
+ /* new file name */
+ p = vfs_path_from_str (ui->src_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (1, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* new file size */
+ size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (2, s2);
+ /* new file modification date & time */
+ cs1 = file_date (ui->src_stat->st_mtime);
+ NEW_LABEL (3, cs1);
+
+ /* existing file */
+ NEW_LABEL (4, dlg_widgets[4].text);
+ /* existing file name */
+ p = vfs_path_from_str (ui->tgt_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (5, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* existing file size */
+ size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (6, s2);
+ /* existing file modification date & time */
+ cs1 = file_date (ui->dst_stat->st_mtime);
+ NEW_LABEL (7, cs1);
+
+ /* will "Append" and "Reget" buttons be in the dialog? */
+ do_append = !S_ISDIR (ui->dst_stat->st_mode);
+ do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0
+ && ui->src_stat->st_size > ui->dst_stat->st_size;
+
+ NEW_LABEL (8, dlg_widgets[8].text);
+ NEW_BUTTON (9);
+ NEW_BUTTON (10);
+ if (do_append)
+ NEW_BUTTON (11);
+ if (do_reget)
+ NEW_BUTTON (12);
+
+ NEW_LABEL (13, dlg_widgets[13].text);
+ dlg_widgets[14].widget =
+ WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
+ for (i = 15; i <= 20; i++)
+ NEW_BUTTON (i);
+
+ /* place widgets */
+ dlg_width -= 2 * (2 + gap); /* inside frame */
+
+ /* perhaps longest line is buttons */
+ bw1 = WCOLS (9) + gap + WCOLS (10);
+ if (do_append)
+ bw1 += gap + WCOLS (11);
+ if (do_reget)
+ bw1 += gap + WCOLS (12);
+ dlg_width = MAX (dlg_width, bw1);
+
+ bw2 = WCOLS (15);
+ for (i = 16; i <= 19; i++)
+ bw2 += gap + WCOLS (i);
+ dlg_width = MAX (dlg_width, bw2);
+
+ dlg_width = MAX (dlg_width, WCOLS (8));
+ dlg_width = MAX (dlg_width, WCOLS (13));
+ dlg_width = MAX (dlg_width, WCOLS (14));
+
+ /* truncate file names */
+ w = WCOLS (0) + gap + WCOLS (1);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (1));
+
+ w = dlg_width - gap - WCOLS (0);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ w = WCOLS (4) + gap + WCOLS (5);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (5));
+
+ w = dlg_width - gap - WCOLS (4);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ /* real dlalog width */
+ dlg_width += 2 * (2 + gap);
+
+ WX (1) = WX (0) + WCOLS (0) + gap;
+ WX (5) = WX (4) + WCOLS (4) + gap;
+
+ /* sizes: right alignment */
+ WX (2) = dlg_width / 2 - WCOLS (2);
+ WX (6) = dlg_width / 2 - WCOLS (6);
+
+ w = dlg_width - (2 + gap); /* right bound */
+
+ /* date & time */
+ WX (3) = w - WCOLS (3);
+ WX (7) = w - WCOLS (7);
+
+ /* buttons: center alignment */
+ WX (9) = dlg_width / 2 - bw1 / 2;
+ WX (10) = WX (9) + WCOLS (9) + gap;
+ if (do_append)
+ WX (11) = WX (10) + WCOLS (10) + gap;
+ if (do_reget)
+ WX (12) = WX (11) + WCOLS (11) + gap;
+
+ WX (15) = dlg_width / 2 - bw2 / 2;
+ for (i = 16; i <= 19; i++)
+ WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
+
+ /* TODO: write help (ticket #3970) */
+ ui->replace_dlg =
+ dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
+ "[Replace]", title);
+ wd = WIDGET (ui->replace_dlg);
+ g = GROUP (ui->replace_dlg);
+
+ /* file info */
+ for (i = 0; i <= 7; i++)
+ ADD_LABEL (i);
+ group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (8); /* Overwrite this file? */
+ yes_id = ADD_BUTTON (9); /* Yes */
+ no_id = ADD_BUTTON (10); /* No */
+ if (do_append)
+ ADD_BUTTON (11); /* Append */
+ if (do_reget)
+ ADD_BUTTON (12); /* Reget */
+ group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (13); /* Overwrite all files? */
+ group_add_widget (g, dlg_widgets[14].widget);
+ for (i = 15; i <= 19; i++)
+ ADD_BUTTON (i);
+ group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
+
+ ADD_BUTTON (20); /* Abort */
+
+ group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
+
+ result = dlg_run (ui->replace_dlg);
+
+ if (result != B_CANCEL)
+ ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
+
+ widget_destroy (wd);
+
+ return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
+
+#undef ADD_BUTTON
+#undef NEW_BUTTON
+#undef ADD_LABEL
+#undef NEW_LABEL
+#undef WCOLS
+#undef WX
+#undef W
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_wildcarded (const char *p)
+{
+ gboolean escaped = FALSE;
+
+ for (; *p != '\0'; p++)
+ {
+ if (*p == '\\')
+ {
+ if (p[1] >= '1' && p[1] <= '9' && !escaped)
+ return TRUE;
+ escaped = !escaped;
+ }
+ else
+ {
+ if ((*p == '*' || *p == '?') && !escaped)
+ return TRUE;
+ escaped = FALSE;
+ }
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+place_progress_buttons (WDialog * h, gboolean suspended)
+{
+ const size_t i = suspended ? 2 : 1;
+ Widget *w = WIDGET (h);
+ int buttons_width;
+
+ buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
+ buttons_width += progress_buttons[i].len;
+ button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
+
+ progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
+ progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
+ progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+progress_button_callback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ /* don't close dialog in any case */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+check_progress_buttons (file_op_context_t * ctx)
+{
+ int c;
+ Gpm_Event event;
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ get_event:
+ event.x = -1; /* Don't show the GPM cursor */
+ c = tty_get_event (&event, FALSE, ctx->suspended);
+ if (c == EV_NONE)
+ return FILE_CONT;
+
+ /* Reinitialize to avoid old values after events other than selecting a button */
+ ui->op_dlg->ret_value = FILE_CONT;
+
+ dlg_process_event (ui->op_dlg, c, &event);
+ switch (ui->op_dlg->ret_value)
+ {
+ case FILE_SKIP:
+ if (ctx->suspended)
+ {
+ /* redraw dialog in case of Skip after Suspend */
+ place_progress_buttons (ui->op_dlg, FALSE);
+ widget_draw (WIDGET (ui->op_dlg));
+ }
+ ctx->suspended = FALSE;
+ return FILE_SKIP;
+ case B_CANCEL:
+ case FILE_ABORT:
+ ctx->suspended = FALSE;
+ return FILE_ABORT;
+ case FILE_SUSPEND:
+ ctx->suspended = !ctx->suspended;
+ place_progress_buttons (ui->op_dlg, ctx->suspended);
+ widget_draw (WIDGET (ui->op_dlg));
+ MC_FALLTHROUGH;
+ default:
+ if (ctx->suspended)
+ goto get_event;
+ return FILE_CONT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ File progress display routines */
+
+void
+file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
+ filegui_dialog_type_t dialog_type)
+{
+ file_op_context_ui_t *ui;
+ Widget *w;
+ WGroup *g;
+ int buttons_width;
+ int dlg_width = 58, dlg_height = 17;
+ int y = 2, x = 3;
+ WRect r;
+
+ if (ctx == NULL || ctx->ui != NULL)
+ return;
+
+#ifdef ENABLE_NLS
+ if (progress_buttons[0].len == -1)
+ {
+ size_t i;
+
+ for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
+ progress_buttons[i].text = _(progress_buttons[i].text);
+ }
+#endif
+
+ ctx->dialog_type = dialog_type;
+ ctx->recursive_result = RECURSIVE_YES;
+ ctx->ui = g_new0 (file_op_context_ui_t, 1);
+
+ ui = ctx->ui;
+ ui->replace_result = REPLACE_YES;
+
+ ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
+ file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
+ w = WIDGET (ui->op_dlg);
+ g = GROUP (ui->op_dlg);
+
+ if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
+ {
+ ui->showing_eta = with_eta && ctx->progress_totals_computed;
+ ui->showing_bps = with_eta;
+
+ ui->src_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file_label);
+
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->tgt_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file_label);
+
+ ui->tgt_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file);
+
+ ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_file_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+
+ ui->progress_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->progress_file_label);
+
+ if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ ui->total_bytes_label = hline_new (y++, -1, -1);
+ group_add_widget (g, ui->total_bytes_label);
+
+ if (ctx->progress_totals_computed)
+ {
+ ui->progress_total_gauge =
+ gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_total_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_total_gauge,
+ WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+ }
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+
+ ui->time_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->time_label);
+ }
+ }
+ else
+ {
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+ }
+
+ group_add_widget (g, hline_new (y++, -1, -1));
+
+ progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
+ progress_buttons[0].flags, progress_buttons[0].text,
+ progress_button_callback));
+ if (progress_buttons[0].len == -1)
+ progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
+
+ progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
+ progress_buttons[1].flags, progress_buttons[1].text,
+ progress_button_callback));
+ if (progress_buttons[1].len == -1)
+ progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
+
+ if (progress_buttons[2].len == -1)
+ {
+ /* create and destroy button to get it length */
+ progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
+ progress_buttons[2].flags,
+ progress_buttons[2].text,
+ progress_button_callback));
+ progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
+ widget_destroy (progress_buttons[2].w);
+ }
+ progress_buttons[2].w = progress_buttons[1].w;
+
+ progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
+ progress_buttons[3].flags, progress_buttons[3].text,
+ NULL));
+ if (progress_buttons[3].len == -1)
+ progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
+
+ group_add_widget (g, progress_buttons[0].w);
+ group_add_widget (g, progress_buttons[1].w);
+ group_add_widget (g, progress_buttons[3].w);
+
+ buttons_width = 2 +
+ progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
+ progress_buttons[3].len;
+
+ /* adjust dialog sizes */
+ r = w->rect;
+ r.lines = y + 3;
+ r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
+ widget_set_size_rect (w, &r);
+
+ place_progress_buttons (ui->op_dlg, FALSE);
+
+ widget_select (progress_buttons[0].w);
+
+ /* We will manage the dialog without any help, that's why
+ we have to call dlg_init */
+ dlg_init (ui->op_dlg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_op_context_destroy_ui (file_op_context_t * ctx)
+{
+ if (ctx != NULL && ctx->ui != NULL)
+ {
+ file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
+
+ dlg_run_done (ui->op_dlg);
+ widget_destroy (WIDGET (ui->op_dlg));
+ MC_PTR_FREE (ctx->ui);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ show progressbar for file
+ */
+
+void
+file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
+ const char *stalled_msg, gboolean force_update)
+{
+ file_op_context_ui_t *ui;
+
+ if (!verbose || ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (total == 0)
+ {
+ gauge_show (ui->progress_file_gauge, FALSE);
+ return;
+ }
+
+ gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
+ gauge_show (ui->progress_file_gauge, TRUE);
+
+ if (!force_update)
+ return;
+
+ if (!ui->showing_eta || ctx->eta_secs <= 0.5)
+ label_set_text (ui->progress_file_label, stalled_msg);
+ else
+ {
+ char buffer2[BUF_TINY];
+
+ file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
+ if (ctx->bps == 0)
+ label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
+ else
+ {
+ char buffer3[BUF_TINY];
+
+ file_bps_prepare_for_show (buffer3, ctx->bps);
+ label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
+ }
+
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->total_files_processed_label == NULL)
+ return;
+
+ if (ctx->progress_totals_computed)
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"), done,
+ total);
+ else
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ uintmax_t copied_bytes, gboolean show_summary)
+{
+ char buffer2[BUF_TINY];
+ char buffer3[BUF_TINY];
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->progress_total_gauge != NULL)
+ {
+ if (ctx->progress_bytes == 0)
+ gauge_show (ui->progress_total_gauge, FALSE);
+ else
+ {
+ gauge_set_value (ui->progress_total_gauge, 1024,
+ (int) (1024 * copied_bytes / ctx->progress_bytes));
+ gauge_show (ui->progress_total_gauge, TRUE);
+ }
+ }
+
+ if (!show_summary && tctx->bps == 0)
+ return;
+
+ if (ui->time_label != NULL)
+ {
+ gint64 tv_current;
+ char buffer4[BUF_TINY];
+
+ tv_current = g_get_monotonic_time ();
+ file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC);
+
+ if (ctx->progress_totals_computed)
+ {
+ file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
+ }
+ }
+ else
+ {
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s"), buffer2);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
+ }
+ }
+ }
+
+ if (ui->total_bytes_label != NULL)
+ {
+ size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
+
+ if (!ctx->progress_totals_computed)
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
+ else
+ {
+ size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
+ }
+ }
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ char *s;
+
+ s = vfs_path_tokens_get (vpath, -1, 1);
+ label_set_text (ui->src_file_label, _("Source"));
+ label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
+ g_free (s);
+ }
+ else
+ {
+ label_set_text (ui->src_file_label, NULL);
+ label_set_text (ui->src_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ label_set_text (ui->tgt_file_label, _("Target"));
+ label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
+ }
+ else
+ {
+ label_set_text (ui->tgt_file_label, NULL);
+ label_set_text (ui->tgt_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
+{
+ static gint64 timestamp = 0;
+ /* update with 25 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 25;
+
+ gboolean ret;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FALSE;
+
+ ret = mc_time_elapsed (&timestamp, delay);
+
+ if (ret)
+ {
+ file_op_context_ui_t *ui;
+
+ ui = ctx->ui;
+
+ if (ui->src_file_label != NULL)
+ label_set_text (ui->src_file_label, _("Deleting"));
+
+ label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
+ }
+
+ if (count != NULL)
+ (*count)++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode,
+ const char *src, struct stat * src_stat,
+ const char *dst, struct stat * dst_stat)
+{
+ file_op_context_ui_t *ui;
+ FileProgressStatus replace_with_zero;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
+ || ui->replace_result == REPLACE_APPEND)
+ {
+ ui->src_filename = src;
+ ui->src_stat = src_stat;
+ ui->tgt_filename = dst;
+ ui->dst_stat = dst_stat;
+ ui->replace_result = overwrite_query_dialog (ctx, mode);
+ }
+
+ replace_with_zero = (src_stat->st_size == 0
+ && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
+
+ switch (ui->replace_result)
+ {
+ case REPLACE_OLDER:
+ do_refresh ();
+ if (src_stat->st_mtime > dst_stat->st_mtime)
+ return replace_with_zero;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_SIZE:
+ do_refresh ();
+ if (src_stat->st_size == dst_stat->st_size)
+ return FILE_SKIP;
+ else
+ return replace_with_zero;
+
+ case REPLACE_SMALLER:
+ do_refresh ();
+ if (src_stat->st_size > dst_stat->st_size)
+ return FILE_CONT;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_ALL:
+ do_refresh ();
+ return replace_with_zero;
+
+ case REPLACE_REGET:
+ /* Careful: we fall through and set do_append */
+ ctx->do_reget = dst_stat->st_size;
+ MC_FALLTHROUGH;
+
+ case REPLACE_APPEND:
+ ctx->do_append = TRUE;
+ MC_FALLTHROUGH;
+
+ case REPLACE_YES:
+ do_refresh ();
+ return FILE_CONT;
+
+ case REPLACE_NO:
+ case REPLACE_NONE:
+ do_refresh ();
+ return FILE_SKIP;
+
+ case REPLACE_ABORT:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+file_mask_dialog (file_op_context_t * ctx, gboolean only_one, const char *format, const void *text,
+ const char *def_text, gboolean * do_bg)
+{
+ size_t fmd_xlen;
+ vfs_path_t *vpath;
+ gboolean source_easy_patterns = easy_patterns;
+ char fmd_buf[BUF_MEDIUM];
+ char *dest_dir = NULL;
+ char *tmp;
+ char *def_text_secure;
+
+ if (ctx == NULL)
+ return NULL;
+
+ /* unselect checkbox if target filesystem doesn't support attributes */
+ ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
+ ctx->stable_symlinks = FALSE;
+ *do_bg = FALSE;
+
+ /* filter out a possible password from def_text */
+ vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
+ tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+
+ if (source_easy_patterns)
+ def_text_secure = strutils_glob_escape (tmp);
+ else
+ def_text_secure = strutils_regex_escape (tmp);
+ g_free (tmp);
+
+ if (only_one)
+ {
+ int format_len, text_len;
+ int max_len;
+
+ format_len = str_term_width1 (format);
+ text_len = str_term_width1 (text);
+ max_len = COLS - 2 - 6;
+
+ if (format_len + text_len <= max_len)
+ {
+ fmd_xlen = format_len + text_len + 6;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ }
+ else
+ {
+ text = str_trunc ((const char *) text, max_len - format_len);
+ fmd_xlen = max_len + 6;
+ }
+
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
+ }
+ else
+ {
+ fmd_xlen = COLS * 2 / 3;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
+ }
+
+ {
+ char *source_mask = NULL;
+ char *orig_mask;
+ int val;
+ struct stat buf;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
+ "input-def", &source_mask, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_FILENAMES),
+ QUICK_START_COLUMNS,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
+ NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
+ QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
+ QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+#ifdef ENABLE_BACKGROUND
+ QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
+#endif /* ENABLE_BACKGROUND */
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, fmd_xlen };
+
+ quick_dialog_t qdlg = {
+ r, op_names[ctx->operation], "[Mask Copy/Rename]",
+ quick_widgets, NULL, NULL
+ };
+
+ while (TRUE)
+ {
+ val = quick_dialog_skip (&qdlg, 4);
+
+ if (val == B_CANCEL)
+ {
+ g_free (def_text_secure);
+ return NULL;
+ }
+
+ ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
+
+ if (ctx->op_preserve)
+ {
+ ctx->preserve = TRUE;
+ ctx->umask_kill = 0777777;
+ ctx->preserve_uidgid = (geteuid () == 0);
+ }
+ else
+ {
+ mode_t i2;
+
+ ctx->preserve = ctx->preserve_uidgid = FALSE;
+ i2 = umask (0);
+ umask (i2);
+ ctx->umask_kill = i2 ^ 0777777;
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (def_text_secure);
+ g_free (source_mask);
+ g_free (dest_dir);
+ return NULL;
+ }
+
+ ctx->search_handle = mc_search_new (source_mask, NULL);
+ if (ctx->search_handle != NULL)
+ break;
+
+ message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
+ MC_PTR_FREE (dest_dir);
+ MC_PTR_FREE (source_mask);
+ }
+
+ g_free (def_text_secure);
+ g_free (source_mask);
+
+ ctx->search_handle->is_case_sensitive = TRUE;
+ if (source_easy_patterns)
+ ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
+ else
+ ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
+
+ tmp = dest_dir;
+ dest_dir = tilde_expand (tmp);
+ g_free (tmp);
+ vpath = vfs_path_from_str (dest_dir);
+
+ ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
+ if (ctx->dest_mask == NULL)
+ ctx->dest_mask = dest_dir;
+ else
+ ctx->dest_mask++;
+
+ orig_mask = ctx->dest_mask;
+
+ if (*ctx->dest_mask == '\0'
+ || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
+ && (!only_one
+ || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
+ || (ctx->dive_into_subdirs
+ && ((!only_one && !is_wildcarded (ctx->dest_mask))
+ || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
+ ctx->dest_mask = g_strdup ("\\0");
+ else
+ {
+ ctx->dest_mask = g_strdup (ctx->dest_mask);
+ *orig_mask = '\0';
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (dest_dir);
+ dest_dir = g_strdup ("./");
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ if (val == B_USER)
+ *do_bg = TRUE;
+ }
+
+ return dest_dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */