File copy/move: make ETA accurate.
[midnight-commander.git] / src / usermenu.c
blob1ec9611538422c07034be02e42b4f58fd09fb31d
1 /*
2 User Menu implementation
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2013
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file usermenu.c
28 * \brief Source: user menu implementation
31 #include <config.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
39 #include "lib/global.h"
40 #include "lib/fileloc.h"
41 #include "lib/tty/tty.h"
42 #include "lib/skin.h"
43 #include "lib/search.h"
44 #include "lib/vfs/vfs.h"
45 #include "lib/strutil.h"
46 #include "lib/util.h"
48 #ifdef USE_INTERNAL_EDIT
49 #include "src/editor/edit.h" /* WEdit */
50 #endif
51 #include "src/viewer/mcviewer.h" /* for default_* externs */
53 #include "src/args.h" /* mc_run_param0 */
54 #include "src/execute.h"
55 #include "src/setup.h"
56 #include "src/history.h"
58 #include "src/filemanager/dir.h"
59 #include "src/filemanager/filemanager.h"
60 #include "src/filemanager/layout.h"
62 #include "usermenu.h"
64 /*** global variables ****************************************************************************/
66 /*** file scope macro definitions ****************************************************************/
68 #define MAX_ENTRIES 16
69 #define MAX_ENTRY_LEN 60
71 /*** file scope type declarations ****************************************************************/
73 /*** forward declarations (file scope functions) *************************************************/
75 /*** file scope variables ************************************************************************/
77 static gboolean debug_flag = FALSE;
78 static gboolean debug_error = FALSE;
79 static char *menu = NULL;
81 /* --------------------------------------------------------------------------------------------- */
82 /*** file scope functions ************************************************************************/
83 /* --------------------------------------------------------------------------------------------- */
85 /** strip file's extension */
86 static char *
87 strip_ext (char *ss)
89 char *s;
90 char *e = NULL;
92 if (ss == NULL)
93 return NULL;
95 for (s = ss; *s != '\0'; s++)
97 if (*s == '.')
98 e = s;
99 if (IS_PATH_SEP (*s) && e != NULL)
100 e = NULL; /* '.' in *directory* name */
103 if (e != NULL)
104 *e = '\0';
106 return (*ss == '\0' ? NULL : ss);
109 /* --------------------------------------------------------------------------------------------- */
111 * Check for the "shell_patterns" directive. If it's found and valid,
112 * interpret it and move the pointer past the directive. Return the
113 * current pointer.
116 static char *
117 check_patterns (char *p)
119 static const char def_name[] = "shell_patterns=";
120 char *p0 = p;
122 if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
123 return p0;
125 p += sizeof (def_name) - 1;
126 if (*p == '1')
127 easy_patterns = TRUE;
128 else if (*p == '0')
129 easy_patterns = FALSE;
130 else
131 return p0;
133 /* Skip spaces */
134 p++;
135 while (whiteness (*p))
136 p++;
137 return p;
140 /* --------------------------------------------------------------------------------------------- */
141 /** Copies a whitespace separated argument from p to arg. Returns the
142 point after argument. */
144 static char *
145 extract_arg (char *p, char *arg, int size)
147 while (*p != '\0' && whiteness (*p))
148 p++;
150 /* support quote space .mnu */
151 while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
153 char *np;
155 np = str_get_next_char (p);
156 if (np - p >= size)
157 break;
158 memcpy (arg, p, np - p);
159 arg += np - p;
160 size -= np - p;
161 p = np;
163 *arg = '\0';
164 if (*p == '\0' || *p == '\n')
165 str_prev_char (&p);
166 return p;
169 /* --------------------------------------------------------------------------------------------- */
170 /* Tests whether the selected file in the panel is of any of the types
171 specified in argument. */
173 static gboolean
174 test_type (WPanel *panel, char *arg)
176 const file_entry_t *fe;
177 int result = 0; /* False by default */
178 mode_t st_mode;
180 fe = panel_current_entry (panel);
181 if (fe == NULL)
182 return FALSE;
184 st_mode = fe->st.st_mode;
186 for (; *arg != '\0'; arg++)
188 switch (*arg)
190 case 'n': /* Not a directory */
191 result |= !S_ISDIR (st_mode);
192 break;
193 case 'r': /* Regular file */
194 result |= S_ISREG (st_mode);
195 break;
196 case 'd': /* Directory */
197 result |= S_ISDIR (st_mode);
198 break;
199 case 'l': /* Link */
200 result |= S_ISLNK (st_mode);
201 break;
202 case 'c': /* Character special */
203 result |= S_ISCHR (st_mode);
204 break;
205 case 'b': /* Block special */
206 result |= S_ISBLK (st_mode);
207 break;
208 case 'f': /* Fifo (named pipe) */
209 result |= S_ISFIFO (st_mode);
210 break;
211 case 's': /* Socket */
212 result |= S_ISSOCK (st_mode);
213 break;
214 case 'x': /* Executable */
215 result |= (st_mode & 0111) != 0 ? 1 : 0;
216 break;
217 case 't':
218 result |= panel->marked != 0 ? 1 : 0;
219 break;
220 default:
221 debug_error = TRUE;
222 break;
226 return (result != 0);
229 /* --------------------------------------------------------------------------------------------- */
230 /** Calculates the truth value of the next condition starting from
231 p. Returns the point after condition. */
233 static char *
234 test_condition (const Widget *edit_widget, char *p, gboolean *condition)
236 char arg[256];
237 const mc_search_type_t search_type = easy_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
238 #ifdef USE_INTERNAL_EDIT
239 const WEdit *e = CONST_EDIT (edit_widget);
240 #endif
242 /* Handle one condition */
243 for (; *p != '\n' && *p != '&' && *p != '|'; p++)
245 WPanel *panel = NULL;
247 /* support quote space .mnu */
248 if ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
249 continue;
250 if (*p >= 'a')
251 panel = current_panel;
252 else if (get_other_type () == view_listing)
253 panel = other_panel;
255 *p |= 0x20;
257 switch (*p++)
259 case '!':
260 p = test_condition (edit_widget, p, condition);
261 *condition = !*condition;
262 str_prev_char (&p);
263 break;
264 case 'f': /* file name pattern */
265 p = extract_arg (p, arg, sizeof (arg));
266 #ifdef USE_INTERNAL_EDIT
267 if (e != NULL)
269 const char *edit_filename;
271 edit_filename = edit_get_file_name (e);
272 *condition = mc_search (arg, DEFAULT_CHARSET, edit_filename, search_type);
274 else
275 #endif
277 if (panel == NULL)
278 *condition = FALSE;
279 else
281 const file_entry_t *fe;
283 fe = panel_current_entry (panel);
284 *condition = fe != NULL
285 && mc_search (arg, DEFAULT_CHARSET, fe->fname->str, search_type);
288 break;
289 case 'y': /* syntax pattern */
290 #ifdef USE_INTERNAL_EDIT
291 if (e != NULL)
293 const char *syntax_type;
295 syntax_type = edit_get_syntax_type (e);
296 if (syntax_type != NULL)
298 p = extract_arg (p, arg, sizeof (arg));
299 *condition = mc_search (arg, DEFAULT_CHARSET, syntax_type, MC_SEARCH_T_NORMAL);
302 #endif
303 break;
304 case 'd':
305 p = extract_arg (p, arg, sizeof (arg));
306 *condition = panel != NULL
307 && mc_search (arg, DEFAULT_CHARSET, vfs_path_as_str (panel->cwd_vpath),
308 search_type);
309 break;
310 case 't':
311 p = extract_arg (p, arg, sizeof (arg));
312 *condition = panel != NULL && test_type (panel, arg);
313 break;
314 case 'x': /* executable */
316 struct stat status;
318 p = extract_arg (p, arg, sizeof (arg));
319 *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
320 break;
322 default:
323 debug_error = TRUE;
324 break;
325 } /* switch */
326 } /* while */
327 return p;
330 /* --------------------------------------------------------------------------------------------- */
331 /** General purpose condition debug output handler */
333 static void
334 debug_out (char *start, char *end, gboolean condition)
336 static char *msg = NULL;
338 if (start == NULL && end == NULL)
340 /* Show output */
341 if (debug_flag && msg != NULL)
343 size_t len;
345 len = strlen (msg);
346 if (len != 0)
347 msg[len - 1] = '\0';
348 message (D_NORMAL, _("Debug"), "%s", msg);
351 debug_flag = FALSE;
352 MC_PTR_FREE (msg);
354 else
356 const char *type;
357 char *p;
359 /* Save debug info for later output */
360 if (!debug_flag)
361 return;
362 /* Save the result of the condition */
363 if (debug_error)
365 type = _("ERROR:");
366 debug_error = FALSE;
368 else if (condition)
369 type = _("True:");
370 else
371 type = _("False:");
372 /* This is for debugging, don't need to be super efficient. */
373 if (end == NULL)
374 p = g_strdup_printf ("%s %s %c \n", msg ? msg : "", type, *start);
375 else
376 p = g_strdup_printf ("%s %s %.*s \n", msg ? msg : "", type, (int) (end - start), start);
377 g_free (msg);
378 msg = p;
382 /* --------------------------------------------------------------------------------------------- */
383 /** Calculates the truth value of one lineful of conditions. Returns
384 the point just before the end of line. */
386 static char *
387 test_line (const Widget *edit_widget, char *p, gboolean *result)
389 char operator;
391 /* Repeat till end of line */
392 while (*p != '\0' && *p != '\n')
394 char *debug_start, *debug_end;
395 gboolean condition = TRUE;
397 /* support quote space .mnu */
398 while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
399 p++;
400 if (*p == '\0' || *p == '\n')
401 break;
402 operator = *p++;
403 if (*p == '?')
405 debug_flag = TRUE;
406 p++;
408 /* support quote space .mnu */
409 while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
410 p++;
411 if (*p == '\0' || *p == '\n')
412 break;
414 debug_start = p;
415 p = test_condition (edit_widget, p, &condition);
416 debug_end = p;
417 /* Add one debug statement */
418 debug_out (debug_start, debug_end, condition);
420 switch (operator)
422 case '+':
423 case '=':
424 /* Assignment */
425 *result = condition;
426 break;
427 case '&': /* Logical and */
428 *result = *result && condition;
429 break;
430 case '|': /* Logical or */
431 *result = *result || condition;
432 break;
433 default:
434 debug_error = TRUE;
435 break;
436 } /* switch */
437 /* Add one debug statement */
438 debug_out (&operator, NULL, *result);
440 } /* while (*p != '\n') */
441 /* Report debug message */
442 debug_out (NULL, NULL, TRUE);
444 if (*p == '\0' || *p == '\n')
445 str_prev_char (&p);
446 return p;
449 /* --------------------------------------------------------------------------------------------- */
450 /** FIXME: recode this routine on version 3.0, it could be cleaner */
452 static void
453 execute_menu_command (const Widget *edit_widget, const char *commands, gboolean show_prompt)
455 FILE *cmd_file;
456 int cmd_file_fd;
457 gboolean expand_prefix_found = FALSE;
458 char *parameter = NULL;
459 gboolean do_quote = FALSE;
460 char lc_prompt[80];
461 int col;
462 vfs_path_t *file_name_vpath;
463 gboolean run_view = FALSE;
464 char *cmd;
466 /* Skip menu entry title line */
467 commands = strchr (commands, '\n');
468 if (commands == NULL)
469 return;
471 cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
473 if (cmd_file_fd == -1)
475 message (D_ERROR, MSG_ERROR, _("Cannot create temporary command file\n%s"),
476 unix_error_string (errno));
477 return;
480 cmd_file = fdopen (cmd_file_fd, "w");
481 fputs ("#! /bin/sh\n", cmd_file);
482 commands++;
484 for (col = 0; *commands != '\0'; commands++)
486 if (col == 0)
488 if (!whitespace (*commands))
489 break;
490 while (whitespace (*commands))
491 commands++;
492 if (*commands == '\0')
493 break;
495 col++;
496 if (*commands == '\n')
497 col = 0;
498 if (parameter != NULL)
500 if (*commands == '}')
502 *parameter = '\0';
503 parameter =
504 input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
505 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD |
506 INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_VARIABLES |
507 INPUT_COMPLETE_USERNAMES);
508 if (parameter == NULL || *parameter == '\0')
510 /* User canceled */
511 g_free (parameter);
512 fclose (cmd_file);
513 mc_unlink (file_name_vpath);
514 vfs_path_free (file_name_vpath, TRUE);
515 return;
517 if (do_quote)
519 char *tmp;
521 tmp = name_quote (parameter, FALSE);
522 if (tmp != NULL)
524 fputs (tmp, cmd_file);
525 g_free (tmp);
528 else
529 fputs (parameter, cmd_file);
531 MC_PTR_FREE (parameter);
533 else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
534 *parameter++ = *commands;
536 else if (expand_prefix_found)
538 expand_prefix_found = FALSE;
539 if (g_ascii_isdigit ((gchar) * commands))
541 do_quote = (atoi (commands) != 0);
542 while (g_ascii_isdigit ((gchar) * commands))
543 commands++;
545 if (*commands == '{')
546 parameter = lc_prompt;
547 else
549 char *text;
551 text = expand_format (edit_widget, *commands, do_quote);
552 if (text != NULL)
554 fputs (text, cmd_file);
555 g_free (text);
559 else if (*commands == '%')
561 int i;
563 i = check_format_view (commands + 1);
564 if (i != 0)
566 commands += i;
567 run_view = TRUE;
569 else
571 do_quote = TRUE; /* Default: Quote expanded macro */
572 expand_prefix_found = TRUE;
575 else
576 fputc (*commands, cmd_file);
579 fclose (cmd_file);
580 mc_chmod (file_name_vpath, S_IRWXU);
582 /* Execute the command indirectly to allow execution even on no-exec filesystems. */
583 cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
585 if (run_view)
587 mcview_viewer (cmd, NULL, 0, 0, 0);
588 dialog_switch_process_pending ();
590 else if (show_prompt)
591 shell_execute (cmd, EXECUTE_HIDE);
592 else
594 gboolean ok;
596 /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
597 * to work as expected, instead of being ignored. */
598 tty_reset_shell_mode ();
600 ok = (system (cmd) != -1);
602 /* Restore terminal configuration. */
603 tty_raw_mode ();
605 /* Redraw the original screen's contents. */
606 tty_clear_screen ();
607 repaint_screen ();
609 if (!ok)
610 message (D_ERROR, MSG_ERROR, "%s", _("Error calling program"));
613 g_free (cmd);
615 mc_unlink (file_name_vpath);
616 vfs_path_free (file_name_vpath, TRUE);
619 /* --------------------------------------------------------------------------------------------- */
621 ** Check owner of the menu file. Using menu file is allowed, if
622 ** owner of the menu is root or the actual user. In either case
623 ** file should not be group and word-writable.
625 ** Q. Should we apply this routine to system and home menu (and .ext files)?
628 static gboolean
629 menu_file_own (char *path)
631 struct stat st;
633 if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
634 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
635 return TRUE;
637 if (verbose)
638 message (D_NORMAL, _("Warning -- ignoring file"),
639 _("File %s is not owned by root or you or is world writable.\n"
640 "Using it may compromise your security"), path);
642 return FALSE;
645 /* --------------------------------------------------------------------------------------------- */
646 /*** public functions ****************************************************************************/
647 /* --------------------------------------------------------------------------------------------- */
649 /* Formats defined:
650 %% The % character
651 %f The current file in the active panel (if non-local vfs, file will be copied locally
652 and %f will be full path to it) or the opened file in the internal editor.
653 %p Likewise.
654 %d The current working directory
655 %s "Selected files"; the tagged files if any, otherwise the current file
656 %t Tagged files
657 %u Tagged files (and they are untagged on return from expand_format)
658 %view Runs the commands and pipes standard output to the view command.
659 If %view is immediately followed by '{', recognize keywords
660 ascii, hex, nroff and unform
662 If the format letter is in uppercase, it refers to the other panel.
664 With a number followed the % character you can turn quoting on (default)
665 and off. For example:
666 %f quote expanded macro
667 %1f ditto
668 %0f don't quote expanded macro
670 expand_format returns a memory block that must be free()d.
673 /* Returns how many characters we should advance if %view was found */
675 check_format_view (const char *p)
677 const char *q = p;
679 if (strncmp (p, "view", 4) == 0)
681 q += 4;
682 if (*q == '{')
684 for (q++; *q != '\0' && *q != '}'; q++)
686 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
688 mcview_global_flags.hex = FALSE;
689 q += 4;
691 else if (strncmp (q, "hex", 3) == 0)
693 mcview_global_flags.hex = TRUE;
694 q += 2;
696 else if (strncmp (q, "nroff", 5) == 0)
698 mcview_global_flags.nroff = TRUE;
699 q += 4;
701 else if (strncmp (q, "unform", 6) == 0)
703 mcview_global_flags.nroff = FALSE;
704 q += 5;
707 if (*q == '}')
708 q++;
710 return q - p;
712 return 0;
715 /* --------------------------------------------------------------------------------------------- */
718 check_format_cd (const char *p)
720 return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
723 /* --------------------------------------------------------------------------------------------- */
724 /* Check if p has a "^var\{var-name\}" */
725 /* Returns the number of skipped characters (zero on not found) */
726 /* V will be set to the expanded variable name */
729 check_format_var (const char *p, char **v)
731 *v = NULL;
733 if (strncmp (p, "var{", 4) == 0)
735 const char *q = p;
736 const char *dots = NULL;
737 const char *value;
738 char *var_name;
740 for (q += 4; *q != '\0' && *q != '}'; q++)
742 if (*q == ':')
743 dots = q + 1;
745 if (*q == '\0')
746 return 0;
748 if (dots == NULL || dots == q + 5)
750 message (D_ERROR,
751 _("Format error on file Extensions File"),
752 dots == NULL ? _("The %%var macro has no default")
753 : _("The %%var macro has no variable"));
754 return 0;
757 /* Copy the variable name */
758 var_name = g_strndup (p + 4, dots - 2 - (p + 3));
759 value = getenv (var_name);
760 g_free (var_name);
762 if (value != NULL)
763 *v = g_strdup (value);
764 else
765 *v = g_strndup (dots, q - dots);
767 return q - p;
769 return 0;
772 /* --------------------------------------------------------------------------------------------- */
774 char *
775 expand_format (const Widget *edit_widget, char c, gboolean do_quote)
777 WPanel *panel = NULL;
778 char *(*quote_func) (const char *, gboolean);
779 const char *fname = NULL;
780 char *result;
781 char c_lc;
783 #ifdef USE_INTERNAL_EDIT
784 const WEdit *e = CONST_EDIT (edit_widget);
785 #else
786 (void) edit_widget;
787 #endif
789 if (c == '%')
790 return g_strdup ("%");
792 switch (mc_global.mc_run_mode)
794 case MC_RUN_FULL:
795 #ifdef USE_INTERNAL_EDIT
796 if (e != NULL)
797 fname = edit_get_file_name (e);
798 else
799 #endif
801 const file_entry_t *fe;
803 if (g_ascii_islower ((gchar) c))
804 panel = current_panel;
805 else
807 if (get_other_type () != view_listing)
808 return NULL;
809 panel = other_panel;
812 fe = panel_current_entry (panel);
813 fname = fe == NULL ? NULL : fe->fname->str;
815 break;
817 #ifdef USE_INTERNAL_EDIT
818 case MC_RUN_EDITOR:
819 fname = edit_get_file_name (e);
820 break;
821 #endif
823 case MC_RUN_VIEWER:
824 /* mc_run_param0 is not NULL here because mcviewer isn't run without input file */
825 fname = (const char *) mc_run_param0;
826 break;
828 default:
829 /* other modes don't use formats */
830 return NULL;
833 if (do_quote)
834 quote_func = name_quote;
835 else
836 quote_func = fake_name_quote;
838 c_lc = g_ascii_tolower ((gchar) c);
840 switch (c_lc)
842 case 'f':
843 case 'p':
844 result = quote_func (fname, FALSE);
845 goto ret;
846 case 'x':
847 result = quote_func (extension (fname), FALSE);
848 goto ret;
849 case 'd':
851 const char *cwd;
853 if (panel != NULL)
854 cwd = vfs_path_as_str (panel->cwd_vpath);
855 else
856 cwd = vfs_get_current_dir ();
858 result = quote_func (cwd, FALSE);
859 goto ret;
861 case 'c':
862 #ifdef USE_INTERNAL_EDIT
863 if (e != NULL)
865 result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
866 goto ret;
868 #endif
869 break;
870 case 'i': /* indent equal number cursor position in line */
871 #ifdef USE_INTERNAL_EDIT
872 if (e != NULL)
874 result = g_strnfill (edit_get_curs_col (e), ' ');
875 goto ret;
877 #endif
878 break;
879 case 'y': /* syntax type */
880 #ifdef USE_INTERNAL_EDIT
881 if (e != NULL)
883 const char *syntax_type;
885 syntax_type = edit_get_syntax_type (e);
886 if (syntax_type != NULL)
888 result = g_strdup (syntax_type);
889 goto ret;
892 #endif
893 break;
894 case 'k': /* block file name */
895 case 'b': /* block file name / strip extension */
896 #ifdef USE_INTERNAL_EDIT
897 if (e != NULL)
899 char *file;
901 file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
902 result = quote_func (file, FALSE);
903 g_free (file);
904 goto ret;
906 #endif
907 if (c_lc == 'b')
909 result = strip_ext (quote_func (fname, FALSE));
910 goto ret;
912 break;
913 case 'n': /* strip extension in editor */
914 #ifdef USE_INTERNAL_EDIT
915 if (e != NULL)
917 result = strip_ext (quote_func (fname, FALSE));
918 goto ret;
920 #endif
921 break;
922 case 'm': /* menu file name */
923 if (menu != NULL)
925 result = quote_func (menu, FALSE);
926 goto ret;
928 break;
929 case 's':
930 if (panel == NULL || panel->marked == 0)
932 result = quote_func (fname, FALSE);
933 goto ret;
936 MC_FALLTHROUGH;
938 case 't':
939 case 'u':
941 GString *block = NULL;
942 int i;
944 if (panel == NULL)
946 result = NULL;
947 goto ret;
950 for (i = 0; i < panel->dir.len; i++)
951 if (panel->dir.list[i].f.marked != 0)
953 char *tmp;
955 tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
956 if (tmp != NULL)
958 if (block == NULL)
959 block = g_string_new_take (tmp);
960 else
962 g_string_append (block, tmp);
963 g_free (tmp);
965 g_string_append_c (block, ' ');
968 if (c_lc == 'u')
969 do_file_mark (panel, i, 0);
971 result = block == NULL ? NULL : g_string_free (block, block->len == 0);
972 goto ret;
973 } /* sub case block */
974 default:
975 break;
976 } /* switch */
978 result = g_strdup ("% ");
979 result[1] = c;
980 ret:
981 return result;
984 /* --------------------------------------------------------------------------------------------- */
986 * If edit_widget is NULL then we are called from the mc menu,
987 * otherwise we are called from the mcedit menu.
990 gboolean
991 user_menu_cmd (const Widget *edit_widget, const char *menu_file, int selected_entry)
993 char *data, *p;
994 GPtrArray *entries = NULL;
995 int max_cols = 0;
996 int col = 0;
997 gboolean accept_entry = TRUE;
998 int selected = 0;
999 gboolean old_patterns;
1000 gboolean res = FALSE;
1001 gboolean interactive = TRUE;
1003 if (!vfs_current_is_local ())
1005 message (D_ERROR, MSG_ERROR, "%s", _("Cannot execute commands on non-local filesystems"));
1006 return FALSE;
1009 menu = g_strdup (menu_file != NULL ? menu_file : edit_widget != NULL ?
1010 EDIT_LOCAL_MENU : MC_LOCAL_MENU);
1012 if (!exist_file (menu) || !menu_file_own (menu))
1014 if (menu_file != NULL)
1016 message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu,
1017 unix_error_string (errno));
1018 MC_PTR_FREE (menu);
1019 return FALSE;
1022 g_free (menu);
1023 menu = mc_config_get_full_path (edit_widget != NULL ? EDIT_HOME_MENU : MC_USERMENU_FILE);
1024 if (!exist_file (menu))
1026 const char *global_menu;
1028 global_menu = edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU;
1030 g_free (menu);
1031 menu = mc_build_filename (mc_config_get_home_dir (), global_menu, (char *) NULL);
1032 if (!exist_file (menu))
1034 g_free (menu);
1035 menu = mc_build_filename (mc_global.sysconfig_dir, global_menu, (char *) NULL);
1036 if (!exist_file (menu))
1038 g_free (menu);
1039 menu = mc_build_filename (mc_global.share_data_dir, global_menu, (char *) NULL);
1045 if (!g_file_get_contents (menu, &data, NULL, NULL))
1047 message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu, unix_error_string (errno));
1048 MC_PTR_FREE (menu);
1049 return FALSE;
1052 old_patterns = easy_patterns;
1054 /* Parse the menu file */
1055 for (p = check_patterns (data); *p != '\0'; str_next_char (&p))
1057 unsigned int menu_lines = entries == NULL ? 0 : entries->len;
1059 if (col == 0 && (entries == NULL || menu_lines == entries->len))
1060 switch (*p)
1062 case '#':
1063 /* do not show prompt if first line of external script is #silent */
1064 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1065 interactive = FALSE;
1066 /* A commented menu entry */
1067 accept_entry = TRUE;
1068 break;
1070 case '+':
1071 if (*(p + 1) == '=')
1073 /* Combined adding and default */
1074 p = test_line (edit_widget, p + 1, &accept_entry);
1075 if (selected == 0 && accept_entry)
1076 selected = menu_lines;
1078 else
1080 /* A condition for adding the entry */
1081 p = test_line (edit_widget, p, &accept_entry);
1083 break;
1085 case '=':
1086 if (*(p + 1) == '+')
1088 /* Combined adding and default */
1089 p = test_line (edit_widget, p + 1, &accept_entry);
1090 if (selected == 0 && accept_entry)
1091 selected = menu_lines;
1093 else
1095 /* A condition for making the entry default */
1096 gboolean ok = TRUE;
1098 p = test_line (edit_widget, p, &ok);
1099 if (selected == 0 && ok)
1100 selected = menu_lines;
1102 break;
1104 default:
1105 if (!whitespace (*p) && str_isprint (p))
1107 /* A menu entry title line */
1108 if (accept_entry)
1110 if (entries == NULL)
1111 entries = g_ptr_array_new ();
1112 g_ptr_array_add (entries, p);
1114 else
1115 accept_entry = TRUE;
1117 break;
1120 if (*p == '\n')
1122 if (entries != NULL && entries->len > menu_lines)
1123 accept_entry = TRUE;
1124 max_cols = MAX (max_cols, col);
1125 col = 0;
1127 else
1129 if (*p == '\t')
1130 *p = ' ';
1131 col++;
1135 if (entries == NULL)
1136 message (D_ERROR, MSG_ERROR, _("No suitable entries found in %s"), menu);
1137 else
1139 if (selected_entry >= 0)
1140 selected = selected_entry;
1141 else
1143 Listbox *listbox;
1144 unsigned int i;
1146 max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1148 /* Create listbox */
1149 listbox = listbox_window_new (entries->len, max_cols + 2, _("User menu"),
1150 "[Edit Menu File]");
1151 /* insert all the items found */
1152 for (i = 0; i < entries->len; i++)
1154 p = g_ptr_array_index (entries, i);
1155 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1156 extract_line (p, p + MAX_ENTRY_LEN, NULL), p, FALSE);
1158 /* Select the default entry */
1159 listbox_set_current (listbox->list, selected);
1161 selected = listbox_run (listbox);
1164 if (selected >= 0)
1166 execute_menu_command (edit_widget, g_ptr_array_index (entries, selected), interactive);
1167 res = TRUE;
1170 g_ptr_array_free (entries, TRUE);
1172 do_refresh ();
1175 easy_patterns = old_patterns;
1176 MC_PTR_FREE (menu);
1177 g_free (data);
1178 return res;
1181 /* --------------------------------------------------------------------------------------------- */