doc/HACKING: some obsoleted info was removed.
[kaloumi3.git] / src / filegui.c
blob6d894ca203e80e028f9d1e67819d970b68b7d00d
1 /* File management GUI for the text mode edition
3 * Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 * 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
6 * Written by: 1994, 1995 Janne Kukonlehto
7 * 1994, 1995 Fred Leeflang
8 * 1994, 1995, 1996 Miguel de Icaza
9 * 1995, 1996 Jakub Jelinek
10 * 1997 Norbert Warmuth
11 * 1998 Pavel Machek
12 * 2009 Slava Zanko
14 * The copy code was based in GNU's cp, and was written by:
15 * Torbjorn Granlund, David MacKenzie, and Jim Meyering.
17 * The move code was based in GNU's mv, and was written by:
18 * Mike Parker and David MacKenzie.
20 * Janne Kukonlehto added much error recovery to them for being used
21 * in an interactive program.
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 2 of the License, or
26 * (at your option) any later version.
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
39 * Please note that all dialogs used here must be safe for background
40 * operations.
43 /** \file filegui.c
44 * \brief Source: file management GUI for the text mode edition
47 /* {{{ Include files */
49 #include <config.h>
51 #include <errno.h>
52 #include <ctype.h>
53 #include <stdio.h>
54 #include <string.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
58 #if defined(STAT_STATVFS) \
59 && (defined(HAVE_STRUCT_STATVFS_F_BASETYPE) \
60 || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME))
61 # include <sys/statvfs.h>
62 # define STRUCT_STATFS struct statvfs
63 # define STATFS statvfs
64 #elif defined(HAVE_STATFS) && !defined(STAT_STATFS4)
65 # ifdef HAVE_SYS_VFS_H
66 # include <sys/vfs.h>
67 # elif defined(HAVE_SYS_MOUNT_H) && defined(HAVE_SYS_PARAM_H)
68 # include <sys/param.h>
69 # include <sys/mount.h>
70 # elif defined(HAVE_SYS_STATFS_H)
71 # include <sys/statfs.h>
72 # endif
73 # define STRUCT_STATFS struct statfs
74 # define STATFS statfs
75 #endif
77 #include <unistd.h>
79 #include "lib/global.h"
81 #include "lib/tty/key.h" /* tty_get_event */
82 #include "lib/mcconfig.h"
83 #include "lib/search.h"
84 #include "lib/vfs/mc-vfs/vfs.h"
85 #include "lib/strescape.h"
86 #include "lib/strutil.h"
88 #include "setup.h" /* verbose */
89 #include "dialog.h" /* do_refresh() */
90 #include "widget.h" /* WLabel */
91 #include "main-widgets.h"
92 #include "main.h" /* the_hint */
93 #include "wtools.h" /* QuickDialog */
94 #include "panel.h" /* current_panel */
95 #include "fileopctx.h" /* FILE_CONT */
96 #include "filegui.h"
98 /* }}} */
99 typedef enum {
100 MSDOS_SUPER_MAGIC = 0x4d44,
101 NTFS_SB_MAGIC = 0x5346544e,
102 NTFS_3G_MAGIC = 0x65735546,
103 PROC_SUPER_MAGIC = 0x9fa0,
104 SMB_SUPER_MAGIC = 0x517B,
105 NCP_SUPER_MAGIC = 0x564c,
106 USBDEVICE_SUPER_MAGIC = 0x9fa2
107 } filegui_nonattrs_fs_t;
109 /* Hack: the vfs code should not rely on this */
110 #define WITH_FULL_PATHS 1
112 /* Used for button result values */
113 typedef enum {
114 REPLACE_YES = B_USER,
115 REPLACE_NO,
116 REPLACE_APPEND,
117 REPLACE_ALWAYS,
118 REPLACE_UPDATE,
119 REPLACE_NEVER,
120 REPLACE_ABORT,
121 REPLACE_SIZE,
122 REPLACE_REGET
123 } replace_action_t;
125 /* This structure describes the UI and internal data required by a file
126 * operation context.
128 typedef struct {
129 /* ETA and bps */
130 gboolean showing_eta;
131 gboolean showing_bps;
133 /* Dialog and widgets for the operation progress window */
134 Dlg_head *op_dlg;
135 WLabel *file_string[2];
136 WLabel *file_label[2];
137 WGauge *progress_file_gauge;
138 WLabel *progress_file_label;
140 WGauge *progress_total_gauge;
142 WLabel *total_files_processed_label;
143 WLabel *time_label;
144 WLabel *total_bytes_label;
146 /* Query replace dialog */
147 Dlg_head *replace_dlg;
148 const char *replace_filename;
149 replace_action_t replace_result;
151 struct stat *s_stat, *d_stat;
152 } FileOpContextUI;
155 /* Used to save the hint line */
156 static int last_hint_line;
158 /* File operate window sizes */
159 #define WX 58
160 #define WY 11
161 #define FCOPY_LABEL_X 3
163 static gboolean
164 filegui__check_attrs_on_fs (const char *fs_path)
166 #ifdef STATFS
167 STRUCT_STATFS stfs;
169 if (!setup_copymove_persistent_attr)
170 return FALSE;
172 if (STATFS (fs_path, &stfs) != 0)
173 return TRUE;
175 # ifdef __linux__
176 switch ((filegui_nonattrs_fs_t) stfs.f_type)
178 case MSDOS_SUPER_MAGIC:
179 case NTFS_SB_MAGIC:
180 case NTFS_3G_MAGIC:
181 case PROC_SUPER_MAGIC:
182 case SMB_SUPER_MAGIC:
183 case NCP_SUPER_MAGIC:
184 case USBDEVICE_SUPER_MAGIC:
185 return FALSE;
187 # elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) \
188 || defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
189 if (!strcmp(stfs.f_fstypename, "msdos")
190 || !strcmp(stfs.f_fstypename, "msdosfs")
191 || !strcmp(stfs.f_fstypename, "ntfs")
192 || !strcmp(stfs.f_fstypename, "procfs")
193 || !strcmp(stfs.f_fstypename, "smbfs")
194 || strstr(stfs.f_fstypename, "fusefs"))
195 return FALSE;
196 # elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
197 if (!strcmp(stfs.f_basetype, "pcfs")
198 || !strcmp(stfs.f_basetype, "ntfs")
199 || !strcmp(stfs.f_basetype, "proc")
200 || !strcmp(stfs.f_basetype, "smbfs")
201 || !strcmp(stfs.f_basetype, "fuse"))
202 return FALSE;
203 # endif
204 #endif /* STATFS */
206 return TRUE;
209 FileProgressStatus
210 check_progress_buttons (FileOpContext *ctx)
212 int c;
213 Gpm_Event event;
214 FileOpContextUI *ui;
216 if (ctx->ui == NULL)
217 return FILE_CONT;
219 ui = ctx->ui;
221 event.x = -1; /* Don't show the GPM cursor */
222 c = tty_get_event (&event, FALSE, FALSE);
223 if (c == EV_NONE)
224 return FILE_CONT;
226 /* Reinitialize to avoid old values after events other than
227 selecting a button */
228 ui->op_dlg->ret_value = FILE_CONT;
230 dlg_process_event (ui->op_dlg, c, &event);
231 switch (ui->op_dlg->ret_value) {
232 case FILE_SKIP:
233 return FILE_SKIP;
234 case B_CANCEL:
235 case FILE_ABORT:
236 return FILE_ABORT;
237 default:
238 return FILE_CONT;
242 /* {{{ File progress display routines */
244 void
245 file_op_context_create_ui_without_init (FileOpContext *ctx, gboolean with_eta, filegui_dialog_type_t dialog_type)
247 FileOpContextUI *ui;
248 int minus, total_reserve=0;
249 const char *abort_button_label = N_("&Abort");
250 const char *skip_button_label = N_("&Skip");
251 int abort_button_width, skip_button_width, buttons_width;
252 int dlg_width;
254 g_return_if_fail (ctx != NULL);
255 g_return_if_fail (ctx->ui == NULL);
257 #ifdef ENABLE_NLS
258 abort_button_label = _(abort_button_label);
259 skip_button_label = _(skip_button_label);
260 #endif
262 abort_button_width = str_term_width1 (abort_button_label) + 3;
263 skip_button_width = str_term_width1 (skip_button_label) + 3;
264 buttons_width = abort_button_width + skip_button_width + 2;
266 dlg_width = max (WX, buttons_width + 6);
268 ui = g_new0 (FileOpContextUI, 1);
269 ctx->ui = ui;
271 ctx->dialog_type = dialog_type;
272 minus = verbose ? 0 : 3;
274 switch (dialog_type) {
275 case FILEGUI_DIALOG_ONE_ITEM:
276 total_reserve = 0;
277 break;
278 case FILEGUI_DIALOG_MULTI_ITEM:
279 total_reserve = 5;
280 break;
281 case FILEGUI_DIALOG_DELETE_ITEM:
282 total_reserve = -5;
283 break;
286 ctx->recursive_result = RECURSIVE_YES;
288 ui->replace_result = REPLACE_YES;
289 ui->showing_eta = with_eta;
290 ui->showing_bps = with_eta;
292 ui->op_dlg =
293 create_dlg (0, 0, WY - minus + 1 + total_reserve, dlg_width,
294 dialog_colors, NULL, NULL, op_names[ctx->operation],
295 DLG_CENTER | DLG_REVERSE);
297 last_hint_line = the_hint->widget.y;
298 if ((ui->op_dlg->y + ui->op_dlg->lines) > last_hint_line)
299 the_hint->widget.y = ui->op_dlg->y + ui->op_dlg->lines + 1;
301 add_widget (ui->op_dlg,
302 button_new (WY - minus - 2 + total_reserve,
303 dlg_width/2 + 1, FILE_ABORT,
304 NORMAL_BUTTON, abort_button_label, NULL));
305 add_widget (ui->op_dlg,
306 button_new (WY - minus - 2 + total_reserve,
307 dlg_width/2 - 1 - skip_button_width, FILE_SKIP,
308 NORMAL_BUTTON, skip_button_label, NULL));
311 if (dialog_type == FILEGUI_DIALOG_MULTI_ITEM) {
312 add_widget (ui->op_dlg, hline_new (8, 1, dlg_width - 2));
314 add_widget (ui->op_dlg, ui->total_bytes_label =
315 label_new (8, FCOPY_LABEL_X + 15, ""));
317 add_widget (ui->op_dlg, ui->progress_total_gauge =
318 gauge_new (9, FCOPY_LABEL_X + 3, 0, 100, 0));
320 add_widget (ui->op_dlg, ui->total_files_processed_label =
321 label_new (11, FCOPY_LABEL_X, ""));
323 add_widget (ui->op_dlg, ui->time_label =
324 label_new (12, FCOPY_LABEL_X, ""));
327 add_widget (ui->op_dlg, ui->progress_file_label =
328 label_new (7, FCOPY_LABEL_X, ""));
330 add_widget (ui->op_dlg, ui->progress_file_gauge =
331 gauge_new (6, FCOPY_LABEL_X + 3, 0, 100, 0));
333 add_widget (ui->op_dlg, ui->file_string[1] =
334 label_new (5, FCOPY_LABEL_X, ""));
336 add_widget (ui->op_dlg, ui->file_label[1] =
337 label_new (4, FCOPY_LABEL_X, ""));
338 add_widget (ui->op_dlg, ui->file_string[0] =
339 label_new (3, FCOPY_LABEL_X, ""));
340 add_widget (ui->op_dlg, ui->file_label[0] =
341 label_new (2, FCOPY_LABEL_X, ""));
343 if ((right_panel == current_panel)
344 && !mc_config_get_bool (mc_main_config,"Layout",
345 "classic_progressbar", TRUE)) {
346 ui->progress_file_gauge->from_left_to_right = FALSE;
347 if (dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
348 ui->progress_total_gauge->from_left_to_right = FALSE;
352 void
353 file_op_context_create_ui (FileOpContext *ctx, gboolean with_eta, filegui_dialog_type_t dialog_type)
355 FileOpContextUI *ui;
357 g_return_if_fail (ctx != NULL);
358 g_return_if_fail (ctx->ui == NULL);
360 file_op_context_create_ui_without_init (ctx, with_eta, dialog_type);
361 ui = ctx->ui;
363 /* We will manage the dialog without any help, that's why
364 we have to call init_dlg */
365 init_dlg (ui->op_dlg);
366 ui->op_dlg->running = 1;
369 void
370 file_op_context_destroy_ui (FileOpContext *ctx)
372 FileOpContextUI *ui;
374 g_return_if_fail (ctx != NULL);
376 if (ctx->ui) {
377 ui = ctx->ui;
379 dlg_run_done (ui->op_dlg);
380 destroy_dlg (ui->op_dlg);
381 g_free (ui);
384 the_hint->widget.y = last_hint_line;
386 ctx->ui = NULL;
389 static void
390 file_frmt_time (char *buffer, double eta_secs)
392 int eta_hours, eta_mins, eta_s;
393 eta_hours = eta_secs / (60 * 60);
394 eta_mins = (eta_secs - (eta_hours * 60 * 60)) / 60;
395 eta_s = eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
396 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
399 static void
400 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
402 char _fmt_buff[BUF_TINY];
403 if (eta_secs <= 0.5 && !always_show) {
404 *buffer = '\0';
405 return;
407 if (eta_secs <= 0.5)
408 eta_secs = 1;
409 file_frmt_time (_fmt_buff, eta_secs);
410 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
413 static void
414 file_bps_prepare_for_show (char *buffer, long bps)
416 if (bps > 1024 * 1024) {
417 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"),
418 bps / (1024 * 1024.0));
419 } else if (bps > 1024) {
420 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"),
421 bps / 1024.0);
422 } else if (bps > 1) {
423 g_snprintf (buffer, BUF_TINY, _("%ld B/s"),
424 bps);
425 } else
426 *buffer = 0;
430 show progressbar for file
432 void
433 file_progress_show (FileOpContext *ctx, off_t done, off_t total,
434 const char *stalled_msg, gboolean force_update)
436 FileOpContextUI *ui;
437 char buffer[BUF_TINY];
438 char buffer2[BUF_TINY];
439 char buffer3[BUF_TINY];
441 g_return_if_fail (ctx != NULL);
443 if (ctx->ui == NULL)
444 return;
446 ui = ctx->ui;
448 if (!verbose)
449 return;
451 if (total == 0) {
452 gauge_show (ui->progress_file_gauge, 0);
453 return;
456 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
457 gauge_show (ui->progress_file_gauge, 1);
459 if (!force_update)
460 return;
462 if (ui->showing_eta && ctx->eta_secs > 0.5) {
463 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
464 file_bps_prepare_for_show (buffer3, ctx->bps);
465 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
466 } else {
467 g_snprintf (buffer, BUF_TINY, "%s",stalled_msg);
470 label_set_text (ui->progress_file_label, buffer);
473 void
474 file_progress_show_count (FileOpContext *ctx, off_t done, off_t total)
476 char buffer[BUF_TINY];
477 FileOpContextUI *ui;
479 g_return_if_fail (ctx != NULL);
481 if (ctx->dialog_type != FILEGUI_DIALOG_MULTI_ITEM || ctx->ui == NULL)
482 return;
484 ui = ctx->ui;
486 if (!verbose)
487 return;
489 g_snprintf (buffer, BUF_TINY, _("Files processed: %llu of %llu"), done, total);
491 label_set_text (ui->total_files_processed_label, buffer);
494 void
495 file_progress_show_total (FileOpTotalContext *tctx, FileOpContext *ctx, double copyed_bytes,
496 gboolean need_show_total_summary)
498 char buffer[BUF_TINY];
499 char buffer2[BUF_TINY];
500 char buffer3[BUF_TINY];
501 char buffer4[BUF_TINY];
502 struct timeval tv_current;
503 FileOpContextUI *ui;
505 if (ctx->dialog_type != FILEGUI_DIALOG_MULTI_ITEM || ctx->ui == NULL)
506 return;
508 ui = ctx->ui;
510 if (ctx->progress_bytes > 0 ){
511 gauge_set_value (ui->progress_total_gauge, 1024, (int) (1024 * copyed_bytes/ ctx->progress_bytes));
512 gauge_show (ui->progress_total_gauge, 1);
513 } else
514 gauge_show (ui->progress_total_gauge, 0);
517 if (!need_show_total_summary && tctx->bps == 0)
518 return;
520 gettimeofday (&tv_current, NULL);
521 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
522 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
523 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
527 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
528 label_set_text (ui->time_label, buffer);
530 size_trunc_len (buffer2, 5, tctx->copyed_bytes, 0);
531 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0);
533 g_snprintf (buffer, BUF_TINY, _(" Total: %s of %s "), buffer2, buffer3);
535 label_set_text (ui->total_bytes_label, buffer);
539 /* }}} */
541 #define truncFileString(ui, s) str_trunc (s, 52)
542 #define truncFileStringSecure(ui, s) path_trunc (s, 52)
544 void
545 file_progress_show_source (FileOpContext *ctx, const char *s)
547 FileOpContextUI *ui;
549 g_return_if_fail (ctx != NULL);
551 if (ctx->ui == NULL)
552 return;
554 ui = ctx->ui;
556 if (s != NULL) {
557 #ifdef WITH_FULL_PATHS
558 int i = strlen (current_panel->cwd);
560 /* We remove the full path we have added before */
561 if (!strncmp (s, current_panel->cwd, i)) {
562 if (s[i] == PATH_SEP)
563 s += i + 1;
565 #endif /* WITH_FULL_PATHS */
567 label_set_text (ui->file_label[0], _("Source"));
568 label_set_text (ui->file_string[0], truncFileString (ui, s));
569 } else {
570 label_set_text (ui->file_label[0], "");
571 label_set_text (ui->file_string[0], "");
575 void
576 file_progress_show_target (FileOpContext *ctx, const char *s)
578 FileOpContextUI *ui;
580 g_return_if_fail (ctx != NULL);
582 if (ctx->ui == NULL)
583 return;
585 ui = ctx->ui;
587 if (s != NULL) {
588 label_set_text (ui->file_label[1], _("Target"));
589 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
590 } else {
591 label_set_text (ui->file_label[1], "");
592 label_set_text (ui->file_string[1], "");
596 void
597 file_progress_show_deleting (FileOpContext *ctx, const char *s)
599 FileOpContextUI *ui;
601 g_return_if_fail (ctx != NULL);
603 if (ctx->ui == NULL)
604 return;
606 ui = ctx->ui;
607 label_set_text (ui->file_label[0], _("Deleting"));
608 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
612 * FIXME: probably it is better to replace this with quick dialog machinery,
613 * but actually I'm not familiar with it and have not much time :(
614 * alex
616 static replace_action_t
617 overwrite_query_dialog (FileOpContext *ctx, enum OperationMode mode)
619 #define ADD_RD_BUTTON(i)\
620 add_widget (ui->replace_dlg,\
621 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value,\
622 NORMAL_BUTTON, rd_widgets [i].text, 0))
624 #define ADD_RD_LABEL(i, p1, p2)\
625 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2);\
626 add_widget (ui->replace_dlg,\
627 label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer))
629 /* dialog sizes */
630 const int rd_ylen = 17;
631 int rd_xlen = 60;
633 struct {
634 const char *text;
635 int ypos, xpos;
636 int value; /* 0 for labels */
637 } rd_widgets[] = {
638 /* 0 */ { N_("Target file already exists!"), 3, 4, 0 },
639 /* 1 */ { "%s", 4, 4, 0 },
640 #if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || (defined _LARGE_FILES && _LARGE_FILES)
641 /* 2 */ { N_("Source date: %s, size %llu"), 6, 4, 0 },
642 /* 3 */ { N_("Target date: %s, size %llu"), 7, 4, 0 },
643 #else
644 /* 2 */ { N_("Source date: %s, size %u"), 6, 4, 0 },
645 /* 3 */ { N_("Target date: %s, size %u"), 7, 4, 0 },
646 #endif
647 /* 4 */ { N_("&Abort"), 14, 25, REPLACE_ABORT },
648 /* 5 */ { N_("If &size differs"), 12, 28, REPLACE_SIZE },
649 /* 6 */ { N_("Non&e"), 11, 47, REPLACE_NEVER },
650 /* 7 */ { N_("&Update"), 11, 36, REPLACE_UPDATE },
651 /* 8 */ { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
652 /* 9 */ { N_("Overwrite all targets?"), 11, 4, 0 },
653 /* 10 */ { N_("&Reget"), 10, 28, REPLACE_REGET },
654 /* 11 */ { N_("A&ppend"), 9, 45, REPLACE_APPEND },
655 /* 12 */ { N_("&No"), 9, 37, REPLACE_NO },
656 /* 13 */ { N_("&Yes"), 9, 28, REPLACE_YES },
657 /* 14 */ { N_("Overwrite this target?"), 9, 4, 0 }
660 const int num = sizeof (rd_widgets) / sizeof (rd_widgets[0]);
661 int *widgets_len;
663 FileOpContextUI *ui = ctx->ui;
665 char buffer[BUF_SMALL];
666 const char *title;
667 const char *stripped_name = strip_home_and_password (ui->replace_filename);
668 int stripped_name_len;
670 int result;
672 widgets_len = g_new0 (int, num);
674 if (mode == Foreground)
675 title = _(" File exists ");
676 else
677 title = _(" Background process: File exists ");
679 stripped_name_len = str_term_width1 (stripped_name);
682 int i, l1, l2, l, row;
684 for (i = 0; i < num; i++) {
685 #ifdef ENABLE_NLS
686 if (i != 1) /* skip filename */
687 rd_widgets[i].text = _(rd_widgets[i].text);
688 #endif /* ENABLE_NLS */
689 widgets_len [i] = str_term_width1 (rd_widgets[i].text);
693 * longest of "Overwrite..." labels
694 * (assume "Target date..." are short enough)
696 l1 = max (widgets_len[9], widgets_len[14]);
698 /* longest of button rows */
699 i = num;
700 for (row = l = l2 = 0; i--;)
701 if (rd_widgets[i].value != 0) {
702 if (row != rd_widgets[i].ypos) {
703 row = rd_widgets[i].ypos;
704 l2 = max (l2, l);
705 l = 0;
707 l += widgets_len[i] + 4;
710 l2 = max (l2, l); /* last row */
711 rd_xlen = max (rd_xlen, l1 + l2 + 8);
712 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
713 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
715 /* Now place widgets */
716 l1 += 5; /* start of first button in the row */
717 i = num;
718 for (l = l1, row = 0; --i > 1;)
719 if (rd_widgets[i].value != 0) {
720 if (row != rd_widgets[i].ypos) {
721 row = rd_widgets[i].ypos;
722 l = l1;
724 rd_widgets[i].xpos = l;
725 l += widgets_len[i] + 4;
728 /* Abort button is centered */
729 rd_widgets[4].xpos = (rd_xlen - widgets_len[4] - 3) / 2;
732 /* FIXME - missing help node */
733 ui->replace_dlg =
734 create_dlg (0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, "[Replace]",
735 title, DLG_CENTER | DLG_REVERSE);
737 /* prompt -- centered */
738 add_widget (ui->replace_dlg,
739 label_new (rd_widgets [0].ypos,
740 (rd_xlen - widgets_len [0]) / 2,
741 rd_widgets [0].text));
742 /* file name -- centered */
743 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
744 stripped_name_len = str_term_width1 (stripped_name);
745 add_widget (ui->replace_dlg,
746 label_new (rd_widgets [1].ypos,
747 (rd_xlen - stripped_name_len) / 2,
748 stripped_name));
750 /* source date */
751 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime),
752 (off_t) ui->s_stat->st_size);
753 /* destination date */
754 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime),
755 (off_t) ui->d_stat->st_size);
757 ADD_RD_BUTTON (4); /* Abort */
758 ADD_RD_BUTTON (5); /* If size differs */
759 ADD_RD_BUTTON (6); /* None */
760 ADD_RD_BUTTON (7); /* Update */
761 ADD_RD_BUTTON (8); /* All" */
762 ADD_RD_LABEL (9, 0, 0); /* Overwrite all targets? */
764 /* "this target..." widgets */
765 if (!S_ISDIR (ui->d_stat->st_mode)) {
766 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
767 && (ui->s_stat->st_size > ui->d_stat->st_size))
768 ADD_RD_BUTTON (10); /* Reget */
770 ADD_RD_BUTTON (11); /* Append */
772 ADD_RD_BUTTON (12); /* No */
773 ADD_RD_BUTTON (13); /* Yes */
774 ADD_RD_LABEL (14, 0, 0); /* Overwrite this target? */
776 result = run_dlg (ui->replace_dlg);
777 destroy_dlg (ui->replace_dlg);
779 g_free (widgets_len);
781 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
782 #undef ADD_RD_LABEL
783 #undef ADD_RD_BUTTON
786 FileProgressStatus
787 file_progress_real_query_replace (FileOpContext *ctx,
788 enum OperationMode mode, const char *destname,
789 struct stat *_s_stat,
790 struct stat *_d_stat)
792 FileOpContextUI *ui;
794 g_return_val_if_fail (ctx != NULL, FILE_CONT);
795 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
797 ui = ctx->ui;
799 if (ui->replace_result < REPLACE_ALWAYS) {
800 ui->replace_filename = destname;
801 ui->s_stat = _s_stat;
802 ui->d_stat = _d_stat;
803 ui->replace_result = overwrite_query_dialog (ctx, mode);
806 switch (ui->replace_result) {
807 case REPLACE_UPDATE:
808 do_refresh ();
809 if (_s_stat->st_mtime > _d_stat->st_mtime)
810 return FILE_CONT;
811 else
812 return FILE_SKIP;
814 case REPLACE_SIZE:
815 do_refresh ();
816 if (_s_stat->st_size == _d_stat->st_size)
817 return FILE_SKIP;
818 else
819 return FILE_CONT;
821 case REPLACE_REGET:
822 /* Careful: we fall through and set do_append */
823 ctx->do_reget = _d_stat->st_size;
825 case REPLACE_APPEND:
826 ctx->do_append = TRUE;
828 case REPLACE_YES:
829 case REPLACE_ALWAYS:
830 do_refresh ();
831 return FILE_CONT;
832 case REPLACE_NO:
833 case REPLACE_NEVER:
834 do_refresh ();
835 return FILE_SKIP;
836 case REPLACE_ABORT:
837 default:
838 return FILE_ABORT;
842 static gboolean
843 is_wildcarded (char *p)
845 for (; *p; p++) {
846 if (*p == '*')
847 return TRUE;
848 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
849 return TRUE;
851 return FALSE;
854 char *
855 file_mask_dialog (FileOpContext *ctx, FileOperation operation,
856 gboolean only_one,
857 const char *format, const void *text,
858 const char *def_text, gboolean *do_background)
860 const size_t FMDY = 13;
861 const size_t FMDX = 68;
862 size_t fmd_xlen;
864 /* buttons */
865 const size_t gap = 1;
866 size_t b0_len, b2_len;
867 size_t b1_len = 0;
869 int source_easy_patterns = easy_patterns;
870 size_t i, len;
871 char fmd_buf [BUF_MEDIUM];
872 char *source_mask, *orig_mask, *dest_dir, *tmp;
873 char *def_text_secure;
874 int val;
876 QuickWidget fmd_widgets[] =
878 /* 0 */ QUICK_BUTTON (42, 64, 10, FMDY, N_("&Cancel"), B_CANCEL, NULL),
879 #ifdef WITH_BACKGROUND
880 /* 1 */ QUICK_BUTTON (25, 64, 10, FMDY, N_("&Background"), B_USER, NULL),
881 #define OFFSET 0
882 #else
883 #define OFFSET 1
884 #endif /* WITH_BACKGROUND */
885 /* 2 - OFFSET */
886 QUICK_BUTTON (14, FMDX, 10, FMDY, N_("&OK"), B_ENTER, NULL),
887 /* 3 - OFFSET */
888 QUICK_CHECKBOX (42, FMDX, 8, FMDY, N_("&Stable Symlinks"), &ctx->stable_symlinks),
889 /* 4 - OFFSET */
890 QUICK_CHECKBOX (31, FMDX, 7, FMDY, N_("di&Ve into subdir if exists"), &ctx->dive_into_subdirs),
891 /* 5 - OFFSET */
892 QUICK_CHECKBOX (3, FMDX, 8, FMDY, N_("preserve &Attributes"), &ctx->op_preserve),
893 /* 6 - OFFSET */
894 QUICK_CHECKBOX (3, FMDX, 7, FMDY, N_("follow &Links"), &ctx->follow_links),
895 /* 7 - OFFSET */
896 QUICK_INPUT (3, FMDX, 6, FMDY, "", 58, 0, "input2", &dest_dir),
897 /* 8 - OFFSET */
898 QUICK_LABEL (3, FMDX, 5, FMDY, N_("to:")),
899 /* 9 - OFFSET */
900 QUICK_CHECKBOX (37, FMDX, 4, FMDY, N_("&Using shell patterns"), &source_easy_patterns),
901 /* 10 - OFFSET */
902 QUICK_INPUT (3, FMDX, 3, FMDY, easy_patterns ? "*" : "^\\(.*\\)$", 58, 0, "input-def", &source_mask),
903 /* 11 - OFFSET */
904 QUICK_LABEL (3, FMDX, 2, FMDY, fmd_buf),
905 QUICK_END
908 g_return_val_if_fail (ctx != NULL, NULL);
910 #ifdef ENABLE_NLS
911 /* buttons */
912 for (i = 0; i <= 2 - OFFSET; i++)
913 fmd_widgets[i].u.button.text = _(fmd_widgets[i].u.button.text);
915 /* checkboxes */
916 for (i = 3 - OFFSET; i <= 9 - OFFSET; i++)
917 if (i != 7 - OFFSET)
918 fmd_widgets[i].u.checkbox.text = _(fmd_widgets[i].u.checkbox.text);
919 #endif /* !ENABLE_NLS */
921 fmd_xlen = max (FMDX, (size_t) COLS * 2/3);
923 len = str_term_width1 (fmd_widgets[6 - OFFSET].u.checkbox.text)
924 + str_term_width1 (fmd_widgets[4 - OFFSET].u.checkbox.text) + 15;
925 fmd_xlen = max (fmd_xlen, len);
927 len = str_term_width1 (fmd_widgets[5 - OFFSET].u.checkbox.text)
928 + str_term_width1 (fmd_widgets[3 - OFFSET].u.checkbox.text) + 15;
929 fmd_xlen = max (fmd_xlen, len);
931 /* buttons */
932 b2_len = str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text) + 6 + gap; /* OK */
933 #ifdef WITH_BACKGROUND
934 b1_len = str_term_width1 (fmd_widgets[1].u.button.text) + 4 + gap; /* Background */
935 #endif
936 b0_len = str_term_width1 (fmd_widgets[0].u.button.text) + 4; /* Cancel */
937 len = b0_len + b1_len + b2_len;
938 fmd_xlen = min (max (fmd_xlen, len + 6), (size_t) COLS);
940 if (only_one) {
941 int flen;
943 flen = str_term_width1 (format);
944 i = fmd_xlen - flen - 4; /* FIXME */
945 g_snprintf (fmd_buf, sizeof (fmd_buf),
946 format, str_trunc ((const char *) text, i));
947 } else {
948 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
949 fmd_xlen = max (fmd_xlen, (size_t) str_term_width1 (fmd_buf) + 6);
952 for (i = sizeof (fmd_widgets) / sizeof (fmd_widgets[0]); i > 0; )
953 fmd_widgets[--i].x_divisions = fmd_xlen;
955 i = (fmd_xlen - len)/2;
956 /* OK button */
957 fmd_widgets[2 - OFFSET].relative_x = i;
958 i += b2_len;
959 #ifdef WITH_BACKGROUND
960 /* Background button */
961 fmd_widgets[1].relative_x = i;
962 i += b1_len;
963 #endif
964 /* Cancel button */
965 fmd_widgets[0].relative_x = i;
967 #define chkbox_xpos(i) \
968 fmd_widgets [i].relative_x = fmd_xlen - str_term_width1 (fmd_widgets [i].u.checkbox.text) - 6
969 chkbox_xpos (3 - OFFSET);
970 chkbox_xpos (4 - OFFSET);
971 chkbox_xpos (9 - OFFSET);
972 #undef chkbox_xpos
974 /* inputs */
975 fmd_widgets[ 7 - OFFSET].u.input.len =
976 fmd_widgets[10 - OFFSET].u.input.len = fmd_xlen - 6;
978 /* unselect checkbox if target filesystem don't support attributes */
979 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
981 /* filter out a possible password from def_text */
982 tmp = strip_password (g_strdup (def_text), 1);
983 if (source_easy_patterns)
984 def_text_secure = strutils_glob_escape (tmp);
985 else
986 def_text_secure = strutils_regex_escape (tmp);
987 g_free (tmp);
989 /* destination */
990 fmd_widgets[7 - OFFSET].u.input.text = def_text_secure;
992 ctx->stable_symlinks = FALSE;
993 *do_background = FALSE;
996 struct stat buf;
998 QuickDialog Quick_input =
1000 fmd_xlen, FMDY, -1, -1, op_names [operation],
1001 "[Mask Copy/Rename]", fmd_widgets, TRUE
1004 ask_file_mask:
1005 val = quick_dialog_skip (&Quick_input, 4);
1007 if (val == B_CANCEL) {
1008 g_free (def_text_secure);
1009 return NULL;
1012 if (ctx->follow_links)
1013 ctx->stat_func = mc_stat;
1014 else
1015 ctx->stat_func = mc_lstat;
1017 if (ctx->op_preserve) {
1018 ctx->preserve = TRUE;
1019 ctx->umask_kill = 0777777;
1020 ctx->preserve_uidgid = (geteuid () == 0);
1021 } else {
1022 int i2;
1023 ctx->preserve = ctx->preserve_uidgid = FALSE;
1024 i2 = umask (0);
1025 umask (i2);
1026 ctx->umask_kill = i2 ^ 0777777;
1029 if ((dest_dir == NULL) || (*dest_dir == '\0')) {
1030 g_free (def_text_secure);
1031 g_free (source_mask);
1032 return dest_dir;
1035 ctx->search_handle = mc_search_new(source_mask,-1);
1037 if (ctx->search_handle == NULL) {
1038 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"),
1039 source_mask);
1040 g_free (dest_dir);
1041 g_free (source_mask);
1042 goto ask_file_mask;
1045 g_free (def_text_secure);
1046 g_free (source_mask);
1048 ctx->search_handle->is_case_sentitive = TRUE;
1049 if (source_easy_patterns)
1050 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1051 else
1052 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1054 tmp = dest_dir;
1055 dest_dir = tilde_expand (tmp);
1056 g_free (tmp);
1058 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1059 if (ctx->dest_mask == NULL)
1060 ctx->dest_mask = dest_dir;
1061 else
1062 ctx->dest_mask++;
1063 orig_mask = ctx->dest_mask;
1064 if (!*ctx->dest_mask
1065 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1066 && (!only_one
1067 || (!mc_stat (dest_dir, &buf) && S_ISDIR (buf.st_mode))))
1068 || (ctx->dive_into_subdirs
1069 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1070 || (only_one && !mc_stat (dest_dir, &buf)
1071 && S_ISDIR (buf.st_mode)))))
1072 ctx->dest_mask = g_strdup ("\\0");
1073 else {
1074 ctx->dest_mask = g_strdup (ctx->dest_mask);
1075 *orig_mask = '\0';
1077 if (!*dest_dir) {
1078 g_free (dest_dir);
1079 dest_dir = g_strdup ("./");
1081 if (val == B_USER)
1082 *do_background = TRUE;
1085 return dest_dir;