Merge branch '4524_cleanup'
[midnight-commander.git] / src / learn.c
blob949cc9b5948ef6b86f3f1defabea3e086c3b8785
1 /*
2 Learn keys
4 Copyright (C) 1995-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Jakub Jelinek, 1995
9 Andrew Borodin <aborodin@vmail.ru>, 2012, 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 learn.c
28 * \brief Source: learn keys module
31 #include <config.h>
33 #include <stdlib.h>
35 #include "lib/global.h"
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/util.h" /* convert_controls() */
42 #include "lib/widget.h"
44 #include "setup.h"
45 #include "learn.h"
47 /*** global variables ****************************************************************************/
49 /*** file scope macro definitions ****************************************************************/
51 #define UX 4
52 #define UY 2
54 #define ROWS 13
55 #define COLSHIFT 23
57 /*** file scope type declarations ****************************************************************/
59 typedef struct
61 Widget *button;
62 Widget *label;
63 gboolean ok;
64 char *sequence;
65 } learnkey_t;
67 /*** forward declarations (file scope functions) *************************************************/
69 /*** file scope variables ************************************************************************/
71 static WDialog *learn_dlg;
72 static const char *learn_title = N_("Learn keys");
74 static learnkey_t *learnkeys = NULL;
75 static int learn_total;
76 static int learnok;
77 static gboolean learnchanged = FALSE;
79 /* --------------------------------------------------------------------------------------------- */
80 /*** file scope functions ************************************************************************/
81 /* --------------------------------------------------------------------------------------------- */
83 static int
84 learn_button (WButton * button, int action)
86 WDialog *d;
87 char *seq;
89 (void) button;
91 d = create_message (D_ERROR, _("Teach me a key"),
92 _("Please press the %s\n"
93 "and then wait until this message disappears.\n\n"
94 "Then, press it again to see if OK appears\n"
95 "next to its button.\n\n"
96 "If you want to escape, press a single Escape key\n"
97 "and wait as well."), _(key_name_conv_tab[action - B_USER].longname));
98 mc_refresh ();
99 if (learnkeys[action - B_USER].sequence != NULL)
100 MC_PTR_FREE (learnkeys[action - B_USER].sequence);
102 seq = learn_key ();
103 if (seq != NULL)
105 /* Esc hides the dialog and do not allow definitions of
106 * regular characters
108 gboolean seq_ok = FALSE;
110 if (strcmp (seq, "\\e") != 0 && strcmp (seq, "\\e\\e") != 0
111 && strcmp (seq, "^m") != 0 && strcmp (seq, "^i") != 0
112 && (seq[1] != '\0' || *seq < ' ' || *seq > '~'))
114 learnchanged = TRUE;
115 learnkeys[action - B_USER].sequence = seq;
116 seq = convert_controls (seq);
117 seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION);
120 if (!seq_ok)
121 message (D_NORMAL, _("Cannot accept this key"), _("You have entered \"%s\""), seq);
123 g_free (seq);
126 dlg_run_done (d);
127 widget_destroy (WIDGET (d));
129 widget_select (learnkeys[action - B_USER].button);
131 return 0; /* Do not kill learn_dlg */
134 /* --------------------------------------------------------------------------------------------- */
136 static gboolean
137 learn_move (gboolean right)
139 int i, totalcols;
141 totalcols = (learn_total - 1) / ROWS + 1;
142 for (i = 0; i < learn_total; i++)
143 if (learnkeys[i].button == WIDGET (GROUP (learn_dlg)->current->data))
145 if (right)
147 if (i < learn_total - ROWS)
148 i += ROWS;
149 else
150 i %= ROWS;
152 else
154 if (i / ROWS != 0)
155 i -= ROWS;
156 else if (i + (totalcols - 1) * ROWS >= learn_total)
157 i += (totalcols - 2) * ROWS;
158 else
159 i += (totalcols - 1) * ROWS;
161 widget_select (learnkeys[i].button);
162 return TRUE;
165 return FALSE;
168 /* --------------------------------------------------------------------------------------------- */
170 static gboolean
171 learn_check_key (int c)
173 int i;
175 for (i = 0; i < learn_total; i++)
177 if (key_name_conv_tab[i].code != c || learnkeys[i].ok)
178 continue;
180 widget_select (learnkeys[i].button);
181 /* TRANSLATORS: This label appears near learned keys. Keep it short. */
182 label_set_text (LABEL (learnkeys[i].label), _("OK"));
183 learnkeys[i].ok = TRUE;
184 learnok++;
185 if (learnok >= learn_total)
187 learn_dlg->ret_value = B_CANCEL;
188 if (learnchanged)
190 if (query_dialog (learn_title,
192 ("It seems that all your keys already\n"
193 "work fine. That's great."), D_ERROR, 2,
194 _("&Save"), _("&Discard")) == 0)
195 learn_dlg->ret_value = B_ENTER;
197 else
199 message (D_ERROR, learn_title, "%s",
201 ("Great! You have a complete terminal database!\n"
202 "All your keys work well."));
204 dlg_close (learn_dlg);
206 return TRUE;
209 switch (c)
211 case KEY_LEFT:
212 case 'h':
213 return learn_move (FALSE);
214 case KEY_RIGHT:
215 case 'l':
216 return learn_move (TRUE);
217 case 'j':
218 group_select_next_widget (GROUP (learn_dlg));
219 return TRUE;
220 case 'k':
221 group_select_prev_widget (GROUP (learn_dlg));
222 return TRUE;
223 default:
224 break;
227 /* Prevent from disappearing if a non-defined sequence is pressed
228 and contains a button hotkey. Only recognize hotkeys with ALT. */
229 return (c < 255 && g_ascii_isalnum (c));
232 /* --------------------------------------------------------------------------------------------- */
234 static cb_ret_t
235 learn_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
237 switch (msg)
239 case MSG_KEY:
240 return learn_check_key (parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
242 default:
243 return dlg_default_callback (w, sender, msg, parm, data);
247 /* --------------------------------------------------------------------------------------------- */
249 static void
250 init_learn (void)
252 WGroup *g;
254 const int dlg_width = 78;
255 const int dlg_height = 23;
257 /* buttons */
258 int bx0, bx1;
259 const char *b0 = N_("&Save");
260 const char *b1 = N_("&Cancel");
261 int bl0, bl1;
263 int x, y, i;
264 const key_code_name_t *key;
266 #ifdef ENABLE_NLS
267 static gboolean i18n_flag = FALSE;
268 if (!i18n_flag)
270 learn_title = _(learn_title);
271 i18n_flag = TRUE;
274 b0 = _(b0);
275 b1 = _(b1);
276 #endif /* ENABLE_NLS */
278 do_refresh ();
280 learn_dlg =
281 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);
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++)
290 learnok = 0;
291 learnchanged = FALSE;
293 learnkeys = g_new (learnkey_t, learn_total);
295 x = UX;
296 y = UY;
298 /* add buttons and "OK" labels */
299 for (i = 0; i < learn_total; i++)
301 char buffer[BUF_TINY];
302 const char *label;
303 int padding;
305 learnkeys[i].ok = FALSE;
306 learnkeys[i].sequence = NULL;
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, "");
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);
319 y++;
320 if (y == UY + ROWS)
322 x += COLSHIFT;
323 y = UY;
327 group_add_widget (g, hline_new (dlg_height - 8, -1, -1));
328 group_add_widget (g, label_new (dlg_height - 7, 5,
330 ("Press all the keys mentioned here. After you have done it, check\n"
331 "which keys are not marked with OK. Press space on the missing\n"
332 "key, or click with the mouse to define it. Move around with Tab.")));
333 group_add_widget (g, hline_new (dlg_height - 4, -1, -1));
334 /* buttons */
335 bl0 = str_term_width1 (b0) + 5; /* default button */
336 bl1 = str_term_width1 (b1) + 3; /* normal button */
337 bx0 = (dlg_width - (bl0 + bl1 + 1)) / 2;
338 bx1 = bx0 + bl0 + 1;
339 group_add_widget (g, button_new (dlg_height - 3, bx0, B_ENTER, DEFPUSH_BUTTON, b0, NULL));
340 group_add_widget (g, button_new (dlg_height - 3, bx1, B_CANCEL, NORMAL_BUTTON, b1, NULL));
343 /* --------------------------------------------------------------------------------------------- */
345 static void
346 learn_done (void)
348 widget_destroy (WIDGET (learn_dlg));
349 repaint_screen ();
352 /* --------------------------------------------------------------------------------------------- */
354 static void
355 learn_save (void)
357 int i;
358 char *section;
359 gboolean profile_changed = FALSE;
361 section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL);
363 for (i = 0; i < learn_total; i++)
364 if (learnkeys[i].sequence != NULL)
366 char *esc_str;
368 esc_str = str_escape (learnkeys[i].sequence, -1, ";\\", TRUE);
369 mc_config_set_string_raw_value (mc_global.main_config, section,
370 key_name_conv_tab[i].name, esc_str);
371 g_free (esc_str);
373 profile_changed = TRUE;
376 /* On the one hand no good idea to save the complete setup but
377 * without 'Auto save setup' the new key-definitions will not be
378 * saved unless the user does an 'Options/Save Setup'.
379 * On the other hand a save-button that does not save anything to
380 * disk is much worse.
382 if (profile_changed)
383 mc_config_save_file (mc_global.main_config, NULL);
385 g_free (section);
388 /* --------------------------------------------------------------------------------------------- */
389 /*** public functions ****************************************************************************/
390 /* --------------------------------------------------------------------------------------------- */
392 void
393 learn_keys (void)
395 gboolean save_old_esc_mode = old_esc_mode;
396 gboolean save_alternate_plus_minus = mc_global.tty.alternate_plus_minus;
397 int result;
399 /* old_esc_mode cannot work in learn keys dialog */
400 old_esc_mode = 0;
402 /* don't translate KP_ADD, KP_SUBTRACT and
403 KP_MULTIPLY to '+', '-' and '*' in
404 correct_key_code */
405 mc_global.tty.alternate_plus_minus = TRUE;
406 application_keypad_mode ();
408 init_learn ();
409 result = dlg_run (learn_dlg);
411 old_esc_mode = save_old_esc_mode;
412 mc_global.tty.alternate_plus_minus = save_alternate_plus_minus;
414 if (!mc_global.tty.alternate_plus_minus)
415 numeric_keypad_mode ();
417 if (result == B_ENTER)
418 learn_save ();
420 learn_done ();
423 /* --------------------------------------------------------------------------------------------- */