Use g_string_new_take().
[midnight-commander.git] / src / learn.c
blobc704ce113c21e0c3d590c7c0e3030797f9bdae4a
1 /*
2 Learn keys
4 Copyright (C) 1995-2023
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/strescape.h"
41 #include "lib/strutil.h"
42 #include "lib/util.h" /* convert_controls() */
43 #include "lib/widget.h"
45 #include "setup.h"
46 #include "learn.h"
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 #define UX 4
53 #define UY 2
55 #define ROWS 13
56 #define COLSHIFT 23
58 /*** file scope type declarations ****************************************************************/
60 typedef struct
62 Widget *button;
63 Widget *label;
64 gboolean ok;
65 char *sequence;
66 } learnkey_t;
68 /*** forward declarations (file scope functions) *************************************************/
70 /*** file scope variables ************************************************************************/
72 static WDialog *learn_dlg;
73 static const char *learn_title = N_("Learn keys");
75 static learnkey_t *learnkeys = NULL;
76 static int learn_total;
77 static int learnok;
78 static gboolean learnchanged = FALSE;
80 /* --------------------------------------------------------------------------------------------- */
81 /*** file scope functions ************************************************************************/
82 /* --------------------------------------------------------------------------------------------- */
84 static int
85 learn_button (WButton * button, int action)
87 WDialog *d;
88 char *seq;
90 (void) button;
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."), _(key_name_conv_tab[action - B_USER].longname));
99 mc_refresh ();
100 if (learnkeys[action - B_USER].sequence != NULL)
101 MC_PTR_FREE (learnkeys[action - B_USER].sequence);
103 seq = learn_key ();
104 if (seq != NULL)
106 /* Esc hides the dialog and do not allow definitions of
107 * regular characters
109 gboolean seq_ok = FALSE;
111 if (strcmp (seq, "\\e") != 0 && strcmp (seq, "\\e\\e") != 0
112 && strcmp (seq, "^m") != 0 && strcmp (seq, "^i") != 0
113 && (seq[1] != '\0' || *seq < ' ' || *seq > '~'))
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);
121 if (!seq_ok)
122 message (D_NORMAL, _("Cannot accept this key"), _("You have entered \"%s\""), seq);
124 g_free (seq);
127 dlg_run_done (d);
128 widget_destroy (WIDGET (d));
130 widget_select (learnkeys[action - B_USER].button);
132 return 0; /* Do not kill learn_dlg */
135 /* --------------------------------------------------------------------------------------------- */
137 static gboolean
138 learn_move (gboolean right)
140 int i, totalcols;
142 totalcols = (learn_total - 1) / ROWS + 1;
143 for (i = 0; i < learn_total; i++)
144 if (learnkeys[i].button == WIDGET (GROUP (learn_dlg)->current->data))
146 if (right)
148 if (i < learn_total - ROWS)
149 i += ROWS;
150 else
151 i %= ROWS;
153 else
155 if (i / ROWS != 0)
156 i -= ROWS;
157 else if (i + (totalcols - 1) * ROWS >= learn_total)
158 i += (totalcols - 2) * ROWS;
159 else
160 i += (totalcols - 1) * ROWS;
162 widget_select (learnkeys[i].button);
163 return TRUE;
166 return FALSE;
169 /* --------------------------------------------------------------------------------------------- */
171 static gboolean
172 learn_check_key (int c)
174 int i;
176 for (i = 0; i < learn_total; i++)
178 if (key_name_conv_tab[i].code != c || learnkeys[i].ok)
179 continue;
181 widget_select (learnkeys[i].button);
182 /* TRANSLATORS: This label appears near learned keys. Keep it short. */
183 label_set_text (LABEL (learnkeys[i].label), _("OK"));
184 learnkeys[i].ok = TRUE;
185 learnok++;
186 if (learnok >= learn_total)
188 learn_dlg->ret_value = B_CANCEL;
189 if (learnchanged)
191 if (query_dialog (learn_title,
193 ("It seems that all your keys already\n"
194 "work fine. That's great."), D_ERROR, 2,
195 _("&Save"), _("&Discard")) == 0)
196 learn_dlg->ret_value = B_ENTER;
198 else
200 message (D_ERROR, learn_title, "%s",
202 ("Great! You have a complete terminal database!\n"
203 "All your keys work well."));
205 dlg_close (learn_dlg);
207 return TRUE;
210 switch (c)
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;
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));
233 /* --------------------------------------------------------------------------------------------- */
235 static cb_ret_t
236 learn_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
238 switch (msg)
240 case MSG_KEY:
241 return learn_check_key (parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
243 default:
244 return dlg_default_callback (w, sender, msg, parm, data);
248 /* --------------------------------------------------------------------------------------------- */
250 static void
251 init_learn (void)
253 WGroup *g;
255 const int dlg_width = 78;
256 const int dlg_height = 23;
258 /* buttons */
259 int bx0, bx1;
260 const char *b0 = N_("&Save");
261 const char *b1 = N_("&Cancel");
262 int bl0, bl1;
264 int x, y, i;
265 const key_code_name_t *key;
267 #ifdef ENABLE_NLS
268 static gboolean i18n_flag = FALSE;
269 if (!i18n_flag)
271 learn_title = _(learn_title);
272 i18n_flag = TRUE;
275 b0 = _(b0);
276 b1 = _(b1);
277 #endif /* ENABLE_NLS */
279 do_refresh ();
281 learn_dlg =
282 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
283 learn_callback, NULL, "[Learn keys]", learn_title);
284 g = GROUP (learn_dlg);
286 /* find first unshown button */
287 for (key = key_name_conv_tab, learn_total = 0;
288 key->name != NULL && strcmp (key->name, "kpleft") != 0; key++, learn_total++)
291 learnok = 0;
292 learnchanged = FALSE;
294 learnkeys = g_new (learnkey_t, learn_total);
296 x = UX;
297 y = UY;
299 /* add buttons and "OK" labels */
300 for (i = 0; i < learn_total; i++)
302 char buffer[BUF_TINY];
303 const char *label;
304 int padding;
306 learnkeys[i].ok = FALSE;
307 learnkeys[i].sequence = NULL;
309 label = _(key_name_conv_tab[i].longname);
310 padding = 16 - str_term_width1 (label);
311 padding = MAX (0, padding);
312 g_snprintf (buffer, sizeof (buffer), "%s%*s", label, padding, "");
314 learnkeys[i].button =
315 WIDGET (button_new (y, x, B_USER + i, NARROW_BUTTON, buffer, learn_button));
316 learnkeys[i].label = WIDGET (label_new (y, x + 19, NULL));
317 group_add_widget (g, learnkeys[i].button);
318 group_add_widget (g, learnkeys[i].label);
320 y++;
321 if (y == UY + ROWS)
323 x += COLSHIFT;
324 y = UY;
328 group_add_widget (g, hline_new (dlg_height - 8, -1, -1));
329 group_add_widget (g, 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));
344 /* --------------------------------------------------------------------------------------------- */
346 static void
347 learn_done (void)
349 widget_destroy (WIDGET (learn_dlg));
350 repaint_screen ();
353 /* --------------------------------------------------------------------------------------------- */
355 static void
356 learn_save (void)
358 int i;
359 char *section;
360 gboolean profile_changed = FALSE;
362 section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL);
364 for (i = 0; i < learn_total; i++)
365 if (learnkeys[i].sequence != NULL)
367 char *esc_str;
369 esc_str = strutils_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);
374 profile_changed = TRUE;
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.
383 if (profile_changed)
384 mc_config_save_file (mc_global.main_config, NULL);
386 g_free (section);
389 /* --------------------------------------------------------------------------------------------- */
390 /*** public functions ****************************************************************************/
391 /* --------------------------------------------------------------------------------------------- */
393 void
394 learn_keys (void)
396 gboolean save_old_esc_mode = old_esc_mode;
397 gboolean save_alternate_plus_minus = mc_global.tty.alternate_plus_minus;
398 int result;
400 /* old_esc_mode cannot work in learn keys dialog */
401 old_esc_mode = 0;
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 ();
409 init_learn ();
410 result = dlg_run (learn_dlg);
412 old_esc_mode = save_old_esc_mode;
413 mc_global.tty.alternate_plus_minus = save_alternate_plus_minus;
415 if (!mc_global.tty.alternate_plus_minus)
416 numeric_keypad_mode ();
418 if (result == B_ENTER)
419 learn_save ();
421 learn_done ();
424 /* --------------------------------------------------------------------------------------------- */