home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 6 File / 06-File.zip / mc454src.zip / mc-4.5.4.src / mc-4.5.4 / src / user.c < prev    next >
C/C++ Source or Header  |  1999-01-04  |  18KB  |  737 lines

  1. /* User Menu implementation
  2.    Copyright (C) 1994 Miguel de Icaza, Janne Kukonlehto
  3.    
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2 of the License, or
  7.    (at your option) any later version.
  8.    
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. #include <config.h>
  19. #include <stdio.h>
  20. #ifdef OS2_NT
  21. #    include <io.h>
  22. #endif
  23. #include "tty.h"
  24. #include <stdlib.h>    /* For free() */
  25. #include "fs.h"
  26. #include <string.h>
  27. #include <ctype.h>
  28. #include <errno.h>
  29. #include <fcntl.h>
  30. #include "mad.h"
  31. #include "util.h"
  32. #include "global.h"
  33. #include "dialog.h"
  34. #include "color.h"
  35. #include "dir.h"
  36. #include "panel.h"
  37. #include "main.h"
  38. #include "user.h"
  39. #include "layout.h"
  40. #include "setup.h"
  41. #include "../vfs/vfs.h"
  42.  
  43. /* For the simple listbox manager */
  44. #include "dlg.h"
  45. #include "widget.h"
  46. #include "wtools.h"
  47.  
  48. #include "view.h" /* for default_* externs */
  49.  
  50. /* "$Id: user.c,v 1.8 1998/12/02 21:28:05 unammx Exp $" */
  51.  
  52. #define MAX_ENTRIES 40
  53. #define MAX_ENTRY_LEN 60
  54.  
  55. static char *data;
  56. static char *entries [MAX_ENTRIES];
  57. static int max_cols;
  58. static int menu_lines;
  59. static int debug_flag = 0;
  60. static int debug_error = 0;
  61. extern char *search_string (char *, char *);
  62.  
  63. /* Formats defined:
  64.    %%  The % character
  65.    %f  The current file (if non-local vfs, file will be copied locally and
  66.        %f will be full path to it).
  67.    %p  The current file
  68.    %d  The current working directory
  69.    %s  "Selected files"; the tagged files if any, otherwise the current file
  70.    %t  Tagged files
  71.    %u  Tagged files (and they are untagged on return from expand_format)
  72.    %view Runs the commands and pipes standard output to the view command
  73.        if %view is directly followed by '{', a list of keywords
  74.        ascii, hex, nroff, unformated and
  75.  
  76.    If the format letter is in uppercase, it refers to the other panel.
  77.    
  78.    With a number followed the % character you can turn quoting on (default)
  79.    and off. For example:
  80.        %f    quote expanded macro
  81.        %1f   ditto
  82.        %0f   don't quote expanded macro
  83.  
  84.    expand_format returns a memory block that must be free()d.
  85. */
  86.  
  87. /* Returns how many characters we should advance if %view was found */
  88. int check_format_view (char *p)
  89. {
  90.     char *q = p;
  91.     if (!strncmp (p, "view", 4)){
  92.         q += 4;
  93.         if (*q == '{'){
  94.             for (q++;*q && *q != '}';q++){
  95.                 if (!strncmp (q, "ascii", 5)){
  96.                     default_hex_mode = 0;
  97.                     q += 4;
  98.                 } else if (!strncmp (q, "hex", 3)){
  99.                     default_hex_mode = 1;
  100.                     q += 2;
  101.                 } else if (!strncmp (q, "nroff", 5)){
  102.                     default_nroff_flag = 1;
  103.                     q += 4;
  104.                 } else if (!strncmp (q, "unformated", 10)){
  105.                     default_nroff_flag = 0;
  106.                     q += 9;
  107.                 } 
  108.             }
  109.             if (*q == '}')
  110.                 q++;
  111.         }
  112.         return q - p;
  113.     } else
  114.         return 0;
  115. }
  116.  
  117. int check_format_cd (char *p)
  118. {
  119.     if (!strncmp (p, "cd", 2))
  120.         return 3;
  121.     else
  122.         return 0;
  123. }
  124.  
  125. /* Check if p has a "^var\{var-name\}" */
  126. /* Returns the number of skipped characters (zero on not found) */
  127. /* V will be set to the expanded variable name */
  128. int check_format_var (char *p, char **v)
  129. {
  130.     char *q = p;
  131.     char *var_name;
  132.     char *value;
  133.     char *dots;
  134.     
  135.     *v = 0;
  136.     dots = 0;
  137.     if (!strncmp (p, "var{", 4)){
  138.     for (q += 4; *q && *q != '}'; q++){
  139.         if (*q == ':')
  140.         dots = q+1;
  141.         ;
  142.     }
  143.     if (!*q)
  144.         return 0;
  145.  
  146.     if (!dots || dots == q+5){
  147.         message (1,
  148.              " Format error on file Extensions File ",
  149.              !dots ? " The %%var macro does not have a default "
  150.              :       " The %%var macros does not have a variable " );
  151.         return 0;
  152.     }
  153.     
  154.     /* Copy the variable name */
  155.     var_name = xmalloc (dots - p, "check_format_var");
  156.     strncpy (var_name, p+4, dots-2 - (p+3));
  157.     var_name [dots-2 - (p+3)] = 0;
  158.  
  159.     value = getenv (var_name);
  160.     if (value){
  161.         *v = strdup (value);
  162.         return q-p;
  163.     }
  164.     free (var_name);
  165.     var_name = xmalloc (q - dots + 1, "check_format_var_2");
  166.     strncpy (var_name, dots, q - dots + 1);
  167.     var_name [q-dots] = 0;
  168.     *v = var_name;
  169.     return q-p;
  170.     }
  171.     return 0;
  172. }
  173.  
  174. /* strip file's extension */
  175. static char *
  176. strip_ext(char *ss)
  177. {
  178.     register char *s = ss;
  179.     char *e = NULL;
  180.     while(*s) {
  181.         if(*s == '.') e = s;
  182.         if(*s == PATH_SEP && e) e=NULL; /* '.' in *directory* name */
  183.         s++;
  184.     }
  185.     if(e) *e = 0;
  186.     return ss;
  187. }
  188.  
  189. char *expand_format (char c, int quote)
  190. {
  191.     WPanel *panel;
  192.     char *(*quote_func)(const char *, int);
  193.     
  194.     if (quote)
  195.     quote_func = name_quote;
  196.     else
  197.     quote_func = fake_name_quote;
  198.  
  199.     if (c == '%')
  200.     return strdup ("%");
  201.     
  202.     if (islower (c))
  203.     panel = cpanel;
  204.     else {
  205.     if (get_other_type () == view_listing){
  206.         panel = other_panel;
  207.     } else
  208.         return strdup ("");
  209.     }
  210.     if (!panel)
  211.     panel = cpanel;
  212.  
  213.     c = tolower (c);
  214.     
  215.     switch (c){
  216.     case 'f': 
  217.     case 'p': return (*quote_func) (panel->dir.list [panel->selected].fname, 0);
  218.     case 'b':
  219.     return strip_ext((*quote_func) (panel->dir.list [panel->selected].fname, 0));
  220.     case 'd': return (*quote_func) (panel->cwd, 0);
  221.     case 's':
  222.     if (!panel->marked)
  223.         return (*quote_func) (panel->dir.list [panel->selected].fname, 0);
  224.  
  225.     /* Fall through */
  226.  
  227.     case 't':
  228.     case 'u':
  229.     {
  230.     int length = 2, i;
  231.     char *block, *tmp;
  232.     
  233.     for (i = 0; i < panel->count; i++)
  234.         if (panel->dir.list [i].f.marked)
  235.         length += strlen (panel->dir.list [i].fname) + 1;
  236.  
  237.     block = xmalloc (length*2+1, "expand_format");
  238.     *block = 0;
  239.     for (i = 0; i < panel->count; i++)
  240.         if (panel->dir.list [i].f.marked){
  241.         strcat (block, tmp = (*quote_func) (panel->dir.list [i].fname, 0));
  242.         free (tmp);
  243.         strcat (block, " ");
  244.         if (c == 'u')
  245.             do_file_mark (panel, i, 0);
  246.         }
  247.     return block;
  248.     } /* sub case block */
  249.     } /* switch */
  250.     return strdup ("");
  251. }
  252.  
  253. /* Checks for shell patterns defination */
  254. char *check_patterns (char *p)
  255. {
  256.     const char *def_name = "shell_patterns=";
  257.     int value;
  258.  
  259.     if (strncmp (p, def_name, sizeof (def_name)) == 0){
  260.     p += strlen (def_name);
  261.     value = *p++ - '0';
  262.     if (value == 0 || value == 1)
  263.         easy_patterns = value;
  264.     else
  265.         message (1, MSG_ERROR, _(" Invalid shell pattern defination \"%c\". "), value + '0');
  266.     }
  267.     while (*p == '\n' || *p == '\t' || *p == ' ') p++;
  268.     return p;
  269. }
  270.  
  271. /* Copies a whitespace separated argument from p to arg. Returns the
  272.    point after argument. */
  273. static char *extract_arg (char *p, char *arg)
  274. {
  275.     while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
  276.     p++;
  277.     while (*p && *p != ' ' && *p != '\t' && *p != '\n')
  278.     *arg++ = *p++;
  279.     *arg = 0;
  280.     if (!*p || *p == '\n')
  281.     p --;
  282.     return p;
  283. }
  284.  
  285. /* Tests whether the selected file in the panel is of any of the types
  286.    specified in argument. */
  287. static int test_type (WPanel *panel, char *arg)
  288. {
  289.     int result = 0; /* False by default */
  290.     int st_mode = panel->dir.list [panel->selected].buf.st_mode;
  291.  
  292.     for (;*arg != 0; arg++){
  293.     switch (*arg){
  294.     case 'n':    /* Not a directory */
  295.         result |= !S_ISDIR (st_mode);
  296.         break;
  297.     case 'r':    /* Regular file */
  298.         result |= S_ISREG (st_mode);
  299.         break;
  300.     case 'd':    /* Directory */
  301.         result |= S_ISDIR (st_mode);
  302.         break;
  303.     case 'l':    /* Link */
  304.         result |= S_ISLNK (st_mode);
  305.         break;
  306.     case 'c':    /* Character special */
  307.         result |= S_ISCHR (st_mode);
  308.         break;
  309.     case 'b':    /* Block special */
  310.         result |= S_ISBLK (st_mode);
  311.         break;
  312.     case 'f':    /* Fifo (named pipe) */
  313.         result |= S_ISFIFO (st_mode);
  314.         break;
  315.     case 's':    /* Socket */
  316.         result |= S_ISSOCK (st_mode);
  317.         break;
  318.     case 'x':    /* Executable */
  319.         result |= (st_mode & 0111) ? 1 : 0;
  320.         break;
  321.     case 't':
  322.         result |= panel->marked ? 1 : 0;
  323.         break;
  324.     default:
  325.         debug_error = 1;
  326.         break;
  327.     }
  328.     }
  329.     return result;
  330. }
  331.  
  332. /* Calculates the truth value of the next condition starting from
  333.    p. Returns the point after condition. */
  334. static char *test_condition (char *p, int *condition)
  335. {
  336.     WPanel *panel;
  337.     char arg [256];
  338.  
  339.     /* Handle one condition */
  340.     for (;*p != '\n' && *p != '&' && *p != '|'; p++){
  341.     if (*p == ' ' || *p == '\t')
  342.         continue;
  343.     if (*p >= 'a')
  344.         panel = cpanel;
  345.     else {
  346.         if (get_other_type () == view_listing)
  347.         panel = other_panel;
  348.         else
  349.         panel = NULL;
  350.     }
  351.     *p |= 0x20;
  352.  
  353.     switch (*p++){
  354.     case '!':
  355.         p = test_condition (p, condition);
  356.         *condition = ! *condition;
  357.         p--;
  358.         break;
  359.     case 'f':
  360.         p = extract_arg (p, arg);
  361.         *condition = panel && regexp_match (arg, panel->dir.list [panel->selected].fname, match_file);
  362.         break;
  363.     case 'd':
  364.         p = extract_arg (p, arg);
  365.         *condition = panel && regexp_match (arg, panel->cwd, match_file);
  366.         break;
  367.     case 't':
  368.         p = extract_arg (p, arg);
  369.         *condition = panel && test_type (panel, arg);
  370.         break;
  371.     default:
  372.         debug_error = 1;
  373.         break;
  374.     } /* switch */
  375.  
  376.     } /* while */
  377.     return p;
  378. }
  379.  
  380. /* General purpose condition debug output handler */
  381. static void
  382. debug_out (char *start, char *end, int cond)
  383. {
  384.     static char msg [256];
  385.     int len;
  386.  
  387.     if (start == NULL && end == NULL){
  388.     if (cond == 0){
  389.         /* Init */
  390.         msg [0] = 0;
  391.     } else {
  392.         /* Show output */
  393.         if (!debug_flag)
  394.         return;
  395.         len = strlen (msg);
  396.         if (len)
  397.         msg [len - 1] = 0;
  398.         message (0, _(" Debug "), msg);
  399.         debug_flag = 0;
  400.     }
  401.     } else {
  402.     /* Save debug info for later output */
  403.     if (!debug_flag)
  404.         return;
  405.     /* Save the result of the condition */
  406.     if (debug_error){
  407.         strcat (msg, _(" ERROR: "));
  408.         debug_error = 0;
  409.     }
  410.     else if (cond)
  411.         strcat (msg, _(" True:  "));
  412.     else
  413.         strcat (msg, _(" False: "));
  414.     /* Copy condition statement */
  415.     len = strlen (msg);
  416.     if (end == NULL){
  417.         /* Copy one character */
  418.         msg [len] = *start;
  419.         msg [len + 1] = 0;
  420.     } else {
  421.         /* Copy many characters */
  422.         while (start < end){
  423.         msg [len++] = *start++;
  424.         }
  425.         msg [len] = 0;
  426.     }
  427.     strcat (msg, " \n");
  428.     }
  429. }
  430.  
  431. /* Calculates the truth value of one lineful of conditions. Returns
  432.    the point just before the end of line. */
  433. static char *test_line (char *p, int *result)
  434. {
  435.     int condition;
  436.     char operator;
  437.     char *debug_start, *debug_end;
  438.  
  439.     /* Init debugger */
  440.     debug_out (NULL, NULL, 0);
  441.     /* Repeat till end of line */
  442.     while (*p && *p != '\n'){
  443.     while (*p == ' ' || *p == '\t')
  444.         p++;
  445.     if (!*p || *p == '\n')
  446.         break;
  447.     operator = *p++;
  448.     if (*p == '?'){
  449.         debug_flag = 1;
  450.         p++;
  451.     }
  452.     while (*p == ' ' || *p == '\t')
  453.         p++;
  454.     if (!*p || *p == '\n')
  455.         break;
  456.     condition = 1;    /* True by default */
  457.  
  458.     debug_start = p;
  459.     p = test_condition (p, &condition);
  460.     debug_end = p;
  461.     /* Add one debug statement */
  462.     debug_out (debug_start, debug_end, condition);
  463.  
  464.     switch (operator){
  465.     case '+':
  466.     case '=':
  467.         /* Assignment */
  468.         *result = condition;
  469.         break;
  470.     case '&':    /* Logical and */
  471.         *result &= condition;
  472.         break;
  473.     case '|':    /* Logical or */
  474.         *result |= condition;
  475.         break;
  476.     default:
  477.         debug_error = 1;
  478.         break;
  479.     } /* switch */
  480.     /* Add one debug statement */
  481.     debug_out (&operator, NULL, *result);
  482.     
  483.     } /* while (*p != '\n') */
  484.     /* Report debug message */
  485.     debug_out (NULL, NULL, 1);
  486.  
  487.     if (!*p || *p == '\n')
  488.     p --;
  489.     return p;
  490. }
  491.  
  492. /* FIXME: recode this routine on version 3.0, it could be cleaner */
  493. static void
  494. execute_menu_command (char *s)
  495. {
  496.     char *commands;
  497.     FILE *cmd_file;
  498.     int  cmd_file_fd;
  499.     int  expand_prefix_found = 0;
  500.     int parameter_found = 0;
  501.     int do_quote;
  502.     char prompt [80] = "";
  503.     int  col;
  504.     char *file_name = tmpnam (0);
  505.  
  506. #ifdef OS2_NT
  507.     /* OS/2 and NT requires the command to end in .cmd */
  508.     file_name = copy_strings (file_name, ".cmd", NULL);
  509. #endif
  510.     if ((cmd_file_fd = open (file_name, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600)) == -1){
  511.     message (1, MSG_ERROR, _(" Can't create temporary command file \n %s "),
  512.          unix_error_string (errno));
  513.     return;
  514.     }
  515.     cmd_file = fdopen (cmd_file_fd, "w");
  516.     commands = strchr (s, '\n');
  517.     if (!commands){
  518.     fclose (cmd_file);
  519.     unlink (file_name);
  520.     return;
  521.     }
  522.     commands++;
  523.     
  524.     for (col = 0; *commands; commands++){
  525.     if (col == 0 && (*commands != ' ' && *commands != '\t'))
  526.         break;
  527.         else if (col == 0)
  528.         while (*commands == ' ' || *commands == '\t')
  529.             commands++;
  530.     col++;
  531.     if (*commands == '\n')
  532.         col = 0;
  533.     if (parameter_found){
  534.         if (*commands == '}'){
  535.         char *parameter;
  536.         char *tmp;
  537.         parameter_found = 0;
  538.         parameter = input_dialog (_(" Parameter "), prompt, "");
  539.         if (!parameter || !*parameter){
  540.             /* User canceled */
  541.             fclose (cmd_file);
  542.             unlink (file_name);
  543.             return;
  544.         }
  545.         if (do_quote) {
  546.                 fputs (tmp = name_quote (parameter, 0), cmd_file);
  547.             free (tmp);
  548.         } else
  549.             fputs (parameter, cmd_file);
  550.         free (parameter);
  551.         } else {
  552.         int len = strlen (prompt);
  553.  
  554.         if (len+1 < sizeof (prompt)){
  555.             prompt [len] = *commands;
  556.             prompt [len+1] = 0;
  557.         } else
  558.             prompt [sizeof (prompt)-1] = 0;
  559.         }
  560.     } else if (expand_prefix_found){
  561.         expand_prefix_found = 0;
  562.         if (isdigit (*commands)) {
  563.         do_quote = atoi (commands);
  564.         for ( ; isdigit (*commands); commands++)
  565.             ;        
  566.         }
  567.         if (*commands == '{')
  568.         parameter_found = 1;
  569.         else{
  570.         char *text = expand_format (*commands, do_quote);
  571.         fputs (text, cmd_file);
  572.         free (text);
  573.         }
  574.     } else {
  575.         if (*commands == '%') {
  576.         do_quote = 1; /* Default: Quote expanded macro */
  577.         expand_prefix_found = 1;
  578.         } else
  579.         fputc (*commands, cmd_file);
  580.     }
  581.     }
  582.     fclose (cmd_file);
  583.     chmod (file_name, S_IRWXU);
  584.     execute (file_name);
  585.     unlink (file_name);
  586. }
  587.  
  588. /* 
  589. **    Check owner of the menu file. Using menu file is allowed, if
  590. **    owner of the menu is root or the actual user. In either case
  591. **    file should not be group and word-writable.
  592. **    
  593. **    Q. Should we apply this routine to system and home menu (and .ext files)?
  594. */
  595. static int
  596. menu_file_own(char* path)
  597. {
  598.     struct stat st;
  599.  
  600.     if (stat (path, &st) == 0
  601.         && (!st.st_uid || (st.st_uid == geteuid ()))
  602.         && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0)
  603.     ) {
  604.         return 1;
  605.     }
  606.     else
  607.     {
  608.         if (verbose)
  609.         {
  610.             message (0, _(" Warning -- ignoring file "),
  611.                  _("File %s is not owned by root or you or is world writable.\n"
  612.                    "Using it may compromise your security"),
  613.                 path
  614.             );
  615.         }
  616.         return 0;
  617.     }
  618. }
  619.  
  620. void user_menu_cmd (void)
  621. {
  622.     char *menu, *p;
  623.     int  col, i, accept_entry = 1;
  624.     int  selected, old_patterns;
  625.     Listbox *listbox;
  626.     
  627.     if (!vfs_current_is_local ()){
  628.     message (1, _(" Oops... "),
  629.          _(" I can't run programs while logged on a non local directory "));
  630.     return;
  631.     }
  632.     
  633.     menu = strdup (MC_LOCAL_MENU);
  634.     if (!exist_file (menu) || !menu_file_own (menu)){
  635.     free (menu);
  636.         menu = concat_dir_and_file (home_dir, MC_HOME_MENU);
  637.     if (!exist_file (menu)){
  638.         free (menu);
  639.         menu = concat_dir_and_file (mc_home, MC_GLOBAL_MENU);
  640.     }
  641.     }
  642.  
  643.     if ((data = load_file (menu)) == NULL){
  644.     message (1, MSG_ERROR, _(" Can't open file %s \n %s "),
  645.          menu, unix_error_string (errno));
  646.     free (menu);
  647.     return;
  648.     }
  649.     free (menu);
  650.     
  651.     max_cols = 0;
  652.     for (i = 0; i < MAX_ENTRIES; i++)
  653.     entries [i] = 0;
  654.     selected = 0;
  655.  
  656.     /* Parse the menu file */
  657.     old_patterns = easy_patterns;
  658.     p = check_patterns (data);
  659.     for (menu_lines = col = 0; *p; p++){
  660.     if (col == 0 && !entries [menu_lines]){
  661.         if (*p == '#'){
  662.         /* A commented menu entry */
  663.         accept_entry = 1;
  664.         } else if (*p == '+'){
  665.         if (*(p+1) == '='){
  666.             /* Combined adding and default */
  667.             char *q = p++;
  668.             
  669.             p = test_line (q, &accept_entry);
  670.             if (selected == 0 && accept_entry)
  671.             selected = menu_lines;
  672.         } else {
  673.             /* A condition for adding the entry */
  674.             p = test_line (p, &accept_entry);
  675.         }
  676.         } else if (*p == '='){
  677.         if (*(p+1) == '+'){
  678.             char *q = p++;
  679.             /* Combined adding and default */
  680.             p = test_line (q, &accept_entry);
  681.             if (selected == 0 && accept_entry)
  682.             selected = menu_lines;
  683.         } else {
  684.             /* A condition for making the entry default */
  685.             i = 1;
  686.             p = test_line (p, &i);
  687.             if (selected == 0 && i)
  688.             selected = menu_lines;
  689.         }
  690.         }
  691.         else if (*p > ' ' && *p < 127){
  692.         /* A menu entry title line */
  693.         if (accept_entry)
  694.             entries [menu_lines] = p;
  695.         else
  696.             accept_entry = 1;
  697.         }
  698.     }
  699.     if (menu_lines == MAX_ENTRIES)
  700.         break;
  701.     if (*p == '\t')
  702.         *p = ' ';
  703.     col++;
  704.     if (*p == '\n'){
  705.         if (entries [menu_lines]){
  706.         menu_lines++;
  707.         accept_entry = 1;
  708.         }
  709.         max_cols = max (max_cols, col);
  710.         col = 0;
  711.     }
  712.     }
  713.     max_cols = min (max (max_cols, col), MAX_ENTRY_LEN);
  714.  
  715.     /* Create listbox */
  716.     listbox = create_listbox_window (max_cols+2, menu_lines, _(" User menu "),
  717.                      "[Menu File Edit]");
  718.     
  719.     /* insert all the items found */
  720.     for (i = 0; i < menu_lines; i++)
  721.     LISTBOX_APPEND_TEXT (listbox, entries [i][0],
  722.                  extract_line (entries [i],
  723.                        entries [i]+MAX_ENTRY_LEN),
  724.                  entries [i]);
  725.  
  726.     /* Select the default entry */
  727.     listbox_select_by_number (listbox->list, selected);
  728.     
  729.     selected = run_listbox (listbox);
  730.     if (selected >= 0)
  731.     execute_menu_command (entries [selected]);
  732.  
  733.     easy_patterns = old_patterns;
  734.     do_refresh ();
  735.     free (data);
  736. }
  737.