Manual pages: mcmcdiffmceditmcview

root/lib/widget/input.c

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

DEFINITIONS

This source file includes following definitions.
  1. get_history_length
  2. draw_history_button
  3. input_mark_cmd
  4. input_eval_marks
  5. do_show_hist
  6. input_history_strip_password
  7. input_push_history
  8. move_buffer_backward
  9. beginning_of_line
  10. end_of_line
  11. backward_char
  12. forward_char
  13. forward_word
  14. backward_word
  15. backward_delete
  16. copy_region
  17. delete_region
  18. insert_char
  19. delete_char
  20. kill_word
  21. back_kill_word
  22. yank
  23. kill_line
  24. clear_line
  25. ins_from_clip
  26. hist_prev
  27. hist_next
  28. port_region_marked_for_delete
  29. input_execute_cmd
  30. input_load_history
  31. input_save_history
  32. input_destroy
  33. input_screen_to_point
  34. input_mouse_callback
  35. input_new
  36. input_callback
  37. input_set_default_colors
  38. input_handle_char
  39. input_assign_text
  40. input_insert
  41. input_set_point
  42. input_update
  43. input_enable_update
  44. input_disable_update
  45. input_clean

   1 /*
   2    Widgets for the Midnight Commander
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   7    Authors:
   8    Radek Doulik, 1994, 1995
   9    Miguel de Icaza, 1994, 1995
  10    Jakub Jelinek, 1995
  11    Andrej Borsenkow, 1996
  12    Norbert Warmuth, 1997
  13    Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  14 
  15    This file is part of the Midnight Commander.
  16 
  17    The Midnight Commander is free software: you can redistribute it
  18    and/or modify it under the terms of the GNU General Public License as
  19    published by the Free Software Foundation, either version 3 of the License,
  20    or (at your option) any later version.
  21 
  22    The Midnight Commander is distributed in the hope that it will be useful,
  23    but WITHOUT ANY WARRANTY; without even the implied warranty of
  24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25    GNU General Public License for more details.
  26 
  27    You should have received a copy of the GNU General Public License
  28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  29  */
  30 
  31 /** \file input.c
  32  *  \brief Source: WInput widget
  33  */
  34 
  35 #include <config.h>
  36 
  37 #include <stdlib.h>
  38 #include <sys/types.h>
  39 #include <sys/stat.h>
  40 
  41 #include "lib/global.h"
  42 
  43 #include "lib/tty/tty.h"
  44 #include "lib/tty/key.h"  // XCTRL and ALT macros
  45 #include "lib/fileloc.h"
  46 #include "lib/skin.h"
  47 #include "lib/strutil.h"
  48 #include "lib/util.h"
  49 #include "lib/widget.h"
  50 #include "lib/event.h"     // mc_event_raise()
  51 #include "lib/mcconfig.h"  // mc_config_history_*()
  52 
  53 /*** global variables ****************************************************************************/
  54 
  55 gboolean quote = FALSE;
  56 
  57 const global_keymap_t *input_map = NULL;
  58 
  59 /* Color styles for input widgets */
  60 input_colors_t input_colors;
  61 
  62 /*** file scope macro definitions ****************************************************************/
  63 
  64 #define LARGE_HISTORY_BUTTON 1
  65 
  66 #ifdef LARGE_HISTORY_BUTTON
  67 #define HISTORY_BUTTON_WIDTH 3
  68 #else
  69 #define HISTORY_BUTTON_WIDTH 1
  70 #endif
  71 
  72 #define should_show_history_button(in)                                                             \
  73     (in->history.list != NULL && WIDGET (in)->rect.cols > HISTORY_BUTTON_WIDTH * 2 + 1             \
  74      && WIDGET (in)->owner != NULL)
  75 
  76 /*** file scope type declarations ****************************************************************/
  77 
  78 /*** forward declarations (file scope functions) *************************************************/
  79 
  80 /*** file scope variables ************************************************************************/
  81 
  82 /* Input widgets have a global kill ring */
  83 /* Pointer to killed data */
  84 static char *kill_buffer = NULL;
  85 
  86 /* --------------------------------------------------------------------------------------------- */
  87 /*** file scope functions ************************************************************************/
  88 /* --------------------------------------------------------------------------------------------- */
  89 
  90 static size_t
  91 get_history_length (GList *history)
     /* [previous][next][first][last][top][bottom][index][help]  */
  92 {
  93     size_t len = 0;
  94 
  95     for (; history != NULL; history = g_list_previous (history))
  96         len++;
  97 
  98     return len;
  99 }
 100 
 101 /* --------------------------------------------------------------------------------------------- */
 102 
 103 static void
 104 draw_history_button (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 105 {
 106     char c;
 107     gboolean disabled;
 108 
 109     if (g_list_next (in->history.current) == NULL)
 110         c = '^';
 111     else if (g_list_previous (in->history.current) == NULL)
 112         c = 'v';
 113     else
 114         c = '|';
 115 
 116     widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH);
 117     disabled = widget_get_state (WIDGET (in), WST_DISABLED);
 118     tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
 119 
 120 #ifdef LARGE_HISTORY_BUTTON
 121     tty_print_string ("[ ]");
 122     widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH + 1);
 123 #endif
 124 
 125     tty_print_char (c);
 126 }
 127 
 128 /* --------------------------------------------------------------------------------------------- */
 129 
 130 static void
 131 input_mark_cmd (WInput *in, gboolean mark)
     /* [previous][next][first][last][top][bottom][index][help]  */
 132 {
 133     in->mark = mark ? in->point : -1;
 134 }
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 
 138 static gboolean
 139 input_eval_marks (WInput *in, long *start_mark, long *end_mark)
     /* [previous][next][first][last][top][bottom][index][help]  */
 140 {
 141     if (in->mark >= 0)
 142     {
 143         *start_mark = MIN (in->mark, in->point);
 144         *end_mark = MAX (in->mark, in->point);
 145         return TRUE;
 146     }
 147 
 148     *start_mark = *end_mark = -1;
 149     return FALSE;
 150 }
 151 
 152 /* --------------------------------------------------------------------------------------------- */
 153 
 154 static void
 155 do_show_hist (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 156 {
 157     size_t len;
 158     history_descriptor_t hd;
 159 
 160     len = get_history_length (in->history.list);
 161 
 162     history_descriptor_init (&hd, WIDGET (in)->rect.y, WIDGET (in)->rect.x, in->history.list,
 163                              g_list_position (in->history.list, in->history.list));
 164     history_show (&hd);
 165 
 166     /* in->history.list was destroyed in history_show().
 167      * Apply new history and current position to avoid use-after-free. */
 168     in->history.list = hd.list;
 169     in->history.current = in->history.list;
 170     if (hd.text != NULL)
 171     {
 172         input_assign_text (in, hd.text);
 173         g_free (hd.text);
 174     }
 175 
 176     // Has history cleaned up or not?
 177     if (len != get_history_length (in->history.list))
 178         in->history.changed = TRUE;
 179 }
 180 
 181 /* --------------------------------------------------------------------------------------------- */
 182 /**
 183  * Strip password from incomplete url (just user:pass@host without VFS prefix).
 184  *
 185  * @param url partial URL
 186  * @return newly allocated string without password
 187  */
 188 
 189 static char *
 190 input_history_strip_password (char *url)
     /* [previous][next][first][last][top][bottom][index][help]  */
 191 {
 192     char *at, *delim, *colon;
 193 
 194     at = strrchr (url, '@');
 195     if (at == NULL)
 196         return g_strdup (url);
 197 
 198     // TODO: handle ':' and '@' in password
 199 
 200     delim = strstr (url, VFS_PATH_URL_DELIMITER);
 201     if (delim != NULL)
 202         colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':');
 203     else
 204         colon = strchr (url, ':');
 205 
 206     // if 'colon' before 'at', 'colon' delimits user and password: user:password@host
 207     // if 'colon' after 'at', 'colon' delimits host and port: user@host:port
 208     if (colon != NULL && colon > at)
 209         colon = NULL;
 210 
 211     if (colon == NULL)
 212         return g_strdup (url);
 213     *colon = '\0';
 214 
 215     return g_strconcat (url, at, (char *) NULL);
 216 }
 217 
 218 /* --------------------------------------------------------------------------------------------- */
 219 
 220 static void
 221 input_push_history (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 222 {
 223     char *t;
 224     gboolean empty;
 225 
 226     t = g_strstrip (input_get_text (in));
 227     empty = *t == '\0';
 228     if (!empty)
 229     {
 230         g_free (t);
 231         t = input_get_text (in);
 232 
 233         if (in->history.name != NULL && in->strip_password)
 234         {
 235             /*
 236                We got string user:pass@host without any VFS prefixes
 237                and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work.
 238                Therefore we want to strip password in separate algorithm
 239              */
 240             char *url_with_stripped_password;
 241 
 242             url_with_stripped_password = input_history_strip_password (t);
 243             g_free (t);
 244             t = url_with_stripped_password;
 245         }
 246     }
 247 
 248     if (in->history.list == NULL || in->history.list->data == NULL
 249         || strcmp (in->history.list->data, t) != 0 || in->history.changed)
 250     {
 251         in->history.list = list_append_unique (in->history.list, t);
 252         in->history.current = in->history.list;
 253         in->history.changed = TRUE;
 254     }
 255     else
 256         g_free (t);
 257 
 258     in->need_push = FALSE;
 259 }
 260 
 261 /* --------------------------------------------------------------------------------------------- */
 262 
 263 static void
 264 move_buffer_backward (WInput *in, int start, int end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 265 {
 266     int str_len;
 267 
 268     str_len = str_length (in->buffer->str);
 269     if (start >= str_len || end > str_len + 1)
 270         return;
 271 
 272     start = str_offset_to_pos (in->buffer->str, start);
 273     end = str_offset_to_pos (in->buffer->str, end);
 274     g_string_erase (in->buffer, start, end - start);
 275 }
 276 
 277 /* --------------------------------------------------------------------------------------------- */
 278 
 279 static void
 280 beginning_of_line (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 281 {
 282     in->point = 0;
 283     in->charpoint = 0;
 284 }
 285 
 286 /* --------------------------------------------------------------------------------------------- */
 287 
 288 static void
 289 end_of_line (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 290 {
 291     in->point = str_length (in->buffer->str);
 292     in->charpoint = 0;
 293 }
 294 
 295 /* --------------------------------------------------------------------------------------------- */
 296 
 297 static void
 298 backward_char (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 299 {
 300     if (in->point > 0)
 301     {
 302         const char *act;
 303 
 304         act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 305         in->point -= str_cprev_noncomb_char (&act, in->buffer->str);
 306     }
 307 
 308     in->charpoint = 0;
 309 }
 310 
 311 /* --------------------------------------------------------------------------------------------- */
 312 
 313 static void
 314 forward_char (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 315 {
 316     const char *act;
 317 
 318     act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 319     if (act[0] != '\0')
 320         in->point += str_cnext_noncomb_char (&act);
 321     in->charpoint = 0;
 322 }
 323 
 324 /* --------------------------------------------------------------------------------------------- */
 325 
 326 static void
 327 forward_word (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 328 {
 329     const char *p;
 330 
 331     p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 332 
 333     for (; p[0] != '\0' && (str_isspace (p) || str_ispunct (p)); in->point++)
 334         str_cnext_char (&p);
 335 
 336     for (; p[0] != '\0' && !str_isspace (p) && !str_ispunct (p); in->point++)
 337         str_cnext_char (&p);
 338 }
 339 
 340 /* --------------------------------------------------------------------------------------------- */
 341 
 342 static void
 343 backward_word (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 344 {
 345     const char *p;
 346 
 347     p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 348 
 349     for (; p != in->buffer->str; in->point--)
 350     {
 351         const char *p_tmp;
 352 
 353         p_tmp = p;
 354         str_cprev_char (&p);
 355         if (!str_isspace (p) && !str_ispunct (p))
 356         {
 357             p = p_tmp;
 358             break;
 359         }
 360     }
 361 
 362     for (; p != in->buffer->str; in->point--)
 363     {
 364         str_cprev_char (&p);
 365         if (str_isspace (p) || str_ispunct (p))
 366             break;
 367     }
 368 }
 369 
 370 /* --------------------------------------------------------------------------------------------- */
 371 
 372 static void
 373 backward_delete (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 374 {
 375     const char *act;
 376     int start;
 377 
 378     if (in->point == 0)
 379         return;
 380 
 381     act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 382     start = in->point - str_cprev_noncomb_char (&act, in->buffer->str);
 383     move_buffer_backward (in, start, in->point);
 384     in->charpoint = 0;
 385     in->need_push = TRUE;
 386     in->point = start;
 387 }
 388 
 389 /* --------------------------------------------------------------------------------------------- */
 390 
 391 static void
 392 copy_region (WInput *in, int start, int end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 393 {
 394     int first = MIN (start, end);
 395     int last = MAX (start, end);
 396 
 397     if (last == first)
 398     {
 399         // Copy selected files to clipboard
 400         mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL);
 401         // try use external clipboard utility
 402         mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
 403         return;
 404     }
 405 
 406     g_free (kill_buffer);
 407 
 408     first = str_offset_to_pos (in->buffer->str, first);
 409     last = str_offset_to_pos (in->buffer->str, last);
 410 
 411     kill_buffer = g_strndup (in->buffer->str + first, last - first);
 412 
 413     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer);
 414     // try use external clipboard utility
 415     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
 416 }
 417 
 418 /* --------------------------------------------------------------------------------------------- */
 419 
 420 static void
 421 delete_region (WInput *in, int start, int end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 422 {
 423     int first = MIN (start, end);
 424     int last = MAX (start, end);
 425 
 426     input_mark_cmd (in, FALSE);
 427     in->point = first;
 428     move_buffer_backward (in, first, last);
 429     in->charpoint = 0;
 430     in->need_push = TRUE;
 431 }
 432 
 433 /* --------------------------------------------------------------------------------------------- */
 434 
 435 static cb_ret_t
 436 insert_char (WInput *in, int c_code)
     /* [previous][next][first][last][top][bottom][index][help]  */
 437 {
 438     int res;
 439     long m1, m2;
 440     size_t ins_point;
 441 
 442     if (input_eval_marks (in, &m1, &m2))
 443         delete_region (in, m1, m2);
 444 
 445     if (c_code == -1)
 446         return MSG_NOT_HANDLED;
 447 
 448     if (in->charpoint >= MB_LEN_MAX)
 449         return MSG_HANDLED;
 450 
 451     in->charbuf[in->charpoint] = c_code;
 452     in->charpoint++;
 453 
 454     res = str_is_valid_char (in->charbuf, in->charpoint);
 455     if (res < 0)
 456     {
 457         if (res != -2)
 458             in->charpoint = 0;  // broken multibyte char, skip
 459         return MSG_HANDLED;
 460     }
 461 
 462     in->need_push = TRUE;
 463     ins_point = str_offset_to_pos (in->buffer->str, in->point);
 464     g_string_insert_len (in->buffer, ins_point, in->charbuf, in->charpoint);
 465     in->point++;
 466     in->charpoint = 0;
 467 
 468     return MSG_HANDLED;
 469 }
 470 
 471 /* --------------------------------------------------------------------------------------------- */
 472 
 473 static void
 474 delete_char (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 475 {
 476     const char *act;
 477     int end;
 478 
 479     act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
 480     end = in->point + str_cnext_noncomb_char (&act);
 481     move_buffer_backward (in, in->point, end);
 482     in->charpoint = 0;
 483     in->need_push = TRUE;
 484 }
 485 
 486 /* --------------------------------------------------------------------------------------------- */
 487 
 488 static void
 489 kill_word (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 490 {
 491     int old_point = in->point;
 492     int new_point;
 493 
 494     forward_word (in);
 495     new_point = in->point;
 496     in->point = old_point;
 497 
 498     delete_region (in, old_point, new_point);
 499     in->need_push = TRUE;
 500     in->charpoint = 0;
 501 }
 502 
 503 /* --------------------------------------------------------------------------------------------- */
 504 
 505 static void
 506 back_kill_word (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 507 {
 508     int old_point = in->point;
 509     int new_point;
 510 
 511     backward_word (in);
 512     new_point = in->point;
 513     in->point = old_point;
 514 
 515     delete_region (in, old_point, new_point);
 516     in->need_push = TRUE;
 517 }
 518 
 519 /* --------------------------------------------------------------------------------------------- */
 520 
 521 static void
 522 yank (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 523 {
 524     if (kill_buffer != NULL)
 525     {
 526         char *p;
 527 
 528         in->charpoint = 0;
 529         for (p = kill_buffer; *p != '\0'; p++)
 530             insert_char (in, *p);
 531         in->charpoint = 0;
 532     }
 533 }
 534 
 535 /* --------------------------------------------------------------------------------------------- */
 536 
 537 static void
 538 kill_line (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 539 {
 540     int chp;
 541 
 542     chp = str_offset_to_pos (in->buffer->str, in->point);
 543     g_free (kill_buffer);
 544     kill_buffer = g_strndup (in->buffer->str + chp, in->buffer->len - chp);
 545     g_string_set_size (in->buffer, chp);
 546     in->charpoint = 0;
 547 }
 548 
 549 /* --------------------------------------------------------------------------------------------- */
 550 
 551 static void
 552 clear_line (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 553 {
 554     in->need_push = TRUE;
 555     g_string_set_size (in->buffer, 0);
 556     in->point = 0;
 557     in->mark = -1;
 558     in->charpoint = 0;
 559 }
 560 
 561 /* --------------------------------------------------------------------------------------------- */
 562 
 563 static void
 564 ins_from_clip (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 565 {
 566     char *p = NULL;
 567     ev_clipboard_text_from_file_t event_data = { NULL, FALSE };
 568 
 569     // try use external clipboard utility
 570     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
 571 
 572     event_data.text = &p;
 573     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data);
 574     if (event_data.ret)
 575     {
 576         char *pp;
 577 
 578         for (pp = p; *pp != '\0'; pp++)
 579             insert_char (in, *pp);
 580 
 581         g_free (p);
 582     }
 583 }
 584 
 585 /* --------------------------------------------------------------------------------------------- */
 586 
 587 static void
 588 hist_prev (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 589 {
 590     GList *prev;
 591 
 592     if (in->history.list == NULL)
 593         return;
 594 
 595     if (in->need_push)
 596         input_push_history (in);
 597 
 598     prev = g_list_previous (in->history.current);
 599     if (prev != NULL)
 600     {
 601         input_assign_text (in, (char *) prev->data);
 602         in->history.current = prev;
 603         in->history.changed = TRUE;
 604         in->need_push = FALSE;
 605     }
 606 }
 607 
 608 /* --------------------------------------------------------------------------------------------- */
 609 
 610 static void
 611 hist_next (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 612 {
 613     GList *next;
 614 
 615     if (in->need_push)
 616     {
 617         input_push_history (in);
 618         input_assign_text (in, "");
 619         return;
 620     }
 621 
 622     if (in->history.list == NULL)
 623         return;
 624 
 625     next = g_list_next (in->history.current);
 626     if (next == NULL)
 627     {
 628         input_assign_text (in, "");
 629         in->history.current = in->history.list;
 630     }
 631     else
 632     {
 633         input_assign_text (in, (char *) next->data);
 634         in->history.current = next;
 635         in->history.changed = TRUE;
 636         in->need_push = FALSE;
 637     }
 638 }
 639 
 640 /* --------------------------------------------------------------------------------------------- */
 641 
 642 static void
 643 port_region_marked_for_delete (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 644 {
 645     g_string_set_size (in->buffer, 0);
 646     in->point = 0;
 647     in->first = FALSE;
 648     in->charpoint = 0;
 649 }
 650 
 651 /* --------------------------------------------------------------------------------------------- */
 652 
 653 static cb_ret_t
 654 input_execute_cmd (WInput *in, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 655 {
 656     cb_ret_t res = MSG_HANDLED;
 657 
 658     switch (command)
 659     {
 660     case CK_MarkLeft:
 661     case CK_MarkRight:
 662     case CK_MarkToWordBegin:
 663     case CK_MarkToWordEnd:
 664     case CK_MarkToHome:
 665     case CK_MarkToEnd:
 666         // a highlight command like shift-arrow
 667         if (in->mark < 0)
 668         {
 669             input_mark_cmd (in, FALSE);  // clear
 670             input_mark_cmd (in, TRUE);   // marking on
 671         }
 672         break;
 673     case CK_WordRight:
 674     case CK_WordLeft:
 675     case CK_Right:
 676     case CK_Left:
 677         if (in->mark >= 0)
 678             input_mark_cmd (in, FALSE);
 679         break;
 680     default:
 681         break;
 682     }
 683 
 684     switch (command)
 685     {
 686     case CK_Home:
 687     case CK_MarkToHome:
 688         beginning_of_line (in);
 689         break;
 690     case CK_End:
 691     case CK_MarkToEnd:
 692         end_of_line (in);
 693         break;
 694     case CK_Left:
 695     case CK_MarkLeft:
 696         backward_char (in);
 697         break;
 698     case CK_WordLeft:
 699     case CK_MarkToWordBegin:
 700         backward_word (in);
 701         break;
 702     case CK_Right:
 703     case CK_MarkRight:
 704         forward_char (in);
 705         break;
 706     case CK_WordRight:
 707     case CK_MarkToWordEnd:
 708         forward_word (in);
 709         break;
 710     case CK_BackSpace:
 711     {
 712         long m1, m2;
 713 
 714         if (input_eval_marks (in, &m1, &m2))
 715             delete_region (in, m1, m2);
 716         else
 717             backward_delete (in);
 718     }
 719     break;
 720     case CK_Delete:
 721         if (in->first)
 722             port_region_marked_for_delete (in);
 723         else
 724         {
 725             long m1, m2;
 726 
 727             if (input_eval_marks (in, &m1, &m2))
 728                 delete_region (in, m1, m2);
 729             else
 730                 delete_char (in);
 731         }
 732         break;
 733     case CK_DeleteToWordEnd:
 734         kill_word (in);
 735         break;
 736     case CK_DeleteToWordBegin:
 737         back_kill_word (in);
 738         break;
 739     case CK_Mark:
 740         input_mark_cmd (in, TRUE);
 741         break;
 742     case CK_Remove:
 743         delete_region (in, in->point, MAX (in->mark, 0));
 744         break;
 745     case CK_DeleteToEnd:
 746         kill_line (in);
 747         break;
 748     case CK_Clear:
 749         clear_line (in);
 750         break;
 751     case CK_Store:
 752         copy_region (in, MAX (in->mark, 0), in->point);
 753         break;
 754     case CK_Cut:
 755     {
 756         long m;
 757 
 758         m = MAX (in->mark, 0);
 759         copy_region (in, m, in->point);
 760         delete_region (in, in->point, m);
 761     }
 762     break;
 763     case CK_Yank:
 764         yank (in);
 765         break;
 766     case CK_Paste:
 767         ins_from_clip (in);
 768         break;
 769     case CK_HistoryPrev:
 770         hist_prev (in);
 771         break;
 772     case CK_HistoryNext:
 773         hist_next (in);
 774         break;
 775     case CK_History:
 776         do_show_hist (in);
 777         break;
 778     case CK_Complete:
 779         input_complete (in);
 780         break;
 781     default:
 782         res = MSG_NOT_HANDLED;
 783     }
 784 
 785     switch (command)
 786     {
 787     case CK_MarkLeft:
 788     case CK_MarkRight:
 789     case CK_MarkToWordBegin:
 790     case CK_MarkToWordEnd:
 791     case CK_MarkToHome:
 792     case CK_MarkToEnd:
 793         // do nothing
 794         break;
 795     default:
 796         in->mark = -1;
 797         break;
 798     }
 799 
 800     return res;
 801 }
 802 
 803 /* --------------------------------------------------------------------------------------------- */
 804 
 805 /* "history_load" event handler */
 806 static gboolean
 807 input_load_history (const gchar *event_group_name, const gchar *event_name, gpointer init_data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 808                     gpointer data)
 809 {
 810     WInput *in = INPUT (init_data);
 811     ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
 812 
 813     (void) event_group_name;
 814     (void) event_name;
 815 
 816     in->history.list = mc_config_history_load (ev->cfg, in->history.name);
 817     in->history.current = in->history.list;
 818 
 819     if (in->init_from_history)
 820     {
 821         const char *def_text = "";
 822 
 823         if (in->history.list != NULL && in->history.list->data != NULL)
 824             def_text = (const char *) in->history.list->data;
 825 
 826         input_assign_text (in, def_text);
 827     }
 828 
 829     return TRUE;
 830 }
 831 
 832 /* --------------------------------------------------------------------------------------------- */
 833 
 834 /* "history_save" event handler */
 835 static gboolean
 836 input_save_history (const gchar *event_group_name, const gchar *event_name, gpointer init_data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 837                     gpointer data)
 838 {
 839     WInput *in = INPUT (init_data);
 840 
 841     (void) event_group_name;
 842     (void) event_name;
 843 
 844     if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL))
 845         input_push_history (in);
 846 
 847     // save modified history regardless of ret_value
 848     if (in->history.changed)
 849     {
 850         const ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
 851 
 852         mc_config_history_save (ev->cfg, in->history.name, in->history.list);
 853     }
 854 
 855     in->history.changed = FALSE;
 856 
 857     return TRUE;
 858 }
 859 
 860 /* --------------------------------------------------------------------------------------------- */
 861 
 862 static void
 863 input_destroy (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 864 {
 865     input_complete_free (in);
 866 
 867     // clean history
 868     if (in->history.list != NULL)
 869     {
 870         // history is already saved before this moment
 871         in->history.list = g_list_first (in->history.list);
 872         g_list_free_full (in->history.list, g_free);
 873     }
 874     g_free (in->history.name);
 875     g_string_free (in->buffer, TRUE);
 876     MC_PTR_FREE (kill_buffer);
 877 }
 878 
 879 /* --------------------------------------------------------------------------------------------- */
 880 
 881 /**
 882  * Calculates the buffer index (aka "point") corresponding to some screen coordinate.
 883  */
 884 static int
 885 input_screen_to_point (const WInput *in, int x)
     /* [previous][next][first][last][top][bottom][index][help]  */
 886 {
 887     x += in->term_first_shown;
 888 
 889     if (x < 0)
 890         return 0;
 891 
 892     if (x < str_term_width1 (in->buffer->str))
 893         return str_column_to_pos (in->buffer->str, x);
 894 
 895     return str_length (in->buffer->str);
 896 }
 897 
 898 /* --------------------------------------------------------------------------------------------- */
 899 
 900 static void
 901 input_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 902 {
 903     // save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG
 904     static int prev_point = 0;
 905     WInput *in = INPUT (w);
 906 
 907     switch (msg)
 908     {
 909     case MSG_MOUSE_DOWN:
 910         widget_select (w);
 911 
 912         if (event->x >= w->rect.cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
 913             do_show_hist (in);
 914         else
 915         {
 916             in->first = FALSE;
 917             input_mark_cmd (in, FALSE);
 918             input_set_point (in, input_screen_to_point (in, event->x));
 919             // save point for the possible following MSG_MOUSE_DRAG action
 920             prev_point = in->point;
 921         }
 922         break;
 923 
 924     case MSG_MOUSE_DRAG:
 925         // start point: set marker using point before first MSG_MOUSE_DRAG action
 926         if (in->mark < 0)
 927             in->mark = prev_point;
 928 
 929         input_set_point (in, input_screen_to_point (in, event->x));
 930         break;
 931 
 932     default:
 933         // don't create highlight region of 0 length
 934         if (in->mark == in->point)
 935             input_mark_cmd (in, FALSE);
 936         break;
 937     }
 938 }
 939 
 940 /* --------------------------------------------------------------------------------------------- */
 941 /*** public functions ****************************************************************************/
 942 /* --------------------------------------------------------------------------------------------- */
 943 
 944 /** Create new instance of WInput object.
 945  * @param y                    Y coordinate
 946  * @param x                    X coordinate
 947  * @param input_colors         Array of used colors
 948  * @param width                Widget width
 949  * @param def_text             Default text filled in widget
 950  * @param histname             Name of history
 951  * @param completion_flags     Flags for specify type of completions
 952  * @return                     WInput object
 953  */
 954 WInput *
 955 input_new (int y, int x, const int *colors, int width, const char *def_text, const char *histname,
     /* [previous][next][first][last][top][bottom][index][help]  */
 956            input_complete_t completion_flags)
 957 {
 958     WRect r = { y, x, 1, width };
 959     WInput *in;
 960     Widget *w;
 961 
 962     in = g_new (WInput, 1);
 963     w = WIDGET (in);
 964     widget_init (w, &r, input_callback, input_mouse_callback);
 965     w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR;
 966     w->keymap = input_map;
 967 
 968     in->color = colors;
 969     in->first = TRUE;
 970     in->mark = -1;
 971     in->term_first_shown = 0;
 972     in->disable_update = 0;
 973     in->is_password = FALSE;
 974     in->strip_password = FALSE;
 975 
 976     // in->buffer will be corrected in "history_load" event handler
 977     in->buffer = g_string_sized_new (width);
 978 
 979     // init completions before input_assign_text() call
 980     in->completions = NULL;
 981     in->completion_flags = completion_flags;
 982 
 983     in->init_from_history = (def_text == INPUT_LAST_TEXT);
 984 
 985     if (in->init_from_history || def_text == NULL)
 986         def_text = "";
 987 
 988     input_assign_text (in, def_text);
 989 
 990     // prepare to history setup
 991     in->history.list = NULL;
 992     in->history.current = NULL;
 993     in->history.changed = FALSE;
 994     in->history.name = NULL;
 995     if ((histname != NULL) && (*histname != '\0'))
 996         in->history.name = g_strdup (histname);
 997     // history will be loaded later
 998 
 999     in->label = NULL;
1000 
1001     return in;
1002 }
1003 
1004 /* --------------------------------------------------------------------------------------------- */
1005 
1006 cb_ret_t
1007 input_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1008 {
1009     WInput *in = INPUT (w);
1010     WDialog *h = DIALOG (w->owner);
1011     cb_ret_t v;
1012 
1013     switch (msg)
1014     {
1015     case MSG_INIT:
1016         // subscribe to "history_load" event
1017         mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
1018         // subscribe to "history_save" event
1019         mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
1020         if (in->label != NULL)
1021             widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
1022         return MSG_HANDLED;
1023 
1024     case MSG_KEY:
1025         if (parm == XCTRL ('q'))
1026         {
1027             quote = TRUE;
1028             v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1029             quote = FALSE;
1030             return v;
1031         }
1032 
1033         // Keys we want others to handle
1034         if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR || parm == KEY_F (10)
1035             || parm == '\n')
1036             return MSG_NOT_HANDLED;
1037 
1038         // When pasting multiline text, insert literal Enter
1039         if ((parm & ~KEY_M_MASK) == '\n')
1040         {
1041             quote = TRUE;
1042             v = input_handle_char (in, '\n');
1043             quote = FALSE;
1044             return v;
1045         }
1046 
1047         return input_handle_char (in, parm);
1048 
1049     case MSG_ACTION:
1050         return input_execute_cmd (in, parm);
1051 
1052     case MSG_DRAW:
1053         input_update (in, FALSE);
1054         return MSG_HANDLED;
1055 
1056     case MSG_ENABLE:
1057     case MSG_DISABLE:
1058         if (in->label != NULL)
1059             widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
1060         return MSG_HANDLED;
1061 
1062     case MSG_CURSOR:
1063         widget_gotoyx (in, 0, str_term_width2 (in->buffer->str, in->point) - in->term_first_shown);
1064         return MSG_HANDLED;
1065 
1066     case MSG_DESTROY:
1067         // unsubscribe from "history_load" event
1068         mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
1069         // unsubscribe from "history_save" event
1070         mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
1071         input_destroy (in);
1072         return MSG_HANDLED;
1073 
1074     default:
1075         return widget_default_callback (w, sender, msg, parm, data);
1076     }
1077 }
1078 
1079 /* --------------------------------------------------------------------------------------------- */
1080 
1081 void
1082 input_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1083 {
1084     input_colors[WINPUTC_MAIN] = INPUT_COLOR;
1085     input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
1086     input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
1087     input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
1088 }
1089 
1090 /* --------------------------------------------------------------------------------------------- */
1091 
1092 cb_ret_t
1093 input_handle_char (WInput *in, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1094 {
1095     cb_ret_t v;
1096     long command;
1097 
1098     if (quote)
1099     {
1100         input_complete_free (in);
1101         v = insert_char (in, key);
1102         input_update (in, TRUE);
1103         quote = FALSE;
1104         return v;
1105     }
1106 
1107     command = widget_lookup_key (WIDGET (in), key);
1108     if (command == CK_IgnoreKey)
1109     {
1110         if (key > 255)
1111             return MSG_NOT_HANDLED;
1112         if (in->first)
1113             port_region_marked_for_delete (in);
1114         input_complete_free (in);
1115         v = insert_char (in, key);
1116         input_update (in, TRUE);
1117     }
1118     else
1119     {
1120         gboolean keep_first;
1121 
1122         if (command != CK_Complete)
1123             input_complete_free (in);
1124         input_execute_cmd (in, command);
1125         v = MSG_HANDLED;
1126         /* if in->first == TRUE and history or completion window was cancelled,
1127            keep "first" state */
1128         keep_first = in->first && (command == CK_History || command == CK_Complete);
1129         input_update (in, !keep_first);
1130     }
1131 
1132     return v;
1133 }
1134 
1135 /* --------------------------------------------------------------------------------------------- */
1136 
1137 void
1138 input_assign_text (WInput *in, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
1139 {
1140     if (text == NULL)
1141         text = "";
1142 
1143     input_complete_free (in);
1144     in->mark = -1;
1145     in->need_push = TRUE;
1146     in->charpoint = 0;
1147     g_string_assign (in->buffer, text);
1148     in->point = str_length (in->buffer->str);
1149     input_update (in, TRUE);
1150 }
1151 
1152 /* --------------------------------------------------------------------------------------------- */
1153 
1154 /* Inserts text in input line */
1155 void
1156 input_insert (WInput *in, const char *text, gboolean insert_extra_space)
     /* [previous][next][first][last][top][bottom][index][help]  */
1157 {
1158     input_disable_update (in);
1159     while (*text != '\0')
1160         input_handle_char (in, (unsigned char) *text++);  // unsigned extension char->int
1161     if (insert_extra_space)
1162         input_handle_char (in, ' ');
1163     input_enable_update (in);
1164     input_update (in, TRUE);
1165 }
1166 
1167 /* --------------------------------------------------------------------------------------------- */
1168 
1169 void
1170 input_set_point (WInput *in, int pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
1171 {
1172     int max_pos;
1173 
1174     max_pos = str_length (in->buffer->str);
1175     pos = MIN (pos, max_pos);
1176     if (pos != in->point)
1177         input_complete_free (in);
1178     in->point = pos;
1179     in->charpoint = 0;
1180     input_update (in, TRUE);
1181 }
1182 
1183 /* --------------------------------------------------------------------------------------------- */
1184 
1185 void
1186 input_update (WInput *in, gboolean clear_first)
     /* [previous][next][first][last][top][bottom][index][help]  */
1187 {
1188     Widget *wi = WIDGET (in);
1189     const WRect *w = &wi->rect;
1190     int has_history = 0;
1191     int buf_len;
1192     const char *cp;
1193     int pw;
1194 
1195     if (in->disable_update != 0)
1196         return;
1197 
1198     // don't draw widget not put into dialog
1199     if (wi->owner == NULL || !widget_get_state (WIDGET (wi->owner), WST_ACTIVE))
1200         return;
1201 
1202     if (clear_first)
1203         in->first = FALSE;
1204 
1205     if (should_show_history_button (in))
1206         has_history = HISTORY_BUTTON_WIDTH;
1207 
1208     buf_len = str_length (in->buffer->str);
1209 
1210     // Adjust the mark
1211     in->mark = MIN (in->mark, buf_len);
1212 
1213     pw = str_term_width2 (in->buffer->str, in->point);
1214 
1215     // Make the point visible
1216     if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
1217     {
1218         in->term_first_shown = pw - (w->cols / 3);
1219         if (in->term_first_shown < 0)
1220             in->term_first_shown = 0;
1221     }
1222 
1223     if (has_history != 0)
1224         draw_history_button (in);
1225 
1226     if (widget_get_state (wi, WST_DISABLED))
1227         tty_setcolor (DISABLED_COLOR);
1228     else if (in->first)
1229         tty_setcolor (in->color[WINPUTC_UNCHANGED]);
1230     else
1231         tty_setcolor (in->color[WINPUTC_MAIN]);
1232 
1233     widget_gotoyx (in, 0, 0);
1234 
1235     if (!in->is_password)
1236     {
1237         if (in->mark < 0)
1238             tty_print_string (
1239                 str_term_substring (in->buffer->str, in->term_first_shown, w->cols - has_history));
1240         else
1241         {
1242             long m1, m2;
1243 
1244             if (input_eval_marks (in, &m1, &m2))
1245             {
1246                 tty_setcolor (in->color[WINPUTC_MAIN]);
1247                 cp = str_term_substring (in->buffer->str, in->term_first_shown,
1248                                          w->cols - has_history);
1249                 tty_print_string (cp);
1250                 tty_setcolor (in->color[WINPUTC_MARK]);
1251                 if (m1 < in->term_first_shown)
1252                 {
1253                     widget_gotoyx (in, 0, 0);
1254                     m1 = in->term_first_shown;
1255                     m2 -= m1;
1256                 }
1257                 else
1258                 {
1259                     int buf_width;
1260 
1261                     widget_gotoyx (in, 0, m1 - in->term_first_shown);
1262                     buf_width = str_term_width2 (in->buffer->str, m1);
1263                     m2 =
1264                         MIN (m2 - m1, (w->cols - has_history) - (buf_width - in->term_first_shown));
1265                 }
1266 
1267                 tty_print_string (str_term_substring (in->buffer->str, m1, m2));
1268             }
1269         }
1270     }
1271     else
1272     {
1273         int i;
1274 
1275         cp = str_term_substring (in->buffer->str, in->term_first_shown, w->cols - has_history);
1276         tty_setcolor (in->color[WINPUTC_MAIN]);
1277         for (i = 0; i < w->cols - has_history; i++)
1278         {
1279             if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
1280                 tty_print_char ('*');
1281             else
1282                 tty_print_char (' ');
1283             if (cp[0] != '\0')
1284                 str_cnext_char (&cp);
1285         }
1286     }
1287 }
1288 
1289 /* --------------------------------------------------------------------------------------------- */
1290 
1291 void
1292 input_enable_update (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1293 {
1294     in->disable_update--;
1295     input_update (in, FALSE);
1296 }
1297 
1298 /* --------------------------------------------------------------------------------------------- */
1299 
1300 void
1301 input_disable_update (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1302 {
1303     in->disable_update++;
1304 }
1305 
1306 /* --------------------------------------------------------------------------------------------- */
1307 
1308 /**
1309  *  Cleans the input line and adds the current text to the history
1310  *
1311  *  @param in the input line
1312  */
1313 void
1314 input_clean (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1315 {
1316     input_push_history (in);
1317     in->need_push = TRUE;
1318     g_string_set_size (in->buffer, 0);
1319     in->point = 0;
1320     in->charpoint = 0;
1321     in->mark = -1;
1322     input_complete_free (in);
1323     input_update (in, FALSE);
1324 }
1325 
1326 /* --------------------------------------------------------------------------------------------- */

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