Merge branch '54_dlg_mouse_handling'
[midnight-commander.git] / src / filegui.c
blob43178e90f8834fda7991e189c3793300c45494df
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 (__FreeBSD__)
59 # include <sys/param.h>
60 #endif
61 #if defined(__APPLE__) || defined (__FreeBSD__)
62 # include <sys/mount.h>
63 #elif defined (__NetBSD__)
64 # include <sys/param.h>
65 #else
66 # ifdef HAVE_VFS
67 # include <sys/vfs.h>
68 # else
69 # include <sys/statfs.h>
70 # endif
71 #endif
73 #include <unistd.h>
75 #include "global.h"
77 #include "../src/tty/key.h" /* tty_get_event */
79 #include "../src/search/search.h"
81 #include "setup.h" /* verbose */
82 #include "dialog.h" /* do_refresh() */
83 #include "widget.h" /* WLabel */
84 #include "main-widgets.h"
85 #include "main.h" /* the_hint */
86 #include "wtools.h" /* QuickDialog */
87 #include "panel.h" /* current_panel */
88 #include "fileopctx.h" /* FILE_CONT */
89 #include "filegui.h"
90 #include "util.h" /* strip_password() */
91 #include "strutil.h"
92 #include "../src/strescape.h"
94 /* }}} */
95 typedef enum {
96 MSDOS_SUPER_MAGIC = 0x4d44,
97 NTFS_SB_MAGIC = 0x5346544e,
98 NTFS_3G_MAGIC = 0x65735546,
99 PROC_SUPER_MAGIC = 0x9fa0,
100 SMB_SUPER_MAGIC = 0x517B,
101 NCP_SUPER_MAGIC = 0x564c,
102 USBDEVICE_SUPER_MAGIC = 0x9fa2
103 } filegui_nonattrs_fs_t;
105 /* Hack: the vfs code should not rely on this */
106 #define WITH_FULL_PATHS 1
108 /* This structure describes the UI and internal data required by a file
109 * operation context.
111 typedef struct {
112 /* ETA and bps */
114 int showing_eta;
115 int showing_bps;
116 int eta_extra;
118 /* Dialog and widgets for the operation progress window */
120 Dlg_head *op_dlg;
122 WLabel *file_label[2];
123 WLabel *file_string[2];
124 WLabel *progress_label[3];
125 WGauge *progress_gauge[3];
126 WLabel *eta_label;
127 WLabel *bps_label;
128 WLabel *stalled_label;
130 /* Query replace dialog */
132 Dlg_head *replace_dlg;
133 const char *replace_filename;
134 int replace_result;
135 struct stat *s_stat, *d_stat;
136 } FileOpContextUI;
139 /* Used to save the hint line */
140 static int last_hint_line;
142 /* File operate window sizes */
143 #define WX 62
144 #define WY 10
145 #define BY 10
146 #define WX_ETA_EXTRA 12
148 #define FCOPY_GAUGE_X 14
149 #define FCOPY_LABEL_X 5
151 /* Used for button result values */
152 enum {
153 REPLACE_YES = B_USER,
154 REPLACE_NO,
155 REPLACE_APPEND,
156 REPLACE_ALWAYS,
157 REPLACE_UPDATE,
158 REPLACE_NEVER,
159 REPLACE_ABORT,
160 REPLACE_SIZE,
161 REPLACE_REGET
164 static int
165 filegui__check_attrs_on_fs(const char *fs_path)
167 struct statfs stfs;
169 if (statfs(fs_path, &stfs)!=0)
170 return 1;
172 switch ((filegui_nonattrs_fs_t) stfs.f_type)
174 case MSDOS_SUPER_MAGIC:
175 case NTFS_SB_MAGIC:
176 case NTFS_3G_MAGIC:
177 case PROC_SUPER_MAGIC:
178 case SMB_SUPER_MAGIC:
179 case NCP_SUPER_MAGIC:
180 case USBDEVICE_SUPER_MAGIC:
181 return 0;
182 break;
184 return 1;
187 static FileProgressStatus
188 check_progress_buttons (FileOpContext *ctx)
190 int c;
191 Gpm_Event event;
192 FileOpContextUI *ui;
194 if (ctx->ui == NULL)
195 return FILE_CONT;
197 ui = ctx->ui;
199 event.x = -1; /* Don't show the GPM cursor */
200 c = tty_get_event (&event, FALSE, FALSE);
201 if (c == EV_NONE)
202 return FILE_CONT;
204 /* Reinitialize to avoid old values after events other than
205 selecting a button */
206 ui->op_dlg->ret_value = FILE_CONT;
208 dlg_process_event (ui->op_dlg, c, &event);
209 switch (ui->op_dlg->ret_value) {
210 case FILE_SKIP:
211 return FILE_SKIP;
212 break;
213 case B_CANCEL:
214 case FILE_ABORT:
215 return FILE_ABORT;
216 break;
217 default:
218 return FILE_CONT;
222 /* {{{ File progress display routines */
224 void
225 file_op_context_create_ui (FileOpContext *ctx, int with_eta)
227 FileOpContextUI *ui;
228 int x_size;
229 int minus;
230 int eta_offset;
231 const char *sixty;
232 const char *fifteen;
234 g_return_if_fail (ctx != NULL);
235 g_return_if_fail (ctx->ui == NULL);
237 ui = g_new0 (FileOpContextUI, 1);
238 ctx->ui = ui;
240 minus = verbose ? 0 : 3;
241 eta_offset = with_eta ? (WX_ETA_EXTRA) / 2 : 0;
243 sixty = "";
244 fifteen = "";
246 ctx->recursive_result = 0;
248 ui->replace_result = 0;
249 ui->showing_eta = with_eta;
250 ui->showing_bps = with_eta;
251 ui->eta_extra = with_eta ? WX_ETA_EXTRA : 0;
252 x_size = (WX + 4) + ui->eta_extra;
254 ui->op_dlg =
255 create_dlg (0, 0, WY - minus + 4, x_size, dialog_colors, NULL,
256 NULL, op_names[ctx->operation],
257 DLG_CENTER | DLG_REVERSE);
259 last_hint_line = the_hint->widget.y;
260 if ((ui->op_dlg->y + ui->op_dlg->lines) > last_hint_line)
261 the_hint->widget.y = ui->op_dlg->y + ui->op_dlg->lines + 1;
263 add_widget (ui->op_dlg,
264 button_new (BY - minus, WX - 19 + eta_offset, FILE_ABORT,
265 NORMAL_BUTTON, _("&Abort"), 0));
266 add_widget (ui->op_dlg,
267 button_new (BY - minus, 14 + eta_offset, FILE_SKIP,
268 NORMAL_BUTTON, _("&Skip"), 0));
270 add_widget (ui->op_dlg, ui->progress_gauge[2] =
271 gauge_new (7, FCOPY_GAUGE_X, 0, 100, 0));
272 add_widget (ui->op_dlg, ui->progress_label[2] =
273 label_new (7, FCOPY_LABEL_X, fifteen));
274 add_widget (ui->op_dlg, ui->bps_label = label_new (7, WX, ""));
276 add_widget (ui->op_dlg, ui->progress_gauge[1] =
277 gauge_new (8, FCOPY_GAUGE_X, 0, 100, 0));
278 add_widget (ui->op_dlg, ui->progress_label[1] =
279 label_new (8, FCOPY_LABEL_X, fifteen));
280 add_widget (ui->op_dlg, ui->stalled_label = label_new (8, WX, ""));
282 add_widget (ui->op_dlg, ui->progress_gauge[0] =
283 gauge_new (6, FCOPY_GAUGE_X, 0, 100, 0));
284 add_widget (ui->op_dlg, ui->progress_label[0] =
285 label_new (6, FCOPY_LABEL_X, fifteen));
286 add_widget (ui->op_dlg, ui->eta_label = label_new (6, WX, ""));
288 add_widget (ui->op_dlg, ui->file_string[1] =
289 label_new (4, FCOPY_GAUGE_X, sixty));
290 add_widget (ui->op_dlg, ui->file_label[1] =
291 label_new (4, FCOPY_LABEL_X, fifteen));
292 add_widget (ui->op_dlg, ui->file_string[0] =
293 label_new (3, FCOPY_GAUGE_X, sixty));
294 add_widget (ui->op_dlg, ui->file_label[0] =
295 label_new (3, FCOPY_LABEL_X, fifteen));
297 /* We will manage the dialog without any help, that's why
298 we have to call init_dlg */
299 init_dlg (ui->op_dlg);
300 ui->op_dlg->running = 1;
303 void
304 file_op_context_destroy_ui (FileOpContext *ctx)
306 FileOpContextUI *ui;
308 g_return_if_fail (ctx != NULL);
310 if (ctx->ui) {
311 ui = ctx->ui;
313 dlg_run_done (ui->op_dlg);
314 destroy_dlg (ui->op_dlg);
315 g_free (ui);
318 the_hint->widget.y = last_hint_line;
320 ctx->ui = NULL;
323 static FileProgressStatus
324 show_no_bar (FileOpContext *ctx, int n)
326 FileOpContextUI *ui;
328 if (ctx->ui == NULL)
329 return FILE_CONT;
331 ui = ctx->ui;
333 if (n >= 0) {
334 label_set_text (ui->progress_label[n], "");
335 gauge_show (ui->progress_gauge[n], 0);
337 return check_progress_buttons (ctx);
340 static FileProgressStatus
341 show_bar (FileOpContext *ctx, int n, double done, double total)
343 FileOpContextUI *ui;
345 if (ctx->ui == NULL)
346 return FILE_CONT;
348 ui = ctx->ui;
351 * Gauge needs integers, so give it with integers between 0 and 1023.
352 * This precision should be quite reasonable.
354 gauge_set_value (ui->progress_gauge[n], 1024,
355 (int) (1024 * done / total));
356 gauge_show (ui->progress_gauge[n], 1);
357 return check_progress_buttons (ctx);
360 static void
361 file_eta_show (FileOpContext *ctx)
363 int eta_hours, eta_mins, eta_s;
364 char eta_buffer[BUF_TINY];
365 FileOpContextUI *ui;
367 if (ctx->ui == NULL)
368 return;
370 ui = ctx->ui;
372 if (!ui->showing_eta)
373 return;
375 if (ctx->eta_secs > 0.5) {
376 eta_hours = ctx->eta_secs / (60 * 60);
377 eta_mins = (ctx->eta_secs - (eta_hours * 60 * 60)) / 60;
378 eta_s = ctx->eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
379 g_snprintf (eta_buffer, sizeof (eta_buffer), _("ETA %d:%02d.%02d"),
380 eta_hours, eta_mins, eta_s);
381 } else
382 *eta_buffer = 0;
384 label_set_text (ui->eta_label, eta_buffer);
387 static void
388 file_bps_show (FileOpContext *ctx)
390 char bps_buffer[BUF_TINY];
391 FileOpContextUI *ui;
393 if (ctx->ui == NULL)
394 return;
396 ui = ctx->ui;
398 if (!ui->showing_bps)
399 return;
401 if (ctx->bps > 1024 * 1024) {
402 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%.2f MB/s"),
403 ctx->bps / (1024 * 1024.0));
404 } else if (ctx->bps > 1024) {
405 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%.2f KB/s"),
406 ctx->bps / 1024.0);
407 } else if (ctx->bps > 1) {
408 g_snprintf (bps_buffer, sizeof (bps_buffer), _("%ld B/s"),
409 ctx->bps);
410 } else
411 *bps_buffer = 0;
413 label_set_text (ui->bps_label, bps_buffer);
416 FileProgressStatus
417 file_progress_show (FileOpContext *ctx, off_t done, off_t total)
419 FileOpContextUI *ui;
421 g_return_val_if_fail (ctx != NULL, FILE_CONT);
423 if (ctx->ui == NULL)
424 return FILE_CONT;
426 ui = ctx->ui;
428 if (!verbose)
429 return check_progress_buttons (ctx);
430 if (total > 0) {
431 label_set_text (ui->progress_label[0], _("File"));
432 file_eta_show (ctx);
433 file_bps_show (ctx);
434 return show_bar (ctx, 0, done, total);
435 } else
436 return show_no_bar (ctx, 0);
439 FileProgressStatus
440 file_progress_show_count (FileOpContext *ctx, off_t done, off_t total)
442 FileOpContextUI *ui;
444 g_return_val_if_fail (ctx != NULL, FILE_CONT);
446 if (ctx->ui == NULL)
447 return FILE_CONT;
449 ui = ctx->ui;
451 if (!verbose)
452 return check_progress_buttons (ctx);
453 if (total > 0) {
454 label_set_text (ui->progress_label[1], _("Count"));
455 return show_bar (ctx, 1, done, total);
456 } else
457 return show_no_bar (ctx, 1);
460 FileProgressStatus
461 file_progress_show_bytes (FileOpContext *ctx, double done, double total)
463 FileOpContextUI *ui;
465 g_return_val_if_fail (ctx != NULL, FILE_CONT);
467 if (ctx->ui == NULL)
468 return FILE_CONT;
470 ui = ctx->ui;
472 if (!verbose)
473 return check_progress_buttons (ctx);
474 if (total > 0) {
475 label_set_text (ui->progress_label[2], _("Bytes"));
476 return show_bar (ctx, 2, done, total);
477 } else
478 return show_no_bar (ctx, 2);
481 /* }}} */
483 #define truncFileString(ui, s) str_trunc (s, ui->eta_extra + 47)
484 #define truncFileStringSecure(ui, s) path_trunc (s, ui->eta_extra + 47)
486 FileProgressStatus
487 file_progress_show_source (FileOpContext *ctx, const char *s)
489 FileOpContextUI *ui;
491 g_return_val_if_fail (ctx != NULL, FILE_CONT);
493 if (ctx->ui == NULL)
494 return FILE_CONT;
496 ui = ctx->ui;
498 if (s != NULL) {
499 #ifdef WITH_FULL_PATHS
500 int i = strlen (current_panel->cwd);
502 /* We remove the full path we have added before */
503 if (!strncmp (s, current_panel->cwd, i)) {
504 if (s[i] == PATH_SEP)
505 s += i + 1;
507 #endif /* WITH_FULL_PATHS */
509 label_set_text (ui->file_label[0], _("Source"));
510 label_set_text (ui->file_string[0], truncFileString (ui, s));
511 return check_progress_buttons (ctx);
512 } else {
513 label_set_text (ui->file_label[0], "");
514 label_set_text (ui->file_string[0], "");
515 return check_progress_buttons (ctx);
519 FileProgressStatus
520 file_progress_show_target (FileOpContext *ctx, const char *s)
522 FileOpContextUI *ui;
524 g_return_val_if_fail (ctx != NULL, FILE_CONT);
526 if (ctx->ui == NULL)
527 return FILE_CONT;
529 ui = ctx->ui;
531 if (s != NULL) {
532 label_set_text (ui->file_label[1], _("Target"));
533 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
534 return check_progress_buttons (ctx);
535 } else {
536 label_set_text (ui->file_label[1], "");
537 label_set_text (ui->file_string[1], "");
538 return check_progress_buttons (ctx);
542 FileProgressStatus
543 file_progress_show_deleting (FileOpContext *ctx, const char *s)
545 FileOpContextUI *ui;
547 g_return_val_if_fail (ctx != NULL, FILE_CONT);
549 if (ctx->ui == NULL)
550 return FILE_CONT;
552 ui = ctx->ui;
554 label_set_text (ui->file_label[0], _("Deleting"));
555 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
556 return check_progress_buttons (ctx);
560 * FIXME: probably it is better to replace this with quick dialog machinery,
561 * but actually I'm not familiar with it and have not much time :(
562 * alex
566 static int
567 overwrite_query_dialog (FileOpContext *ctx, enum OperationMode mode)
569 #define ADD_RD_BUTTON(i)\
570 add_widget (ui->replace_dlg,\
571 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value,\
572 NORMAL_BUTTON, rd_widgets [i].text, 0))
574 #define ADD_RD_LABEL(i, p1, p2)\
575 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2);\
576 add_widget (ui->replace_dlg,\
577 label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer))
579 /* dialog sizes */
580 const int rd_ylen = 17;
581 int rd_xlen = 60;
583 struct {
584 const char *text;
585 int ypos, xpos;
586 int value; /* 0 for labels */
587 } rd_widgets[] = {
588 /* 0 */ { N_("Target file already exists!"), 3, 4, 0 },
589 /* 1 */ { "%s", 4, 4, 0 },
590 #if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || (defined _LARGE_FILES && _LARGE_FILES)
591 /* 2 */ { N_("Source date: %s, size %llu"), 6, 4, 0 },
592 /* 3 */ { N_("Target date: %s, size %llu"), 7, 4, 0 },
593 #else
594 /* 2 */ { N_("Source date: %s, size %u"), 6, 4, 0 },
595 /* 3 */ { N_("Target date: %s, size %u"), 7, 4, 0 },
596 #endif
597 /* 4 */ { N_("&Abort"), 14, 25, REPLACE_ABORT },
598 /* 5 */ { N_("If &size differs"), 12, 28, REPLACE_SIZE },
599 /* 6 */ { N_("Non&e"), 11, 47, REPLACE_NEVER },
600 /* 7 */ { N_("&Update"), 11, 36, REPLACE_UPDATE },
601 /* 8 */ { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
602 /* 9 */ { N_("Overwrite all targets?"), 11, 4, 0 },
603 /* 10 */ { N_("&Reget"), 10, 28, REPLACE_REGET },
604 /* 11 */ { N_("A&ppend"), 9, 45, REPLACE_APPEND },
605 /* 12 */ { N_("&No"), 9, 37, REPLACE_NO },
606 /* 13 */ { N_("&Yes"), 9, 28, REPLACE_YES },
607 /* 14 */ { N_("Overwrite this target?"), 9, 4, 0 }
610 const int num = sizeof (rd_widgets) / sizeof (rd_widgets[0]);
611 int *widgets_len;
613 FileOpContextUI *ui = ctx->ui;
615 char buffer[BUF_SMALL];
616 const char *title;
617 const char *stripped_name = strip_home_and_password (ui->replace_filename);
618 int stripped_name_len;
620 int result;
622 widgets_len = g_new0 (int, num);
624 if (mode == Foreground)
625 title = _(" File exists ");
626 else
627 title = _(" Background process: File exists ");
629 stripped_name_len = str_term_width1 (stripped_name);
632 int i, l1, l2, l, row;
634 for (i = 0; i < num; i++) {
635 #ifdef ENABLE_NLS
636 if (i != 1) /* skip filename */
637 rd_widgets[i].text = _(rd_widgets[i].text);
638 #endif /* ENABLE_NLS */
639 widgets_len [i] = str_term_width1 (rd_widgets[i].text);
643 * longest of "Overwrite..." labels
644 * (assume "Target date..." are short enough)
646 l1 = max (widgets_len[9], widgets_len[14]);
648 /* longest of button rows */
649 i = num;
650 for (row = l = l2 = 0; i--;)
651 if (rd_widgets[i].value != 0) {
652 if (row != rd_widgets[i].ypos) {
653 row = rd_widgets[i].ypos;
654 l2 = max (l2, l);
655 l = 0;
657 l += widgets_len[i] + 4;
660 l2 = max (l2, l); /* last row */
661 rd_xlen = max (rd_xlen, l1 + l2 + 8);
662 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
663 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
665 /* Now place widgets */
666 l1 += 5; /* start of first button in the row */
667 i = num;
668 for (l = l1, row = 0; --i > 1;)
669 if (rd_widgets[i].value != 0) {
670 if (row != rd_widgets[i].ypos) {
671 row = rd_widgets[i].ypos;
672 l = l1;
674 rd_widgets[i].xpos = l;
675 l += widgets_len[i] + 4;
678 /* Abort button is centered */
679 rd_widgets[4].xpos = (rd_xlen - widgets_len[4] - 3) / 2;
682 /* FIXME - missing help node */
683 ui->replace_dlg =
684 create_dlg (0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, "[Replace]",
685 title, DLG_CENTER | DLG_REVERSE);
687 /* prompt -- centered */
688 add_widget (ui->replace_dlg,
689 label_new (rd_widgets [0].ypos,
690 (rd_xlen - widgets_len [0]) / 2,
691 rd_widgets [0].text));
692 /* file name -- centered */
693 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
694 stripped_name_len = str_term_width1 (stripped_name);
695 add_widget (ui->replace_dlg,
696 label_new (rd_widgets [1].ypos,
697 (rd_xlen - stripped_name_len) / 2,
698 stripped_name));
700 /* source date */
701 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime),
702 (off_t) ui->s_stat->st_size);
703 /* destination date */
704 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime),
705 (off_t) ui->d_stat->st_size);
707 ADD_RD_BUTTON (4); /* Abort */
708 ADD_RD_BUTTON (5); /* If size differs */
709 ADD_RD_BUTTON (6); /* None */
710 ADD_RD_BUTTON (7); /* Update */
711 ADD_RD_BUTTON (8); /* All" */
712 ADD_RD_LABEL (9, 0, 0); /* Overwrite all targets? */
714 /* "this target..." widgets */
715 if (!S_ISDIR (ui->d_stat->st_mode)) {
716 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
717 && (ui->s_stat->st_size > ui->d_stat->st_size))
718 ADD_RD_BUTTON (10); /* Reget */
720 ADD_RD_BUTTON (11); /* Append */
722 ADD_RD_BUTTON (12); /* No */
723 ADD_RD_BUTTON (13); /* Yes */
724 ADD_RD_LABEL (14, 0, 0); /* Overwrite this target? */
726 result = run_dlg (ui->replace_dlg);
727 destroy_dlg (ui->replace_dlg);
729 g_free (widgets_len);
731 return result;
732 #undef ADD_RD_LABEL
733 #undef ADD_RD_BUTTON
736 void
737 file_progress_set_stalled_label (FileOpContext *ctx, const char *stalled_msg)
739 FileOpContextUI *ui;
741 g_return_if_fail (ctx != NULL);
742 g_return_if_fail (ctx->ui != NULL);
744 ui = ctx->ui;
745 label_set_text (ui->stalled_label, stalled_msg);
748 FileProgressStatus
749 file_progress_real_query_replace (FileOpContext *ctx,
750 enum OperationMode mode, const char *destname,
751 struct stat *_s_stat,
752 struct stat *_d_stat)
754 FileOpContextUI *ui;
756 g_return_val_if_fail (ctx != NULL, FILE_CONT);
757 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
759 ui = ctx->ui;
761 if (ui->replace_result < REPLACE_ALWAYS) {
762 ui->replace_filename = destname;
763 ui->s_stat = _s_stat;
764 ui->d_stat = _d_stat;
765 ui->replace_result = overwrite_query_dialog (ctx, mode);
766 if (ui->replace_result == B_CANCEL)
767 ui->replace_result = REPLACE_ABORT;
770 switch (ui->replace_result) {
771 case REPLACE_UPDATE:
772 do_refresh ();
773 if (_s_stat->st_mtime > _d_stat->st_mtime)
774 return FILE_CONT;
775 else
776 return FILE_SKIP;
778 case REPLACE_SIZE:
779 do_refresh ();
780 if (_s_stat->st_size == _d_stat->st_size)
781 return FILE_SKIP;
782 else
783 return FILE_CONT;
785 case REPLACE_REGET:
786 /* Careful: we fall through and set do_append */
787 ctx->do_reget = _d_stat->st_size;
789 case REPLACE_APPEND:
790 ctx->do_append = 1;
792 case REPLACE_YES:
793 case REPLACE_ALWAYS:
794 do_refresh ();
795 return FILE_CONT;
796 case REPLACE_NO:
797 case REPLACE_NEVER:
798 do_refresh ();
799 return FILE_SKIP;
800 case REPLACE_ABORT:
801 default:
802 return FILE_ABORT;
806 static gboolean
807 is_wildcarded (char *p)
809 for (; *p; p++) {
810 if (*p == '*')
811 return TRUE;
812 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
813 return TRUE;
815 return FALSE;
818 char *
819 file_mask_dialog (FileOpContext *ctx, FileOperation operation, const char *text,
820 const char *def_text, int only_one, int *do_background)
822 const size_t FMDY = 13;
823 const size_t FMDX = 64;
824 size_t fmd_xlen = 0;
826 int source_easy_patterns = easy_patterns;
827 size_t i, len;
828 char *source_mask, *orig_mask, *dest_dir, *tmp;
829 char *def_text_secure;
830 int val;
832 #ifdef ENABLE_NLS
833 static gboolean i18n = FALSE;
834 #endif /* !ENABLE_NLS */
836 QuickWidget fmd_widgets[] =
838 /* 0 */ QUICK_BUTTON (42, 64, 10, FMDY, N_("&Cancel"), B_CANCEL, NULL),
839 #ifdef WITH_BACKGROUND
840 /* 1 */ QUICK_BUTTON (25, 64, 10, FMDY, N_("&Background"), B_USER, NULL),
841 #define OFFSET 0
842 #else
843 #define OFFSET 1
844 #endif /* WITH_BACKGROUND */
845 /* 2 - OFFSET */
846 QUICK_BUTTON (14, FMDX, 10, FMDY, N_("&OK"), B_ENTER, NULL),
847 /* 3 - OFFSET */
848 QUICK_CHECKBOX (42, FMDX, 8, FMDY, N_("&Stable Symlinks"), &ctx->stable_symlinks),
849 /* 4 - OFFSET */
850 QUICK_CHECKBOX (31, FMDX, 7, FMDY, N_("di&Ve into subdir if exists"), &ctx->dive_into_subdirs),
851 /* 5 - OFFSET */
852 QUICK_CHECKBOX (3, FMDX, 8, FMDY, N_("preserve &Attributes"), &ctx->op_preserve),
853 /* 6 - OFFSET */
854 QUICK_CHECKBOX (3, FMDX, 7, FMDY, N_("follow &Links"), &ctx->follow_links),
855 /* 7 - OFFSET */
856 QUICK_INPUT (3, FMDX, 6, FMDY, "", 58, 0, "input2", &dest_dir),
857 /* 8 - OFFSET */
858 QUICK_LABEL (3, FMDX, 5, FMDY, N_("to:")),
859 /* 9 - OFFSET */
860 QUICK_CHECKBOX (37, FMDX, 4, FMDY, N_("&Using shell patterns"), &source_easy_patterns),
861 /* 10 - OFFSET */
862 QUICK_INPUT (3, FMDX, 3, FMDY, easy_patterns ? "*" : "^\\(.*\\)$", 58, 0, "input-def", &source_mask),
863 /* 11 - OFFSET */
864 QUICK_LABEL (3, FMDX, 2, FMDY, text),
865 QUICK_END
868 g_return_val_if_fail (ctx != NULL, NULL);
870 #ifdef ENABLE_NLS
871 if (!i18n) {
872 for (i = sizeof (op_names) / sizeof (op_names[0]); i--;)
873 op_names[i] = _(op_names[i]);
875 i18n = TRUE;
878 /* buttons */
879 for (i = 0; i <= 2 - OFFSET; i++)
880 fmd_widgets[i].u.button.text = _(fmd_widgets[i].u.button.text);
882 /* checkboxes */
883 for (i = 3 - OFFSET; i <= 9 - OFFSET; i++)
884 if (i != 7 - OFFSET)
885 fmd_widgets[i].u.checkbox.text = _(fmd_widgets[i].u.checkbox.text);
886 #endif /* !ENABLE_NLS */
888 len = str_term_width1 (fmd_widgets[6 - OFFSET].u.checkbox.text)
889 + str_term_width1 (fmd_widgets[4 - OFFSET].u.checkbox.text) + 15;
890 fmd_xlen = max (fmd_xlen, len);
892 len = str_term_width1 (fmd_widgets[5 - OFFSET].u.checkbox.text)
893 + str_term_width1 (fmd_widgets[3 - OFFSET].u.checkbox.text) + 15;
894 fmd_xlen = max (fmd_xlen, len);
896 /* buttons */
897 len = str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text)
898 + str_term_width1 (fmd_widgets[0].u.button.text) + 11;
899 #ifdef WITH_BACKGROUND
900 len += str_term_width1 (fmd_widgets[1].u.button.text) + 6;
901 #endif
902 fmd_xlen = max (fmd_xlen, len + 4);
904 len = (fmd_xlen - (len + 6)) / 2;
905 i = len + 3;
906 fmd_widgets[2 - OFFSET].relative_x = i;
907 i += str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text) + 8;
909 #ifdef WITH_BACKGROUND
910 fmd_widgets[1].relative_x = i;
911 i += str_term_width1 (fmd_widgets[1].u.button.text) + 6;
912 #endif
914 fmd_widgets[0].relative_x = i;
916 #define chkbox_xpos(i) \
917 fmd_widgets [i].relative_x = fmd_xlen - str_term_width1 (fmd_widgets [i].u.checkbox.text) - 6
918 chkbox_xpos (3 - OFFSET);
919 chkbox_xpos (4 - OFFSET);
920 chkbox_xpos (9 - OFFSET);
921 #undef chkbox_xpos
923 if (fmd_xlen != FMDX) {
924 i = sizeof (fmd_widgets) / sizeof (fmd_widgets[0]) - 1;
925 while (i--)
926 fmd_widgets[i].x_divisions = fmd_xlen;
928 /* inputs */
929 fmd_widgets[ 7 - OFFSET].u.input.len =
930 fmd_widgets[10 - OFFSET].u.input.len = fmd_xlen - 6;
933 /* unselect checkbox if target filesystem don't support attributes */
934 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
936 /* filter out a possible password from def_text */
937 tmp = strip_password (g_strdup (def_text), 1);
938 if (source_easy_patterns)
939 def_text_secure = strutils_glob_escape (tmp);
940 else
941 def_text_secure = strutils_regex_escape (tmp);
942 g_free (tmp);
944 /* destination */
945 fmd_widgets[7 - OFFSET].u.input.text = def_text_secure;
947 ctx->stable_symlinks = 0;
948 *do_background = 0;
951 struct stat buf;
953 QuickDialog Quick_input =
955 fmd_xlen, FMDY, -1, -1, op_names [operation],
956 "[Mask Copy/Rename]", fmd_widgets, TRUE
959 ask_file_mask:
960 val = quick_dialog_skip (&Quick_input, 4);
962 if (val == B_CANCEL) {
963 g_free (def_text_secure);
964 return NULL;
967 if (ctx->follow_links)
968 ctx->stat_func = mc_stat;
969 else
970 ctx->stat_func = mc_lstat;
972 if (ctx->op_preserve) {
973 ctx->preserve = 1;
974 ctx->umask_kill = 0777777;
975 ctx->preserve_uidgid = (geteuid () == 0) ? 1 : 0;
976 } else {
977 int i;
978 ctx->preserve = ctx->preserve_uidgid = 0;
979 i = umask (0);
980 umask (i);
981 ctx->umask_kill = i ^ 0777777;
984 if (!dest_dir || !*dest_dir) {
985 g_free (def_text_secure);
986 g_free (source_mask);
987 return dest_dir;
990 ctx->search_handle = mc_search_new(source_mask,-1);
992 if (ctx->search_handle == NULL) {
993 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"),
994 source_mask);
995 g_free (dest_dir);
996 g_free (source_mask);
997 goto ask_file_mask;
1000 g_free (def_text_secure);
1001 g_free (source_mask);
1003 ctx->search_handle->is_case_sentitive = TRUE;
1004 if (source_easy_patterns)
1005 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1006 else
1007 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1009 tmp = dest_dir;
1010 dest_dir = tilde_expand (tmp);
1011 g_free (tmp);
1013 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1014 if (ctx->dest_mask == NULL)
1015 ctx->dest_mask = dest_dir;
1016 else
1017 ctx->dest_mask++;
1018 orig_mask = ctx->dest_mask;
1019 if (!*ctx->dest_mask
1020 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1021 && (!only_one
1022 || (!mc_stat (dest_dir, &buf) && S_ISDIR (buf.st_mode))))
1023 || (ctx->dive_into_subdirs
1024 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1025 || (only_one && !mc_stat (dest_dir, &buf)
1026 && S_ISDIR (buf.st_mode)))))
1027 ctx->dest_mask = g_strdup ("\\0");
1028 else {
1029 ctx->dest_mask = g_strdup (ctx->dest_mask);
1030 *orig_mask = '\0';
1032 if (!*dest_dir) {
1033 g_free (dest_dir);
1034 dest_dir = g_strdup ("./");
1036 if (val == B_USER)
1037 *do_background = 1;
1040 return dest_dir;