root/src/learn.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. learn_button
  2. learn_move
  3. learn_check_key
  4. learn_callback
  5. init_learn
  6. learn_done
  7. learn_save
  8. learn_keys

   1 /*
   2    Learn keys
   3 
   4    Copyright (C) 1995-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Jakub Jelinek, 1995
   9    Andrew Borodin <aborodin@vmail.ru>, 2012, 2013
  10 
  11    This file is part of the Midnight Commander.
  12 
  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.
  17 
  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.
  22 
  23    You should have received a copy of the GNU General Public License
  24    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  25  */
  26 
  27 /** \file learn.c
  28  *  \brief Source: learn keys module
  29  */
  30 
  31 #include <config.h>
  32 
  33 #include <stdlib.h>
  34 
  35 #include "lib/global.h"
  36 
  37 #include "lib/tty/tty.h"
  38 #include "lib/tty/key.h"
  39 #include "lib/mcconfig.h"
  40 #include "lib/strutil.h"
  41 #include "lib/terminal.h"  // convert_controls()
  42 #include "lib/util.h"      // MC_PTR_FREE
  43 #include "lib/widget.h"
  44 
  45 #include "setup.h"
  46 #include "learn.h"
  47 
  48 /*** global variables ****************************************************************************/
  49 
  50 /*** file scope macro definitions ****************************************************************/
  51 
  52 #define UX       4
  53 #define UY       2
  54 
  55 #define ROWS     13
  56 #define COLSHIFT 23
  57 
  58 /*** file scope type declarations ****************************************************************/
  59 
  60 typedef struct
  61 {
  62     Widget *button;
  63     Widget *label;
  64     gboolean ok;
  65     char *sequence;
  66 } learnkey_t;
  67 
  68 /*** forward declarations (file scope functions) *************************************************/
  69 
  70 /*** file scope variables ************************************************************************/
  71 
  72 static WDialog *learn_dlg;
  73 static const char *learn_title = N_ ("Learn keys");
  74 
  75 static learnkey_t *learnkeys = NULL;
  76 static int learn_total;
  77 static int learnok;
  78 static gboolean learnchanged = FALSE;
  79 
  80 /* --------------------------------------------------------------------------------------------- */
  81 /*** file scope functions ************************************************************************/
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 static int
  85 learn_button (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
  86 {
  87     WDialog *d;
  88     char *seq;
  89 
  90     (void) button;
  91 
  92     d = create_message (D_ERROR, _ ("Teach me a key"),
  93                         _ ("Please press the %s\n"
  94                            "and then wait until this message disappears.\n\n"
  95                            "Then, press it again to see if OK appears\n"
  96                            "next to its button.\n\n"
  97                            "If you want to escape, press a single Escape key\n"
  98                            "and wait as well."),
  99                         _ (key_name_conv_tab[action - B_USER].longname));
 100     mc_refresh ();
 101     if (learnkeys[action - B_USER].sequence != NULL)
 102         MC_PTR_FREE (learnkeys[action - B_USER].sequence);
 103 
 104     seq = learn_key ();
 105     if (seq != NULL)
 106     {
 107         /* Esc hides the dialog and do not allow definitions of
 108          * regular characters
 109          */
 110         gboolean seq_ok = FALSE;
 111 
 112         if (strcmp (seq, "\\e") != 0 && strcmp (seq, "\\e\\e") != 0 && strcmp (seq, "^m") != 0
 113             && strcmp (seq, "^i") != 0 && (seq[1] != '\0' || *seq < ' ' || *seq > '~'))
 114         {
 115             learnchanged = TRUE;
 116             learnkeys[action - B_USER].sequence = seq;
 117             seq = convert_controls (seq);
 118             seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION);
 119         }
 120 
 121         if (!seq_ok)
 122             message (D_NORMAL, _ ("Warning"),
 123                      _ ("Cannot accept this key.\nYou have entered \"%s\""), seq);
 124 
 125         g_free (seq);
 126     }
 127 
 128     dlg_run_done (d);
 129     widget_destroy (WIDGET (d));
 130 
 131     widget_select (learnkeys[action - B_USER].button);
 132 
 133     return 0;  // Do not kill learn_dlg
 134 }
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 
 138 static gboolean
 139 learn_move (gboolean right)
     /* [previous][next][first][last][top][bottom][index][help]  */
 140 {
 141     int i, totalcols;
 142 
 143     totalcols = (learn_total - 1) / ROWS + 1;
 144     for (i = 0; i < learn_total; i++)
 145         if (learnkeys[i].button == WIDGET (GROUP (learn_dlg)->current->data))
 146         {
 147             if (right)
 148             {
 149                 if (i < learn_total - ROWS)
 150                     i += ROWS;
 151                 else
 152                     i %= ROWS;
 153             }
 154             else
 155             {
 156                 if (i / ROWS != 0)
 157                     i -= ROWS;
 158                 else if (i + (totalcols - 1) * ROWS >= learn_total)
 159                     i += (totalcols - 2) * ROWS;
 160                 else
 161                     i += (totalcols - 1) * ROWS;
 162             }
 163             widget_select (learnkeys[i].button);
 164             return TRUE;
 165         }
 166 
 167     return FALSE;
 168 }
 169 
 170 /* --------------------------------------------------------------------------------------------- */
 171 
 172 static gboolean
 173 learn_check_key (int c)
     /* [previous][next][first][last][top][bottom][index][help]  */
 174 {
 175     int i;
 176 
 177     for (i = 0; i < learn_total; i++)
 178     {
 179         if (key_name_conv_tab[i].code != c || learnkeys[i].ok)
 180             continue;
 181 
 182         widget_select (learnkeys[i].button);
 183         // TRANSLATORS: This label appears near learned keys.  Keep it short.
 184         label_set_text (LABEL (learnkeys[i].label), _ ("OK"));
 185         learnkeys[i].ok = TRUE;
 186         learnok++;
 187         if (learnok >= learn_total)
 188         {
 189             learn_dlg->ret_value = B_CANCEL;
 190             if (learnchanged)
 191             {
 192                 if (query_dialog (learn_title,
 193                                   _ ("It seems that all your keys already\n"
 194                                      "work fine. That's great."),
 195                                   D_ERROR, 2, _ ("&Save"), _ ("&Discard"))
 196                     == 0)
 197                     learn_dlg->ret_value = B_ENTER;
 198             }
 199             else
 200             {
 201                 message (D_ERROR, learn_title, "%s",
 202                          _ ("Great! You have a complete terminal database!\n"
 203                             "All your keys work well."));
 204             }
 205             dlg_close (learn_dlg);
 206         }
 207         return TRUE;
 208     }
 209 
 210     switch (c)
 211     {
 212     case KEY_LEFT:
 213     case 'h':
 214         return learn_move (FALSE);
 215     case KEY_RIGHT:
 216     case 'l':
 217         return learn_move (TRUE);
 218     case 'j':
 219         group_select_next_widget (GROUP (learn_dlg));
 220         return TRUE;
 221     case 'k':
 222         group_select_prev_widget (GROUP (learn_dlg));
 223         return TRUE;
 224     default:
 225         break;
 226     }
 227 
 228     /* Prevent from disappearing if a non-defined sequence is pressed
 229        and contains a button hotkey.  Only recognize hotkeys with ALT.  */
 230     return (c < 255 && g_ascii_isalnum (c));
 231 }
 232 
 233 /* --------------------------------------------------------------------------------------------- */
 234 
 235 static cb_ret_t
 236 learn_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 237 {
 238     switch (msg)
 239     {
 240     case MSG_KEY:
 241         return learn_check_key (parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
 242 
 243     default:
 244         return dlg_default_callback (w, sender, msg, parm, data);
 245     }
 246 }
 247 
 248 /* --------------------------------------------------------------------------------------------- */
 249 
 250 static void
 251 init_learn (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 252 {
 253     WGroup *g;
 254 
 255     const int dlg_width = 78;
 256     const int dlg_height = 23;
 257 
 258     // buttons
 259     int bx0, bx1;
 260     const char *b0 = N_ ("&Save");
 261     const char *b1 = N_ ("&Cancel");
 262     int bl0, bl1;
 263 
 264     int x, y, i;
 265     const key_code_name_t *key;
 266 
 267 #ifdef ENABLE_NLS
 268     static gboolean i18n_flag = FALSE;
 269     if (!i18n_flag)
 270     {
 271         learn_title = _ (learn_title);
 272         i18n_flag = TRUE;
 273     }
 274 
 275     b0 = _ (b0);
 276     b1 = _ (b1);
 277 #endif
 278 
 279     do_refresh ();
 280 
 281     learn_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
 282                             learn_callback, NULL, "[Learn keys]", learn_title);
 283     g = GROUP (learn_dlg);
 284 
 285     // find first unshown button
 286     for (key = key_name_conv_tab, learn_total = 0;
 287          key->name != NULL && strcmp (key->name, "kpleft") != 0; key++, learn_total++)
 288         ;
 289 
 290     learnok = 0;
 291     learnchanged = FALSE;
 292 
 293     learnkeys = g_new (learnkey_t, learn_total);
 294 
 295     x = UX;
 296     y = UY;
 297 
 298     // add buttons and "OK" labels
 299     for (i = 0; i < learn_total; i++)
 300     {
 301         char buffer[BUF_TINY];
 302         const char *label;
 303         int padding;
 304 
 305         learnkeys[i].ok = FALSE;
 306         learnkeys[i].sequence = NULL;
 307 
 308         label = _ (key_name_conv_tab[i].longname);
 309         padding = 16 - str_term_width1 (label);
 310         padding = MAX (0, padding);
 311         g_snprintf (buffer, sizeof (buffer), "%s%*s", label, padding, "");
 312 
 313         learnkeys[i].button =
 314             WIDGET (button_new (y, x, B_USER + i, NARROW_BUTTON, buffer, learn_button));
 315         learnkeys[i].label = WIDGET (label_new (y, x + 19, NULL));
 316         group_add_widget (g, learnkeys[i].button);
 317         group_add_widget (g, learnkeys[i].label);
 318 
 319         y++;
 320         if (y == UY + ROWS)
 321         {
 322             x += COLSHIFT;
 323             y = UY;
 324         }
 325     }
 326 
 327     group_add_widget (g, hline_new (dlg_height - 8, -1, -1));
 328     group_add_widget (
 329         g,
 330         label_new (dlg_height - 7, 5,
 331                    _ ("Press all the keys mentioned here. After you have done it, check\n"
 332                       "which keys are not marked with OK. Press space on the missing\n"
 333                       "key, or click with the mouse to define it. Move around with Tab.")));
 334     group_add_widget (g, hline_new (dlg_height - 4, -1, -1));
 335     // buttons
 336     bl0 = str_term_width1 (b0) + 5;  // default button
 337     bl1 = str_term_width1 (b1) + 3;  // normal button
 338     bx0 = (dlg_width - (bl0 + bl1 + 1)) / 2;
 339     bx1 = bx0 + bl0 + 1;
 340     group_add_widget (g, button_new (dlg_height - 3, bx0, B_ENTER, DEFPUSH_BUTTON, b0, NULL));
 341     group_add_widget (g, button_new (dlg_height - 3, bx1, B_CANCEL, NORMAL_BUTTON, b1, NULL));
 342 }
 343 
 344 /* --------------------------------------------------------------------------------------------- */
 345 
 346 static void
 347 learn_done (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 348 {
 349     widget_destroy (WIDGET (learn_dlg));
 350     repaint_screen ();
 351 }
 352 
 353 /* --------------------------------------------------------------------------------------------- */
 354 
 355 static void
 356 learn_save (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 357 {
 358     int i;
 359     char *section;
 360     gboolean profile_changed = FALSE;
 361 
 362     section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL);
 363 
 364     for (i = 0; i < learn_total; i++)
 365         if (learnkeys[i].sequence != NULL)
 366         {
 367             char *esc_str;
 368 
 369             esc_str = str_escape (learnkeys[i].sequence, -1, ";\\", TRUE);
 370             mc_config_set_string_raw_value (mc_global.main_config, section,
 371                                             key_name_conv_tab[i].name, esc_str);
 372             g_free (esc_str);
 373 
 374             profile_changed = TRUE;
 375         }
 376 
 377     /* On the one hand no good idea to save the complete setup but
 378      * without 'Auto save setup' the new key-definitions will not be
 379      * saved unless the user does an 'Options/Save Setup'.
 380      * On the other hand a save-button that does not save anything to
 381      * disk is much worse.
 382      */
 383     if (profile_changed)
 384         mc_config_save_file (mc_global.main_config, NULL);
 385 
 386     g_free (section);
 387 }
 388 
 389 /* --------------------------------------------------------------------------------------------- */
 390 /*** public functions ****************************************************************************/
 391 /* --------------------------------------------------------------------------------------------- */
 392 
 393 void
 394 learn_keys (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 395 {
 396     gboolean save_old_esc_mode = old_esc_mode;
 397     gboolean save_alternate_plus_minus = mc_global.tty.alternate_plus_minus;
 398     int result;
 399 
 400     // old_esc_mode cannot work in learn keys dialog
 401     old_esc_mode = 0;
 402 
 403     /* don't translate KP_ADD, KP_SUBTRACT and
 404        KP_MULTIPLY to '+', '-' and '*' in
 405        correct_key_code */
 406     mc_global.tty.alternate_plus_minus = TRUE;
 407     application_keypad_mode ();
 408 
 409     init_learn ();
 410     result = dlg_run (learn_dlg);
 411 
 412     old_esc_mode = save_old_esc_mode;
 413     mc_global.tty.alternate_plus_minus = save_alternate_plus_minus;
 414 
 415     if (!mc_global.tty.alternate_plus_minus)
 416         numeric_keypad_mode ();
 417 
 418     if (result == B_ENTER)
 419         learn_save ();
 420 
 421     learn_done ();
 422 }
 423 
 424 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */