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

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