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_get_text
  41. input_is_empty
  42. input_insert
  43. input_set_point
  44. input_update
  45. input_enable_update
  46. input_disable_update
  47. input_clean

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

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