Build file operation dialogs in normal order.
[midnight-commander.git] / src / filemanager / filegui.c
blob7abb7e0b7568ca3cbd4aa223eac2ebb8e080d3a7
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, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
14 2004, 2005, 2006, 2007, 2009, 2011, 2012
15 The Free Software Foundation, Inc.
17 Written by:
18 Janne Kukonlehto, 1994, 1995
19 Fred Leeflang, 1994, 1995
20 Miguel de Icaza, 1994, 1995, 1996
21 Jakub Jelinek, 1995, 1996
22 Norbert Warmuth, 1997
23 Pavel Machek, 1998
24 Slava Zanko, 2009-2012
25 Andrew Borodin, 2009-2012
27 This file is part of the Midnight Commander.
29 The Midnight Commander is free software: you can redistribute it
30 and/or modify it under the terms of the GNU General Public License as
31 published by the Free Software Foundation, either version 3 of the License,
32 or (at your option) any later version.
34 The Midnight Commander is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
39 You should have received a copy of the GNU General Public License
40 along with this program. If not, see <http://www.gnu.org/licenses/>.
44 * Please note that all dialogs used here must be safe for background
45 * operations.
48 /** \file filegui.c
49 * \brief Source: file management GUI for the text mode edition
52 /* {{{ Include files */
54 #include <config.h>
56 /* Keep this conditional in sync with the similar conditional in m4.include/mc-get-fs-info. */
57 #if ((STAT_STATVFS || STAT_STATVFS64) \
58 && (HAVE_STRUCT_STATVFS_F_BASETYPE || HAVE_STRUCT_STATVFS_F_FSTYPENAME \
59 || (! HAVE_STRUCT_STATFS_F_FSTYPENAME)))
60 #define USE_STATVFS 1
61 #else
62 #define USE_STATVFS 0
63 #endif
65 #include <errno.h>
66 #include <ctype.h>
67 #include <stdio.h>
68 #include <string.h>
69 #include <sys/types.h>
70 #include <sys/stat.h>
72 #if USE_STATVFS
73 #include <sys/statvfs.h>
74 #elif HAVE_SYS_VFS_H
75 #include <sys/vfs.h>
76 #elif HAVE_SYS_MOUNT_H && HAVE_SYS_PARAM_H
77 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
78 It does have statvfs.h, but shouldn't use it, since it doesn't
79 HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
80 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
81 #include <sys/param.h>
82 #include <sys/mount.h>
83 #if HAVE_NFS_NFS_CLNT_H && HAVE_NFS_VFS_H
84 /* Ultrix 4.4 needs these for the declaration of struct statfs. */
85 #include <netinet/in.h>
86 #include <nfs/nfs_clnt.h>
87 #include <nfs/vfs.h>
88 #endif
89 #elif HAVE_OS_H /* BeOS */
90 #include <fs_info.h>
91 #endif
93 #if USE_STATVFS
94 #if ! STAT_STATVFS && STAT_STATVFS64
95 #define STRUCT_STATVFS struct statvfs64
96 #define STATFS statvfs64
97 #else
98 #define STRUCT_STATVFS struct statvfs
99 #define STATFS statvfs
100 /* Return true if statvfs works. This is false for statvfs on systems
101 with GNU libc on Linux kernels before 2.6.36, which stats all
102 preceding entries in /proc/mounts; that makes df hang if even one
103 of the corresponding file systems is hard-mounted but not available. */
104 #if ! (__linux__ && (__GLIBC__ || __UCLIBC__))
105 static int
106 statvfs_works (void)
108 return 1;
110 #else
111 #include <string.h> /* for strverscmp */
112 #include <sys/utsname.h>
113 #include <sys/statfs.h>
114 #define STAT_STATFS2_BSIZE 1
116 static int
117 statvfs_works (void)
119 static int statvfs_works_cache = -1;
120 struct utsname name;
122 if (statvfs_works_cache < 0)
123 statvfs_works_cache = (uname (&name) == 0 && 0 <= strverscmp (name.release, "2.6.36"));
124 return statvfs_works_cache;
126 #endif
127 #endif
128 #else
129 #define STATFS statfs
130 #define STRUCT_STATVFS struct statfs
131 #if HAVE_OS_H /* BeOS */
132 /* BeOS has a statvfs function, but it does not return sensible values
133 for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
134 f_fstypename. Use 'struct fs_info' instead. */
135 static int
136 statfs (char const *filename, struct fs_info *buf)
138 dev_t device = dev_for_path (filename);
140 if (device < 0)
142 errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
143 : device == B_BAD_VALUE ? EINVAL
144 : device == B_NAME_TOO_LONG ? ENAMETOOLONG
145 : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
146 return -1;
148 /* If successful, buf->dev will be == device. */
149 return fs_stat_dev (device, buf);
152 #define STRUCT_STATVFS struct fs_info
153 #else
154 #define STRUCT_STATVFS struct statfs
155 #endif
156 #endif
158 #if HAVE_STRUCT_STATVFS_F_BASETYPE
159 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
160 #else
161 #if HAVE_STRUCT_STATVFS_F_FSTYPENAME || HAVE_STRUCT_STATFS_F_FSTYPENAME
162 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
163 #elif HAVE_OS_H /* BeOS */
164 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
165 #endif
166 #endif
168 #include <unistd.h>
170 #include "lib/global.h"
172 #include "lib/tty/key.h" /* tty_get_event */
173 #include "lib/mcconfig.h"
174 #include "lib/search.h"
175 #include "lib/vfs/vfs.h"
176 #include "lib/strescape.h"
177 #include "lib/strutil.h"
178 #include "lib/timefmt.h" /* file_date() */
179 #include "lib/util.h"
180 #include "lib/widget.h"
182 #include "src/setup.h" /* verbose */
184 #include "midnight.h"
185 #include "fileopctx.h" /* FILE_CONT */
187 #include "filegui.h"
189 /* }}} */
191 /*** global variables ****************************************************************************/
193 int classic_progressbar = 1;
195 /*** file scope macro definitions ****************************************************************/
197 /* Hack: the vfs code should not rely on this */
198 #define WITH_FULL_PATHS 1
200 #define truncFileString(ui, s) str_trunc (s, 52)
201 #define truncFileStringSecure(ui, s) path_trunc (s, 52)
203 /*** file scope type declarations ****************************************************************/
205 /* *INDENT-OFF* */
206 typedef enum {
207 MSDOS_SUPER_MAGIC = 0x4d44,
208 NTFS_SB_MAGIC = 0x5346544e,
209 FUSE_MAGIC = 0x65735546,
210 PROC_SUPER_MAGIC = 0x9fa0,
211 SMB_SUPER_MAGIC = 0x517B,
212 NCP_SUPER_MAGIC = 0x564c,
213 USBDEVICE_SUPER_MAGIC = 0x9fa2
214 } filegui_nonattrs_fs_t;
215 /* *INDENT-ON* */
217 /* Used for button result values */
218 typedef enum
220 REPLACE_YES = B_USER,
221 REPLACE_NO,
222 REPLACE_APPEND,
223 REPLACE_ALWAYS,
224 REPLACE_UPDATE,
225 REPLACE_NEVER,
226 REPLACE_ABORT,
227 REPLACE_SIZE,
228 REPLACE_REGET
229 } replace_action_t;
231 /* This structure describes the UI and internal data required by a file
232 * operation context.
234 typedef struct
236 /* ETA and bps */
237 gboolean showing_eta;
238 gboolean showing_bps;
240 /* Dialog and widgets for the operation progress window */
241 Dlg_head *op_dlg;
242 WLabel *file_string[2];
243 WLabel *file_label[2];
244 WGauge *progress_file_gauge;
245 WLabel *progress_file_label;
247 WGauge *progress_total_gauge;
249 WLabel *total_files_processed_label;
250 WLabel *time_label;
251 WLabel *total_bytes_label;
253 /* Query replace dialog */
254 Dlg_head *replace_dlg;
255 const char *replace_filename;
256 replace_action_t replace_result;
258 struct stat *s_stat, *d_stat;
259 } FileOpContextUI;
261 /*** file scope variables ************************************************************************/
263 /*** file scope functions ************************************************************************/
264 /* --------------------------------------------------------------------------------------------- */
266 static gboolean
267 filegui__check_attrs_on_fs (const char *fs_path)
269 STRUCT_STATVFS stfs;
271 if (!setup_copymove_persistent_attr)
272 return FALSE;
274 #if USE_STATVFS && defined(STAT_STATVFS)
275 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
276 return TRUE;
277 #else
278 if (STATFS (fs_path, &stfs) != 0)
279 return TRUE;
280 #endif
282 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
283 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
284 switch ((filegui_nonattrs_fs_t) stfs.f_type)
286 case MSDOS_SUPER_MAGIC:
287 case NTFS_SB_MAGIC:
288 case PROC_SUPER_MAGIC:
289 case SMB_SUPER_MAGIC:
290 case NCP_SUPER_MAGIC:
291 case USBDEVICE_SUPER_MAGIC:
292 return FALSE;
293 default:
294 break;
296 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
297 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
298 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
299 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
300 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
301 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
302 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
303 return FALSE;
304 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
305 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
306 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
307 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
308 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
309 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
310 return FALSE;
311 #endif
313 return TRUE;
316 /* --------------------------------------------------------------------------------------------- */
318 static void
319 file_frmt_time (char *buffer, double eta_secs)
321 int eta_hours, eta_mins, eta_s;
322 eta_hours = eta_secs / (60 * 60);
323 eta_mins = (eta_secs - (eta_hours * 60 * 60)) / 60;
324 eta_s = eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
325 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
328 /* --------------------------------------------------------------------------------------------- */
330 static void
331 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
333 char _fmt_buff[BUF_TINY];
334 if (eta_secs <= 0.5 && !always_show)
336 *buffer = '\0';
337 return;
339 if (eta_secs <= 0.5)
340 eta_secs = 1;
341 file_frmt_time (_fmt_buff, eta_secs);
342 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
345 /* --------------------------------------------------------------------------------------------- */
347 static void
348 file_bps_prepare_for_show (char *buffer, long bps)
350 if (bps > 1024 * 1024)
352 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
354 else if (bps > 1024)
356 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
358 else if (bps > 1)
360 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
362 else
363 *buffer = '\0';
366 /* --------------------------------------------------------------------------------------------- */
368 * FIXME: probably it is better to replace this with quick dialog machinery,
369 * but actually I'm not familiar with it and have not much time :(
370 * alex
372 static replace_action_t
373 overwrite_query_dialog (FileOpContext * ctx, enum OperationMode mode)
375 #define ADD_RD_BUTTON(i, ypos) \
376 add_widget (ui->replace_dlg, \
377 button_new (ypos, rd_widgets [i].xpos, rd_widgets [i].value, \
378 NORMAL_BUTTON, rd_widgets [i].text, 0))
380 #define ADD_RD_LABEL(i, p1, p2, ypos) \
381 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2); \
382 add_widget (ui->replace_dlg, label_new (ypos, rd_widgets [i].xpos, buffer))
384 /* dialog sizes */
385 const int rd_ylen = 17;
386 int rd_xlen = 60;
387 int y = 2;
388 unsigned long yes_id;
390 struct
392 const char *text;
393 int ypos, xpos;
394 int value; /* 0 for labels */
395 } rd_widgets[] =
397 /* *INDENT-OFF* */
398 /* 0 */
399 { N_("Target file already exists!"), 3, 4, 0 },
400 /* 1 */
401 { "%s", 4, 4, 0 },
402 /* 2 */ /* cannot use PRIuMAX here; %llu is used instead */
403 { N_("New : %s, size %llu"), 6, 4, 0 },
404 /* 3 */ /* cannot use PRIuMAX here; %llu is used instead */
405 { N_("Existing: %s, size %llu"), 7, 4, 0 },
406 /* 4 */
407 { N_("Overwrite this target?"), 9, 4, 0 },
408 /* 5 */
409 { N_("&Yes"), 9, 28, REPLACE_YES },
410 /* 6 */
411 { N_("&No"), 9, 37, REPLACE_NO },
412 /* 7 */
413 { N_("A&ppend"), 9, 45, REPLACE_APPEND },
414 /* 8 */
415 { N_("&Reget"), 10, 28, REPLACE_REGET },
416 /* 9 */
417 { N_("Overwrite all targets?"), 11, 4, 0 },
418 /* 10 */
419 { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
420 /* 11 */
421 { N_("&Update"), 11, 36, REPLACE_UPDATE },
422 /* 12 */
423 { N_("Non&e"), 11, 47, REPLACE_NEVER },
424 /* 13 */
425 { N_("If &size differs"), 12, 28, REPLACE_SIZE },
426 /* 14 */
427 { N_("&Abort"), 14, 25, REPLACE_ABORT }
428 /* *INDENT-ON* */
431 const size_t num = G_N_ELEMENTS (rd_widgets);
432 int *widgets_len;
434 FileOpContextUI *ui = ctx->ui;
436 char buffer[BUF_SMALL];
437 const char *title;
438 int stripped_name_len;
439 vfs_path_t *stripped_vpath;
440 const char *stripped_name;
441 char *stripped_name_orig;
442 int result;
444 widgets_len = g_new0 (int, num);
446 if (mode == Foreground)
447 title = _("File exists");
448 else
449 title = _("Background process: File exists");
451 stripped_vpath = vfs_path_from_str (ui->replace_filename);
452 stripped_name = stripped_name_orig =
453 vfs_path_to_str_flags (stripped_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
454 vfs_path_free (stripped_vpath);
455 stripped_name_len = str_term_width1 (stripped_name);
458 size_t i;
459 int l1, l2, l, row;
461 for (i = 0; i < num; i++)
463 #ifdef ENABLE_NLS
464 if (i != 1) /* skip filename */
465 rd_widgets[i].text = _(rd_widgets[i].text);
466 #endif /* ENABLE_NLS */
467 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
471 * longest of "Overwrite..." labels
472 * (assume "Target date..." are short enough)
474 l1 = max (widgets_len[9], widgets_len[4]);
476 /* longest of button rows */
477 l = l2 = 0;
478 row = 0;
479 for (i = 1; i < num - 1; i++)
480 if (rd_widgets[i].value != 0)
482 if (row != rd_widgets[i].ypos)
484 row = rd_widgets[i].ypos;
485 l2 = max (l2, l);
486 l = 0;
488 l += widgets_len[i] + 4;
491 l2 = max (l2, l); /* last row */
492 rd_xlen = max (rd_xlen, l1 + l2 + 8);
493 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
494 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
496 /* Now place widgets */
497 l1 += 5; /* start of first button in the row */
498 l = l1;
499 row = 0;
500 for (i = 2; i < num - 1; i++)
501 if (rd_widgets[i].value != 0)
503 if (row != rd_widgets[i].ypos)
505 row = rd_widgets[i].ypos;
506 l = l1;
508 rd_widgets[i].xpos = l;
509 l += widgets_len[i] + 4;
512 /* Abort button is centered */
513 rd_widgets[num - 1].xpos = (rd_xlen - widgets_len[num - 1] - 3) / 2;
516 /* FIXME - missing help node */
517 ui->replace_dlg =
518 create_dlg (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, NULL, "[Replace]", title,
519 DLG_CENTER);
521 /* prompt -- centered */
522 add_widget (ui->replace_dlg,
523 label_new (y++, (rd_xlen - widgets_len[0]) / 2, rd_widgets[0].text));
524 /* file name -- centered */
525 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
526 stripped_name_len = str_term_width1 (stripped_name);
527 add_widget (ui->replace_dlg, label_new (y++, (rd_xlen - stripped_name_len) / 2, stripped_name));
529 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
531 /* source date and size */
532 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), (unsigned long long) ui->s_stat->st_size,
533 y++);
534 /* destination date and size */
535 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), (unsigned long long) ui->d_stat->st_size,
536 y++);
538 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
540 ADD_RD_LABEL (4, 0, 0, y); /* Overwrite this target? */
541 yes_id = ADD_RD_BUTTON (5, y); /* Yes */
542 ADD_RD_BUTTON (6, y); /* No */
544 /* "this target..." widgets */
545 if (!S_ISDIR (ui->d_stat->st_mode))
547 ADD_RD_BUTTON (7, y++); /* Append */
549 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
550 && (ui->s_stat->st_size > ui->d_stat->st_size))
551 ADD_RD_BUTTON (8, y++); /* Reget */
554 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
556 ADD_RD_LABEL (9, 0, 0, y); /* Overwrite all targets? */
557 ADD_RD_BUTTON (10, y); /* All" */
558 ADD_RD_BUTTON (11, y); /* Update */
559 ADD_RD_BUTTON (12, y++); /* None */
560 ADD_RD_BUTTON (13, y++); /* If size differs */
562 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
564 ADD_RD_BUTTON (14, y); /* Abort */
566 dlg_set_size (ui->replace_dlg, y + 3, rd_xlen);
567 dlg_select_by_id (ui->replace_dlg, yes_id);
568 result = run_dlg (ui->replace_dlg);
569 destroy_dlg (ui->replace_dlg);
571 g_free (widgets_len);
572 g_free (stripped_name_orig);
574 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
575 #undef ADD_RD_LABEL
576 #undef ADD_RD_BUTTON
579 /* --------------------------------------------------------------------------------------------- */
581 static gboolean
582 is_wildcarded (char *p)
584 for (; *p; p++)
586 if (*p == '*')
587 return TRUE;
588 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
589 return TRUE;
591 return FALSE;
594 /* --------------------------------------------------------------------------------------------- */
595 /*** public functions ****************************************************************************/
596 /* --------------------------------------------------------------------------------------------- */
598 FileProgressStatus
599 check_progress_buttons (FileOpContext * ctx)
601 int c;
602 Gpm_Event event;
603 FileOpContextUI *ui;
605 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
607 ui = ctx->ui;
609 event.x = -1; /* Don't show the GPM cursor */
610 c = tty_get_event (&event, FALSE, FALSE);
611 if (c == EV_NONE)
612 return FILE_CONT;
614 /* Reinitialize to avoid old values after events other than
615 selecting a button */
616 ui->op_dlg->ret_value = FILE_CONT;
618 dlg_process_event (ui->op_dlg, c, &event);
619 switch (ui->op_dlg->ret_value)
621 case FILE_SKIP:
622 return FILE_SKIP;
623 case B_CANCEL:
624 case FILE_ABORT:
625 return FILE_ABORT;
626 default:
627 return FILE_CONT;
632 /* --------------------------------------------------------------------------------------------- */
633 /* {{{ File progress display routines */
635 void
636 file_op_context_create_ui_without_init (FileOpContext * ctx, gboolean with_eta,
637 filegui_dialog_type_t dialog_type)
639 FileOpContextUI *ui;
640 const char *abort_button_label = N_("&Abort");
641 const char *skip_button_label = N_("&Skip");
642 int abort_button_width, skip_button_width, buttons_width;
643 int dlg_width, dlg_height;
644 int y = 2, x = 3;
645 Widget *skip_button;
647 g_return_if_fail (ctx != NULL);
648 g_return_if_fail (ctx->ui == NULL);
650 #ifdef ENABLE_NLS
651 abort_button_label = _(abort_button_label);
652 skip_button_label = _(skip_button_label);
653 #endif
655 abort_button_width = str_term_width1 (abort_button_label) + 3;
656 skip_button_width = str_term_width1 (skip_button_label) + 3;
657 buttons_width = abort_button_width + skip_button_width + 1;
659 dlg_width = max (58, buttons_width + 6);
660 dlg_height = 17; /* will be adjusted later */
662 ui = g_new0 (FileOpContextUI, 1);
663 ctx->ui = ui;
664 ctx->dialog_type = dialog_type;
665 ctx->recursive_result = RECURSIVE_YES;
666 ui->replace_result = REPLACE_YES;
667 ui->showing_eta = with_eta && file_op_compute_totals;
668 ui->showing_bps = with_eta;
670 ui->op_dlg =
671 create_dlg (TRUE, 0, 0, dlg_height, dlg_width, dialog_colors, NULL, NULL, NULL,
672 op_names[ctx->operation], DLG_CENTER);
674 ui->file_label[0] = label_new (y++, x, "");
675 add_widget (ui->op_dlg, ui->file_label[0]);
677 ui->file_string[0] = label_new (y++, x, "");
678 add_widget (ui->op_dlg, ui->file_string[0]);
680 ui->file_label[1] = label_new (y++, x, "");
681 add_widget (ui->op_dlg, ui->file_label[1]);
683 ui->file_string[1] = label_new (y++, x, "");
684 add_widget (ui->op_dlg, ui->file_string[1]);
686 ui->progress_file_gauge = gauge_new (y++, x + 3, 0, 100, 0);
687 if (!classic_progressbar && (current_panel == right_panel))
688 ui->progress_file_gauge->from_left_to_right = FALSE;
689 add_widget (ui->op_dlg, ui->progress_file_gauge);
691 ui->progress_file_label = label_new (y++, x, "");
692 add_widget (ui->op_dlg, ui->progress_file_label);
694 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
696 add_widget (ui->op_dlg, hline_new (y, -1, -1));
698 ui->total_bytes_label = label_new (y++, x + 15, "");
699 add_widget (ui->op_dlg, ui->total_bytes_label);
701 if (file_op_compute_totals)
703 ui->progress_total_gauge = gauge_new (y++, x + 3, 0, 100, 0);
704 if (!classic_progressbar && (current_panel == right_panel))
705 ui->progress_total_gauge->from_left_to_right = FALSE;
706 add_widget (ui->op_dlg, ui->progress_total_gauge);
709 ui->total_files_processed_label = label_new (y++, x, "");
710 add_widget (ui->op_dlg, ui->total_files_processed_label);
712 ui->time_label = label_new (y++, x, "");
713 add_widget (ui->op_dlg, ui->time_label);
716 add_widget (ui->op_dlg, hline_new (y++, -1, -1));
718 x = (dlg_width - buttons_width) / 2;
719 skip_button = WIDGET (button_new (y, x, FILE_SKIP, NORMAL_BUTTON, skip_button_label, NULL));
720 add_widget (ui->op_dlg, skip_button);
722 x += skip_button_width + 1;
723 add_widget (ui->op_dlg, button_new (y, x, FILE_ABORT, NORMAL_BUTTON, abort_button_label, NULL));
725 /* adjust dialog height */
726 dlg_set_size (ui->op_dlg, y + 3, dlg_width);
728 dlg_select_widget (skip_button);
731 /* --------------------------------------------------------------------------------------------- */
733 void
734 file_op_context_create_ui (FileOpContext * ctx, gboolean with_eta,
735 filegui_dialog_type_t dialog_type)
737 FileOpContextUI *ui;
739 g_return_if_fail (ctx != NULL);
740 g_return_if_fail (ctx->ui == NULL);
742 file_op_context_create_ui_without_init (ctx, with_eta, dialog_type);
743 ui = ctx->ui;
745 /* We will manage the dialog without any help, that's why
746 we have to call init_dlg */
747 init_dlg (ui->op_dlg);
750 /* --------------------------------------------------------------------------------------------- */
752 void
753 file_op_context_destroy_ui (FileOpContext * ctx)
755 g_return_if_fail (ctx != NULL);
757 if (ctx->ui != NULL)
759 FileOpContextUI *ui = (FileOpContextUI *) ctx->ui;
761 dlg_run_done (ui->op_dlg);
762 destroy_dlg (ui->op_dlg);
763 g_free (ui);
764 ctx->ui = NULL;
768 /* --------------------------------------------------------------------------------------------- */
770 show progressbar for file
773 void
774 file_progress_show (FileOpContext * ctx, off_t done, off_t total,
775 const char *stalled_msg, gboolean force_update)
777 FileOpContextUI *ui;
778 char buffer[BUF_TINY];
779 char buffer2[BUF_TINY];
780 char buffer3[BUF_TINY];
782 if (!verbose)
783 return;
785 g_return_if_fail (ctx != NULL);
786 g_return_if_fail (ctx->ui != NULL);
788 ui = ctx->ui;
790 if (total == 0)
792 gauge_show (ui->progress_file_gauge, 0);
793 return;
796 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
797 gauge_show (ui->progress_file_gauge, 1);
799 if (!force_update)
800 return;
802 if (ui->showing_eta && ctx->eta_secs > 0.5)
804 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
805 if (ctx->bps == 0)
806 g_snprintf (buffer, BUF_TINY, "%s %s", buffer2, stalled_msg);
807 else
809 file_bps_prepare_for_show (buffer3, ctx->bps);
810 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
813 else
815 g_snprintf (buffer, BUF_TINY, "%s", stalled_msg);
818 label_set_text (ui->progress_file_label, buffer);
821 /* --------------------------------------------------------------------------------------------- */
823 void
824 file_progress_show_count (FileOpContext * ctx, size_t done, size_t total)
826 char buffer[BUF_TINY];
827 FileOpContextUI *ui;
829 g_return_if_fail (ctx != NULL);
830 g_return_if_fail (ctx->ui != NULL);
832 ui = ctx->ui;
833 if (file_op_compute_totals)
834 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu/%zu"), done, total);
835 else
836 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu"), done);
837 label_set_text (ui->total_files_processed_label, buffer);
840 /* --------------------------------------------------------------------------------------------- */
842 void
843 file_progress_show_total (FileOpTotalContext * tctx, FileOpContext * ctx, uintmax_t copied_bytes,
844 gboolean show_summary)
846 char buffer[BUF_TINY];
847 char buffer2[BUF_TINY];
848 char buffer3[BUF_TINY];
849 char buffer4[BUF_TINY];
850 struct timeval tv_current;
851 FileOpContextUI *ui;
853 g_return_if_fail (ctx != NULL);
854 g_return_if_fail (ctx->ui != NULL);
856 ui = ctx->ui;
858 if (file_op_compute_totals)
860 if (ctx->progress_bytes == 0)
861 gauge_show (ui->progress_total_gauge, 0);
862 else
864 gauge_set_value (ui->progress_total_gauge, 1024,
865 (int) (1024 * copied_bytes / ctx->progress_bytes));
866 gauge_show (ui->progress_total_gauge, 1);
870 if (!show_summary && tctx->bps == 0)
871 return;
873 gettimeofday (&tv_current, NULL);
874 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
876 if (file_op_compute_totals)
878 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
879 if (tctx->bps == 0)
880 g_snprintf (buffer, BUF_TINY, _("Time: %s %s"), buffer2, buffer3);
881 else
883 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
884 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
887 else
889 if (tctx->bps == 0)
890 g_snprintf (buffer, BUF_TINY, _("Time: %s"), buffer2);
891 else
893 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
894 g_snprintf (buffer, BUF_TINY, _("Time: %s (%s)"), buffer2, buffer4);
898 label_set_text (ui->time_label, buffer);
900 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
901 if (!file_op_compute_totals)
902 g_snprintf (buffer, BUF_TINY, _(" Total: %s "), buffer2);
903 else
905 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
906 g_snprintf (buffer, BUF_TINY, _(" Total: %s/%s "), buffer2, buffer3);
909 label_set_text (ui->total_bytes_label, buffer);
912 /* }}} */
914 /* --------------------------------------------------------------------------------------------- */
916 void
917 file_progress_show_source (FileOpContext * ctx, const vfs_path_t * s_vpath)
919 FileOpContextUI *ui;
921 g_return_if_fail (ctx != NULL);
922 g_return_if_fail (ctx->ui != NULL);
924 ui = ctx->ui;
926 if (s_vpath != NULL)
928 char *s;
930 s = vfs_path_tokens_get (s_vpath, -1, 1);
931 label_set_text (ui->file_label[0], _("Source"));
932 label_set_text (ui->file_string[0], truncFileString (ui, s));
933 g_free (s);
935 else
937 label_set_text (ui->file_label[0], "");
938 label_set_text (ui->file_string[0], "");
942 /* --------------------------------------------------------------------------------------------- */
944 void
945 file_progress_show_target (FileOpContext * ctx, const vfs_path_t * s_vpath)
947 FileOpContextUI *ui;
949 g_return_if_fail (ctx != NULL);
950 g_return_if_fail (ctx->ui != NULL);
952 ui = ctx->ui;
954 if (s_vpath != NULL)
956 char *s;
958 s = vfs_path_to_str (s_vpath);
959 label_set_text (ui->file_label[1], _("Target"));
960 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
961 g_free (s);
963 else
965 label_set_text (ui->file_label[1], "");
966 label_set_text (ui->file_string[1], "");
970 /* --------------------------------------------------------------------------------------------- */
972 void
973 file_progress_show_deleting (FileOpContext * ctx, const char *s)
975 FileOpContextUI *ui;
977 g_return_if_fail (ctx != NULL);
978 g_return_if_fail (ctx->ui != NULL);
980 ui = ctx->ui;
981 label_set_text (ui->file_label[0], _("Deleting"));
982 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
985 /* --------------------------------------------------------------------------------------------- */
987 FileProgressStatus
988 file_progress_real_query_replace (FileOpContext * ctx,
989 enum OperationMode mode, const char *destname,
990 struct stat *_s_stat, struct stat *_d_stat)
992 FileOpContextUI *ui;
994 g_return_val_if_fail (ctx != NULL, FILE_CONT);
995 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
997 ui = ctx->ui;
999 if (ui->replace_result < REPLACE_ALWAYS)
1001 ui->replace_filename = destname;
1002 ui->s_stat = _s_stat;
1003 ui->d_stat = _d_stat;
1004 ui->replace_result = overwrite_query_dialog (ctx, mode);
1007 switch (ui->replace_result)
1009 case REPLACE_UPDATE:
1010 do_refresh ();
1011 if (_s_stat->st_mtime > _d_stat->st_mtime)
1012 return FILE_CONT;
1013 else
1014 return FILE_SKIP;
1016 case REPLACE_SIZE:
1017 do_refresh ();
1018 if (_s_stat->st_size == _d_stat->st_size)
1019 return FILE_SKIP;
1020 else
1021 return FILE_CONT;
1023 case REPLACE_REGET:
1024 /* Careful: we fall through and set do_append */
1025 ctx->do_reget = _d_stat->st_size;
1027 case REPLACE_APPEND:
1028 ctx->do_append = TRUE;
1030 case REPLACE_YES:
1031 case REPLACE_ALWAYS:
1032 do_refresh ();
1033 return FILE_CONT;
1034 case REPLACE_NO:
1035 case REPLACE_NEVER:
1036 do_refresh ();
1037 return FILE_SKIP;
1038 case REPLACE_ABORT:
1039 default:
1040 return FILE_ABORT;
1044 /* --------------------------------------------------------------------------------------------- */
1046 char *
1047 file_mask_dialog (FileOpContext * ctx, FileOperation operation,
1048 gboolean only_one,
1049 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1051 size_t fmd_xlen;
1052 vfs_path_t *vpath;
1053 int source_easy_patterns = easy_patterns;
1054 char fmd_buf[BUF_MEDIUM];
1055 char *dest_dir, *tmp;
1056 char *def_text_secure;
1058 g_return_val_if_fail (ctx != NULL, NULL);
1060 /* unselect checkbox if target filesystem don't support attributes */
1061 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1062 ctx->stable_symlinks = FALSE;
1063 *do_bg = FALSE;
1065 /* filter out a possible password from def_text */
1066 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1067 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1068 vfs_path_free (vpath);
1070 if (source_easy_patterns)
1071 def_text_secure = strutils_glob_escape (tmp);
1072 else
1073 def_text_secure = strutils_regex_escape (tmp);
1074 g_free (tmp);
1076 fmd_xlen = max (68, (COLS * 2 / 3));
1078 if (only_one)
1079 g_snprintf (fmd_buf, sizeof (fmd_buf), format,
1080 str_trunc ((const char *) text, fmd_xlen - 7));
1081 else
1082 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1085 char *source_mask, *orig_mask;
1086 int val;
1087 struct stat buf;
1089 quick_widget_t quick_widgets[] = {
1090 /* *INDENT-OFF* */
1091 QUICK_LABELED_INPUT (fmd_buf, input_label_above,
1092 easy_patterns ? "*" : "^(.*)$", 0, "input-def", &source_mask,
1093 NULL),
1094 QUICK_START_COLUMNS,
1095 QUICK_SEPARATOR (FALSE),
1096 QUICK_NEXT_COLUMN,
1097 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1098 QUICK_STOP_COLUMNS,
1099 QUICK_LABELED_INPUT (N_("to:"), input_label_above,
1100 def_text_secure, 0, "input2", &dest_dir, NULL),
1101 QUICK_SEPARATOR (TRUE),
1102 QUICK_START_COLUMNS,
1103 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1104 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1105 QUICK_NEXT_COLUMN,
1106 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1107 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1108 QUICK_STOP_COLUMNS,
1109 QUICK_START_BUTTONS (TRUE, TRUE),
1110 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1111 #ifdef ENABLE_BACKGROUND
1112 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1113 #endif /* ENABLE_BACKGROUND */
1114 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1115 QUICK_END
1116 /* *INDENT-ON* */
1119 quick_dialog_t qdlg = {
1120 -1, -1, fmd_xlen,
1121 op_names[operation], "[Mask Copy/Rename]",
1122 quick_widgets, NULL, NULL
1125 ask_file_mask:
1126 val = quick_dialog_skip (&qdlg, 4);
1128 if (val == B_CANCEL)
1130 g_free (def_text_secure);
1131 return NULL;
1134 if (ctx->follow_links)
1135 ctx->stat_func = mc_stat;
1136 else
1137 ctx->stat_func = mc_lstat;
1139 if (ctx->op_preserve)
1141 ctx->preserve = TRUE;
1142 ctx->umask_kill = 0777777;
1143 ctx->preserve_uidgid = (geteuid () == 0);
1145 else
1147 int i2;
1149 ctx->preserve = ctx->preserve_uidgid = FALSE;
1150 i2 = umask (0);
1151 umask (i2);
1152 ctx->umask_kill = i2 ^ 0777777;
1155 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1157 g_free (def_text_secure);
1158 g_free (source_mask);
1159 return dest_dir;
1162 ctx->search_handle = mc_search_new (source_mask, -1);
1164 if (ctx->search_handle == NULL)
1166 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"), source_mask);
1167 g_free (dest_dir);
1168 g_free (source_mask);
1169 goto ask_file_mask;
1172 g_free (def_text_secure);
1173 g_free (source_mask);
1175 ctx->search_handle->is_case_sensitive = TRUE;
1176 if (source_easy_patterns)
1177 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1178 else
1179 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1181 tmp = dest_dir;
1182 dest_dir = tilde_expand (tmp);
1183 g_free (tmp);
1184 vpath = vfs_path_from_str (dest_dir);
1186 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1187 if (ctx->dest_mask == NULL)
1188 ctx->dest_mask = dest_dir;
1189 else
1190 ctx->dest_mask++;
1191 orig_mask = ctx->dest_mask;
1192 if (*ctx->dest_mask == '\0'
1193 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1194 && (!only_one
1195 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1196 || (ctx->dive_into_subdirs
1197 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1198 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1199 ctx->dest_mask = g_strdup ("\\0");
1200 else
1202 ctx->dest_mask = g_strdup (ctx->dest_mask);
1203 *orig_mask = '\0';
1205 if (*dest_dir == '\0')
1207 g_free (dest_dir);
1208 dest_dir = g_strdup ("./");
1210 vfs_path_free (vpath);
1211 if (val == B_USER)
1212 *do_bg = TRUE;
1215 return dest_dir;
1218 /* --------------------------------------------------------------------------------------------- */