root/src/filemanager/panel.c

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

DEFINITIONS

This source file includes following definitions.
  1. panelized_descr_new
  2. panelized_descr_free
  3. set_colors
  4. format_item_free
  5. panel_lines
  6. add_permission_string
  7. string_file_name
  8. ilog10
  9. format_device_number
  10. string_file_size
  11. string_file_size_brief
  12. string_file_type
  13. string_file_mtime
  14. string_file_atime
  15. string_file_ctime
  16. string_file_permission
  17. string_file_perm_octal
  18. string_file_nlinks
  19. string_inode
  20. string_file_nuid
  21. string_file_ngid
  22. string_file_owner
  23. string_file_group
  24. string_marked
  25. string_space
  26. string_dot
  27. file_compute_color
  28. panel_items
  29. format_file
  30. repaint_file
  31. repaint_status
  32. display_mini_info
  33. paint_dir
  34. display_total_marked_size
  35. mini_info_separator
  36. show_free_space
  37. panel_correct_path_to_show
  38. panel_get_encoding_info_str
  39. show_dir
  40. adjust_top_file
  41. panel_save_name
  42. directory_history_add
  43. panel_load_history
  44. panel_save_history
  45. panel_destroy
  46. panel_paint_sort_info
  47. panel_get_title_without_hotkey
  48. panel_print_header
  49. parse_panel_size
  50. parse_display_format
  51. use_display_format
  52. panel_format
  53. mini_status_format
  54. cd_up_dir
  55. maybe_cd
  56. force_maybe_cd
  57. unselect_item
  58. panel_select_ext_cmd
  59. panel_set_current
  60. panel_current_at_half
  61. move_down
  62. move_up
  63. panel_move_current
  64. move_left
  65. move_right
  66. prev_page
  67. goto_parent_dir
  68. next_page
  69. goto_child_dir
  70. goto_top_file
  71. goto_middle_file
  72. goto_bottom_file
  73. move_home
  74. move_end
  75. do_mark_file
  76. mark_file
  77. mark_file_up
  78. mark_file_down
  79. mark_file_right
  80. mark_file_left
  81. panel_select_unselect_files_dialog
  82. panel_select_unselect_files
  83. panel_select_files
  84. panel_unselect_files
  85. panel_select_invert_files
  86. panel_do_set_filter
  87. do_search
  88. start_search
  89. stop_search
  90. do_enter_on_file_entry
  91. do_enter
  92. panel_cycle_listing_format
  93. chdir_other_panel
  94. panel_sync_other
  95. chdir_to_readlink
  96. panel_get_format_field_index_by_name
  97. panel_get_sortable_field_by_format
  98. panel_toggle_sort_order_prev
  99. panel_toggle_sort_order_next
  100. panel_select_sort_order
  101. panel_content_scroll_left
  102. panel_content_scroll_right
  103. panel_set_sort_type_by_id
  104. get_parent_dir_name
  105. subshell_chdir
  106. panel_do_cd_int
  107. directory_history_next
  108. directory_history_prev
  109. directory_history_list
  110. panel_execute_cmd
  111. panel_key
  112. panel_callback
  113. mouse_toggle_mark
  114. mouse_set_mark
  115. mark_if_marking
  116. mouse_sort_col
  117. panel_mouse_is_on_item
  118. panel_mouse_callback
  119. reload_panelized
  120. update_one_panel_widget
  121. update_one_panel
  122. event_update_panels
  123. panel_save_current_file_to_clip_file
  124. panel_recursive_cd_to_parent
  125. panel_dir_list_callback
  126. panel_current_entry
  127. panel_set_current_by_name
  128. panel_clean_dir
  129. panel_set_cwd
  130. panel_set_lwd
  131. panel_sized_empty_new
  132. panel_sized_with_dir_new
  133. panel_reload
  134. set_panel_formats
  135. panel_set_filter
  136. select_item
  137. unmark_files
  138. recalculate_panel_summary
  139. do_file_mark
  140. panel_do_cd
  141. file_mark
  142. panel_find_marked_file
  143. panel_get_marked_file
  144. panel_re_sort
  145. panel_set_sort_order
  146. panel_change_encoding
  147. remove_encoding_from_path
  148. update_panels
  149. panel_get_num_of_sortable_fields
  150. panel_get_sortable_fields
  151. panel_get_field_by_id
  152. panel_get_field_by_title_hotkey
  153. panel_get_field_by_title
  154. panel_get_num_of_user_possible_fields
  155. panel_get_user_possible_fields
  156. panel_panelize_cd
  157. panel_panelize_change_root
  158. panel_panelize_absolutize_if_needed
  159. panel_panelize_save
  160. panel_init
  161. panel_deinit
  162. panel_cd

   1 /*
   2    Panel managing.
   3 
   4    Copyright (C) 1994-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Miguel de Icaza, 1995
   9    Timur Bakeyev, 1997, 1999
  10    Slava Zanko <slavazanko@gmail.com>, 2013
  11    Andrew Borodin <aborodin@vmail.ru>, 2013-2023
  12 
  13    This file is part of the Midnight Commander.
  14 
  15    The Midnight Commander is free software: you can redistribute it
  16    and/or modify it under the terms of the GNU General Public License as
  17    published by the Free Software Foundation, either version 3 of the License,
  18    or (at your option) any later version.
  19 
  20    The Midnight Commander is distributed in the hope that it will be useful,
  21    but WITHOUT ANY WARRANTY; without even the implied warranty of
  22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23    GNU General Public License for more details.
  24 
  25    You should have received a copy of the GNU General Public License
  26    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  27  */
  28 
  29 /** \file panel.c
  30  *  \brief Source: panel managin module
  31  */
  32 
  33 #include <config.h>
  34 
  35 #include <stdio.h>
  36 #include <stdlib.h>
  37 #include <string.h>
  38 
  39 #include "lib/global.h"
  40 
  41 #include "lib/tty/tty.h"
  42 #include "lib/tty/key.h"        /* XCTRL and ALT macros  */
  43 #include "lib/skin.h"
  44 #include "lib/strutil.h"
  45 #include "lib/mcconfig.h"
  46 #include "lib/vfs/vfs.h"
  47 #include "lib/unixcompat.h"
  48 #include "lib/search.h"
  49 #include "lib/timefmt.h"        /* file_date() */
  50 #include "lib/util.h"
  51 #include "lib/widget.h"
  52 #ifdef HAVE_CHARSET
  53 #include "lib/charsets.h"       /* get_codepage_id () */
  54 #endif
  55 #include "lib/event.h"
  56 
  57 #include "src/setup.h"          /* For loading/saving panel options */
  58 #include "src/execute.h"
  59 #ifdef HAVE_CHARSET
  60 #include "src/selcodepage.h"    /* select_charset (), SELECT_CHARSET_NO_TRANSLATE */
  61 #endif
  62 #include "src/keymap.h"         /* global_keymap_t */
  63 #include "src/history.h"
  64 #ifdef ENABLE_SUBSHELL
  65 #include "src/subshell/subshell.h"      /* do_subshell_chdir() */
  66 #endif
  67 
  68 #include "src/usermenu.h"
  69 
  70 #include "dir.h"
  71 #include "boxes.h"
  72 #include "tree.h"
  73 #include "ext.h"                /* regexp_command */
  74 #include "layout.h"             /* Most layout variables are here */
  75 #include "cmd.h"
  76 #include "command.h"            /* cmdline */
  77 #include "filemanager.h"
  78 #include "mountlist.h"          /* my_statfs */
  79 #include "cd.h"                 /* cd_error_message() */
  80 
  81 #include "panel.h"
  82 
  83 /*** global variables ****************************************************************************/
  84 
  85 /* The hook list for the select file function */
  86 hook_t *select_file_hook = NULL;
  87 
  88 mc_fhl_t *mc_filehighlight = NULL;
  89 
  90 /*** file scope macro definitions ****************************************************************/
  91 
  92 typedef enum
  93 {
  94     FATTR_NORMAL = 0,
  95     FATTR_CURRENT,
  96     FATTR_MARKED,
  97     FATTR_MARKED_CURRENT,
  98     FATTR_STATUS
  99 } file_attr_t;
 100 
 101 #define DEFAULT_USER_FORMAT "half type name | size | perm"
 102 
 103 /* select/unselect dialog results */
 104 #define SELECT_RESET ((mc_search_t *)(-1))
 105 #define SELECT_ERROR ((mc_search_t *)(-2))
 106 
 107 /* mouse position relative to file list */
 108 #define MOUSE_UPPER_FILE_LIST (-1)
 109 #define MOUSE_BELOW_FILE_LIST (-2)
 110 #define MOUSE_AFTER_LAST_FILE (-3)
 111 
 112 /*** file scope type declarations ****************************************************************/
 113 
 114 typedef enum
 115 {
 116     MARK_DONT_MOVE = 0,
 117     MARK_DOWN = 1,
 118     MARK_FORCE_DOWN = 2,
 119     MARK_FORCE_UP = 3
 120 } mark_act_t;
 121 
 122 /*
 123  * This describes a format item.  The parse_display_format routine parses
 124  * the user specified format and creates a linked list of format_item_t structures.
 125  */
 126 typedef struct format_item_t
 127 {
 128     int requested_field_len;
 129     int field_len;
 130     align_crt_t just_mode;
 131     gboolean expand;
 132     const char *(*string_fn) (const file_entry_t * fe, int len);
 133     char *title;
 134     const char *id;
 135 } format_item_t;
 136 
 137 /* File name scroll states */
 138 typedef enum
 139 {
 140     FILENAME_NOSCROLL = 1,
 141     FILENAME_SCROLL_LEFT = 2,
 142     FILENAME_SCROLL_RIGHT = 4
 143 } filename_scroll_flag_t;
 144 
 145 /*** forward declarations (file scope functions) *************************************************/
 146 
 147 static const char *string_file_name (const file_entry_t * fe, int len);
 148 static const char *string_file_size (const file_entry_t * fe, int len);
 149 static const char *string_file_size_brief (const file_entry_t * fe, int len);
 150 static const char *string_file_type (const file_entry_t * fe, int len);
 151 static const char *string_file_mtime (const file_entry_t * fe, int len);
 152 static const char *string_file_atime (const file_entry_t * fe, int len);
 153 static const char *string_file_ctime (const file_entry_t * fe, int len);
 154 static const char *string_file_permission (const file_entry_t * fe, int len);
 155 static const char *string_file_perm_octal (const file_entry_t * fe, int len);
 156 static const char *string_file_nlinks (const file_entry_t * fe, int len);
 157 static const char *string_inode (const file_entry_t * fe, int len);
 158 static const char *string_file_nuid (const file_entry_t * fe, int len);
 159 static const char *string_file_ngid (const file_entry_t * fe, int len);
 160 static const char *string_file_owner (const file_entry_t * fe, int len);
 161 static const char *string_file_group (const file_entry_t * fe, int len);
 162 static const char *string_marked (const file_entry_t * fe, int len);
 163 static const char *string_space (const file_entry_t * fe, int len);
 164 static const char *string_dot (const file_entry_t * fe, int len);
 165 
 166 /*** file scope variables ************************************************************************/
 167 
 168 /* *INDENT-OFF* */
 169 static panel_field_t panel_fields[] = {
 170     {
 171      "unsorted", 12, TRUE, J_LEFT_FIT,
 172      /* TRANSLATORS: one single character to represent 'unsorted' sort mode  */
 173      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 174      N_("sort|u"),
 175      N_("&Unsorted"), TRUE, FALSE,
 176      string_file_name,
 177      (GCompareFunc) unsorted
 178     }
 179     ,
 180     {
 181      "name", 12, TRUE, J_LEFT_FIT,
 182      /* TRANSLATORS: one single character to represent 'name' sort mode  */
 183      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 184      N_("sort|n"),
 185      N_("&Name"), TRUE, TRUE,
 186      string_file_name,
 187      (GCompareFunc) sort_name
 188     }
 189     ,
 190     {
 191      "version", 12, TRUE, J_LEFT_FIT,
 192      /* TRANSLATORS: one single character to represent 'version' sort mode  */
 193      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 194      N_("sort|v"),
 195      N_("&Version"), TRUE, FALSE,
 196      string_file_name,
 197      (GCompareFunc) sort_vers
 198     }
 199     ,
 200     {
 201      "extension", 12, TRUE, J_LEFT_FIT,
 202      /* TRANSLATORS: one single character to represent 'extension' sort mode  */
 203      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 204      N_("sort|e"),
 205      N_("E&xtension"), TRUE, FALSE,
 206      string_file_name,          /* TODO: string_file_ext */
 207      (GCompareFunc) sort_ext
 208     }
 209     ,
 210     {
 211      "size", 7, FALSE, J_RIGHT,
 212      /* TRANSLATORS: one single character to represent 'size' sort mode  */
 213      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 214      N_("sort|s"),
 215      N_("&Size"), TRUE, TRUE,
 216      string_file_size,
 217      (GCompareFunc) sort_size
 218     }
 219     ,
 220     {
 221      "bsize", 7, FALSE, J_RIGHT,
 222      "",
 223      N_("Block Size"), FALSE, FALSE,
 224      string_file_size_brief,
 225      (GCompareFunc) sort_size
 226     }
 227     ,
 228     {
 229      "type", 1, FALSE, J_LEFT,
 230      "",
 231      "", FALSE, TRUE,
 232      string_file_type,
 233      NULL
 234     }
 235     ,
 236     {
 237      "mtime", 12, FALSE, J_RIGHT,
 238      /* TRANSLATORS: one single character to represent 'Modify time' sort mode  */
 239      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 240      N_("sort|m"),
 241      N_("&Modify time"), TRUE, TRUE,
 242      string_file_mtime,
 243      (GCompareFunc) sort_time
 244     }
 245     ,
 246     {
 247      "atime", 12, FALSE, J_RIGHT,
 248      /* TRANSLATORS: one single character to represent 'Access time' sort mode  */
 249      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 250      N_("sort|a"),
 251      N_("&Access time"), TRUE, TRUE,
 252      string_file_atime,
 253      (GCompareFunc) sort_atime
 254     }
 255     ,
 256     {
 257      "ctime", 12, FALSE, J_RIGHT,
 258      /* TRANSLATORS: one single character to represent 'Change time' sort mode  */
 259      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 260      N_("sort|h"),
 261      N_("C&hange time"), TRUE, TRUE,
 262      string_file_ctime,
 263      (GCompareFunc) sort_ctime
 264     }
 265     ,
 266     {
 267      "perm", 10, FALSE, J_LEFT,
 268      "",
 269      N_("Permission"), FALSE, TRUE,
 270      string_file_permission,
 271      NULL
 272     }
 273     ,
 274     {
 275      "mode", 6, FALSE, J_RIGHT,
 276      "",
 277      N_("Perm"), FALSE, TRUE,
 278      string_file_perm_octal,
 279      NULL
 280     }
 281     ,
 282     {
 283      "nlink", 2, FALSE, J_RIGHT,
 284      "",
 285      N_("Nl"), FALSE, TRUE,
 286      string_file_nlinks, NULL
 287     }
 288     ,
 289     {
 290      "inode", 5, FALSE, J_RIGHT,
 291      /* TRANSLATORS: one single character to represent 'inode' sort mode  */
 292      /* TRANSLATORS: no need to translate 'sort', it's just a context prefix  */
 293      N_("sort|i"),
 294      N_("&Inode"), TRUE, TRUE,
 295      string_inode,
 296      (GCompareFunc) sort_inode
 297     }
 298     ,
 299     {
 300      "nuid", 5, FALSE, J_RIGHT,
 301      "",
 302      N_("UID"), FALSE, FALSE,
 303      string_file_nuid,
 304      NULL
 305     }
 306     ,
 307     {
 308      "ngid", 5, FALSE, J_RIGHT,
 309      "",
 310      N_("GID"), FALSE, FALSE,
 311      string_file_ngid,
 312      NULL
 313     }
 314     ,
 315     {
 316      "owner", 8, FALSE, J_LEFT_FIT,
 317      "",
 318      N_("Owner"), FALSE, TRUE,
 319      string_file_owner,
 320      NULL
 321     }
 322     ,
 323     {
 324      "group", 8, FALSE, J_LEFT_FIT,
 325      "",
 326      N_("Group"), FALSE, TRUE,
 327      string_file_group,
 328      NULL
 329     }
 330     ,
 331     {
 332      "mark", 1, FALSE, J_RIGHT,
 333      "",
 334      " ", FALSE, TRUE,
 335      string_marked,
 336      NULL
 337     }
 338     ,
 339     {
 340      "|", 1, FALSE, J_RIGHT,
 341      "",
 342      " ", FALSE, TRUE,
 343      NULL,
 344      NULL
 345     }
 346     ,
 347     {
 348      "space", 1, FALSE, J_RIGHT,
 349      "",
 350      " ", FALSE, TRUE,
 351      string_space,
 352      NULL
 353     }
 354     ,
 355     {
 356      "dot", 1, FALSE, J_RIGHT,
 357      "",
 358      " ", FALSE, FALSE,
 359      string_dot,
 360      NULL
 361     }
 362     ,
 363     {
 364      NULL, 0, FALSE, J_RIGHT, NULL, NULL, FALSE, FALSE, NULL, NULL
 365     }
 366 };
 367 /* *INDENT-ON* */
 368 
 369 static char *panel_sort_up_char = NULL;
 370 static char *panel_sort_down_char = NULL;
 371 
 372 static char *panel_hiddenfiles_show_char = NULL;
 373 static char *panel_hiddenfiles_hide_char = NULL;
 374 static char *panel_history_prev_item_char = NULL;
 375 static char *panel_history_next_item_char = NULL;
 376 static char *panel_history_show_list_char = NULL;
 377 static char *panel_filename_scroll_left_char = NULL;
 378 static char *panel_filename_scroll_right_char = NULL;
 379 
 380 /* Panel that selection started */
 381 static WPanel *mouse_mark_panel = NULL;
 382 
 383 static gboolean mouse_marking = FALSE;
 384 static int state_mark = 0;
 385 
 386 static GString *string_file_name_buffer;
 387 
 388 /* --------------------------------------------------------------------------------------------- */
 389 /*** file scope functions ************************************************************************/
 390 /* --------------------------------------------------------------------------------------------- */
 391 
 392 static panelized_descr_t *
 393 panelized_descr_new (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 394 {
 395     panelized_descr_t *p;
 396 
 397     p = g_new0 (panelized_descr_t, 1);
 398     p->list.len = -1;
 399 
 400     return p;
 401 }
 402 
 403 /* --------------------------------------------------------------------------------------------- */
 404 
 405 static void
 406 panelized_descr_free (panelized_descr_t *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 407 {
 408     if (p != NULL)
 409     {
 410         dir_list_free_list (&p->list);
 411         vfs_path_free (p->root_vpath, TRUE);
 412         g_free (p);
 413     }
 414 }
 415 
 416 /* --------------------------------------------------------------------------------------------- */
 417 
 418 static void
 419 set_colors (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 420 {
 421     (void) panel;
 422 
 423     tty_set_normal_attrs ();
 424     tty_setcolor (NORMAL_COLOR);
 425 }
 426 
 427 /* --------------------------------------------------------------------------------------------- */
 428 /** Delete format_item_t object */
 429 
 430 static void
 431 format_item_free (format_item_t *format)
     /* [previous][next][first][last][top][bottom][index][help]  */
 432 {
 433     g_free (format->title);
 434     g_free (format);
 435 }
 436 
 437 /* --------------------------------------------------------------------------------------------- */
 438 /** Extract the number of available lines in a panel */
 439 
 440 static int
 441 panel_lines (const WPanel *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 442 {
 443     /* 3 lines are: top frame, column header, button frame */
 444     return (CONST_WIDGET (p)->rect.lines - 3 - (panels_options.show_mini_info ? 2 : 0));
 445 }
 446 
 447 /* --------------------------------------------------------------------------------------------- */
 448 /** This code relies on the default justification!!! */
 449 
 450 static void
 451 add_permission_string (const char *dest, int width, file_entry_t *fe, file_attr_t attr, int color,
     /* [previous][next][first][last][top][bottom][index][help]  */
 452                        gboolean is_octal)
 453 {
 454     int i, r, l;
 455 
 456     l = get_user_permissions (&fe->st);
 457 
 458     if (is_octal)
 459     {
 460         /* Place of the access bit in octal mode */
 461         l = width + l - 3;
 462         r = l + 1;
 463     }
 464     else
 465     {
 466         /* The same to the triplet in string mode */
 467         l = l * 3 + 1;
 468         r = l + 3;
 469     }
 470 
 471     for (i = 0; i < width; i++)
 472     {
 473         if (i >= l && i < r)
 474         {
 475             if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
 476                 tty_setcolor (MARKED_SELECTED_COLOR);
 477             else
 478                 tty_setcolor (MARKED_COLOR);
 479         }
 480         else if (color >= 0)
 481             tty_setcolor (color);
 482         else
 483             tty_lowlevel_setcolor (-color);
 484 
 485         tty_print_char (dest[i]);
 486     }
 487 }
 488 
 489 /* --------------------------------------------------------------------------------------------- */
 490 /** String representations of various file attributes name */
 491 
 492 static const char *
 493 string_file_name (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 494 {
 495     (void) len;
 496 
 497     mc_g_string_copy (string_file_name_buffer, fe->fname);
 498 
 499     return string_file_name_buffer->str;
 500 }
 501 
 502 /* --------------------------------------------------------------------------------------------- */
 503 
 504 static unsigned int
 505 ilog10 (dev_t n)
     /* [previous][next][first][last][top][bottom][index][help]  */
 506 {
 507     unsigned int digits = 0;
 508 
 509     do
 510     {
 511         digits++;
 512         n /= 10;
 513     }
 514     while (n != 0);
 515 
 516     return digits;
 517 }
 518 
 519 /* --------------------------------------------------------------------------------------------- */
 520 
 521 static void
 522 format_device_number (char *buf, size_t bufsize, dev_t dev)
     /* [previous][next][first][last][top][bottom][index][help]  */
 523 {
 524     dev_t major_dev, minor_dev;
 525     unsigned int major_digits, minor_digits;
 526 
 527     major_dev = major (dev);
 528     major_digits = ilog10 (major_dev);
 529 
 530     minor_dev = minor (dev);
 531     minor_digits = ilog10 (minor_dev);
 532 
 533     g_assert (bufsize >= 1);
 534 
 535     if (major_digits + 1 + minor_digits + 1 <= bufsize)
 536         g_snprintf (buf, bufsize, "%lu,%lu", (unsigned long) major_dev, (unsigned long) minor_dev);
 537     else
 538         g_strlcpy (buf, _("[dev]"), bufsize);
 539 }
 540 
 541 /* --------------------------------------------------------------------------------------------- */
 542 /** size */
 543 
 544 static const char *
 545 string_file_size (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 546 {
 547     static char buffer[BUF_TINY];
 548 
 549     /* Don't ever show size of ".." since we don't calculate it */
 550     if (DIR_IS_DOTDOT (fe->fname->str))
 551         return _("UP--DIR");
 552 
 553 #ifdef HAVE_STRUCT_STAT_ST_RDEV
 554     if (S_ISBLK (fe->st.st_mode) || S_ISCHR (fe->st.st_mode))
 555         format_device_number (buffer, len + 1, fe->st.st_rdev);
 556     else
 557 #endif
 558         size_trunc_len (buffer, (unsigned int) len, fe->st.st_size, 0, panels_options.kilobyte_si);
 559 
 560     return buffer;
 561 }
 562 
 563 /* --------------------------------------------------------------------------------------------- */
 564 /** bsize */
 565 
 566 static const char *
 567 string_file_size_brief (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 568 {
 569     if (S_ISLNK (fe->st.st_mode) && !link_isdir (fe))
 570         return _("SYMLINK");
 571 
 572     if ((S_ISDIR (fe->st.st_mode) || link_isdir (fe)) && !DIR_IS_DOTDOT (fe->fname->str))
 573         return _("SUB-DIR");
 574 
 575     return string_file_size (fe, len);
 576 }
 577 
 578 /* --------------------------------------------------------------------------------------------- */
 579 /** This functions return a string representation of a file entry type */
 580 
 581 static const char *
 582 string_file_type (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 583 {
 584     static char buffer[2];
 585 
 586     (void) len;
 587 
 588     if (S_ISDIR (fe->st.st_mode))
 589         buffer[0] = PATH_SEP;
 590     else if (S_ISLNK (fe->st.st_mode))
 591     {
 592         if (link_isdir (fe))
 593             buffer[0] = '~';
 594         else if (fe->f.stale_link != 0)
 595             buffer[0] = '!';
 596         else
 597             buffer[0] = '@';
 598     }
 599     else if (S_ISCHR (fe->st.st_mode))
 600         buffer[0] = '-';
 601     else if (S_ISSOCK (fe->st.st_mode))
 602         buffer[0] = '=';
 603     else if (S_ISDOOR (fe->st.st_mode))
 604         buffer[0] = '>';
 605     else if (S_ISBLK (fe->st.st_mode))
 606         buffer[0] = '+';
 607     else if (S_ISFIFO (fe->st.st_mode))
 608         buffer[0] = '|';
 609     else if (S_ISNAM (fe->st.st_mode))
 610         buffer[0] = '#';
 611     else if (!S_ISREG (fe->st.st_mode))
 612         buffer[0] = '?';        /* non-regular of unknown kind */
 613     else if (is_exe (fe->st.st_mode))
 614         buffer[0] = '*';
 615     else
 616         buffer[0] = ' ';
 617     buffer[1] = '\0';
 618     return buffer;
 619 }
 620 
 621 /* --------------------------------------------------------------------------------------------- */
 622 /** mtime */
 623 
 624 static const char *
 625 string_file_mtime (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 626 {
 627     (void) len;
 628 
 629     return file_date (fe->st.st_mtime);
 630 }
 631 
 632 /* --------------------------------------------------------------------------------------------- */
 633 /** atime */
 634 
 635 static const char *
 636 string_file_atime (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 637 {
 638     (void) len;
 639 
 640     return file_date (fe->st.st_atime);
 641 }
 642 
 643 /* --------------------------------------------------------------------------------------------- */
 644 /** ctime */
 645 
 646 static const char *
 647 string_file_ctime (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 648 {
 649     (void) len;
 650 
 651     return file_date (fe->st.st_ctime);
 652 }
 653 
 654 /* --------------------------------------------------------------------------------------------- */
 655 /** perm */
 656 
 657 static const char *
 658 string_file_permission (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 659 {
 660     (void) len;
 661 
 662     return string_perm (fe->st.st_mode);
 663 }
 664 
 665 /* --------------------------------------------------------------------------------------------- */
 666 /** mode */
 667 
 668 static const char *
 669 string_file_perm_octal (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 670 {
 671     static char buffer[10];
 672 
 673     (void) len;
 674 
 675     g_snprintf (buffer, sizeof (buffer), "0%06lo", (unsigned long) fe->st.st_mode);
 676     return buffer;
 677 }
 678 
 679 /* --------------------------------------------------------------------------------------------- */
 680 /** nlink */
 681 
 682 static const char *
 683 string_file_nlinks (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 684 {
 685     static char buffer[BUF_TINY];
 686 
 687     (void) len;
 688 
 689     g_snprintf (buffer, sizeof (buffer), "%16d", (int) fe->st.st_nlink);
 690     return buffer;
 691 }
 692 
 693 /* --------------------------------------------------------------------------------------------- */
 694 /** inode */
 695 
 696 static const char *
 697 string_inode (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 698 {
 699     static char buffer[10];
 700 
 701     (void) len;
 702 
 703     g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_ino);
 704     return buffer;
 705 }
 706 
 707 /* --------------------------------------------------------------------------------------------- */
 708 /** nuid */
 709 
 710 static const char *
 711 string_file_nuid (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 712 {
 713     static char buffer[10];
 714 
 715     (void) len;
 716 
 717     g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_uid);
 718     return buffer;
 719 }
 720 
 721 /* --------------------------------------------------------------------------------------------- */
 722 /** ngid */
 723 
 724 static const char *
 725 string_file_ngid (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 726 {
 727     static char buffer[10];
 728 
 729     (void) len;
 730 
 731     g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_gid);
 732     return buffer;
 733 }
 734 
 735 /* --------------------------------------------------------------------------------------------- */
 736 /** owner */
 737 
 738 static const char *
 739 string_file_owner (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 740 {
 741     (void) len;
 742 
 743     return get_owner (fe->st.st_uid);
 744 }
 745 
 746 /* --------------------------------------------------------------------------------------------- */
 747 /** group */
 748 
 749 static const char *
 750 string_file_group (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 751 {
 752     (void) len;
 753 
 754     return get_group (fe->st.st_gid);
 755 }
 756 
 757 /* --------------------------------------------------------------------------------------------- */
 758 /** mark */
 759 
 760 static const char *
 761 string_marked (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 762 {
 763     (void) len;
 764 
 765     return fe->f.marked != 0 ? "*" : " ";
 766 }
 767 
 768 /* --------------------------------------------------------------------------------------------- */
 769 /** space */
 770 
 771 static const char *
 772 string_space (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 773 {
 774     (void) fe;
 775     (void) len;
 776 
 777     return " ";
 778 }
 779 
 780 /* --------------------------------------------------------------------------------------------- */
 781 /** dot */
 782 
 783 static const char *
 784 string_dot (const file_entry_t *fe, int len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 785 {
 786     (void) fe;
 787     (void) len;
 788 
 789     return ".";
 790 }
 791 
 792 /* --------------------------------------------------------------------------------------------- */
 793 
 794 static int
 795 file_compute_color (const file_attr_t attr, file_entry_t *fe)
     /* [previous][next][first][last][top][bottom][index][help]  */
 796 {
 797     switch (attr)
 798     {
 799     case FATTR_CURRENT:
 800         return (SELECTED_COLOR);
 801     case FATTR_MARKED:
 802         return (MARKED_COLOR);
 803     case FATTR_MARKED_CURRENT:
 804         return (MARKED_SELECTED_COLOR);
 805     case FATTR_STATUS:
 806         return (NORMAL_COLOR);
 807     case FATTR_NORMAL:
 808     default:
 809         if (!panels_options.filetype_mode)
 810             return (NORMAL_COLOR);
 811     }
 812 
 813     return mc_fhl_get_color (mc_filehighlight, fe);
 814 }
 815 
 816 /* --------------------------------------------------------------------------------------------- */
 817 /** Returns the number of items in the given panel */
 818 
 819 static int
 820 panel_items (const WPanel *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 821 {
 822     return panel_lines (p) * p->list_cols;
 823 }
 824 
 825 /* --------------------------------------------------------------------------------------------- */
 826 /** Formats the file number file_index of panel in the buffer dest */
 827 
 828 static filename_scroll_flag_t
 829 format_file (WPanel *panel, int file_index, int width, file_attr_t attr, gboolean isstatus,
     /* [previous][next][first][last][top][bottom][index][help]  */
 830              int *field_length)
 831 {
 832     int color = NORMAL_COLOR;
 833     int length = 0;
 834     GSList *format, *home;
 835     file_entry_t *fe = NULL;
 836     filename_scroll_flag_t res = FILENAME_NOSCROLL;
 837 
 838     *field_length = 0;
 839 
 840     if (panel->dir.len != 0 && file_index < panel->dir.len)
 841     {
 842         fe = &panel->dir.list[file_index];
 843         color = file_compute_color (attr, fe);
 844     }
 845 
 846     home = isstatus ? panel->status_format : panel->format;
 847 
 848     for (format = home; format != NULL && length != width; format = g_slist_next (format))
 849     {
 850         format_item_t *fi = (format_item_t *) format->data;
 851 
 852         if (fi->string_fn != NULL)
 853         {
 854             const char *txt = " ";
 855             int len, perm = 0;
 856             const char *prepared_text;
 857             int name_offset = 0;
 858 
 859             if (fe != NULL)
 860                 txt = fi->string_fn (fe, fi->field_len);
 861 
 862             len = fi->field_len;
 863             if (len + length > width)
 864                 len = width - length;
 865             if (len <= 0)
 866                 break;
 867 
 868             if (!isstatus && panel->content_shift > -1 && strcmp (fi->id, "name") == 0)
 869             {
 870                 int str_len;
 871                 int i;
 872 
 873                 *field_length = len + 1;
 874 
 875                 str_len = str_length (txt);
 876                 i = MAX (0, str_len - len);
 877                 panel->max_shift = MAX (panel->max_shift, i);
 878                 i = MIN (panel->content_shift, i);
 879 
 880                 if (i > -1)
 881                 {
 882                     name_offset = str_offset_to_pos (txt, i);
 883                     if (str_len > len)
 884                     {
 885                         res = FILENAME_SCROLL_LEFT;
 886                         if (str_length (txt + name_offset) > len)
 887                             res |= FILENAME_SCROLL_RIGHT;
 888                     }
 889                 }
 890             }
 891 
 892             if (panels_options.permission_mode)
 893             {
 894                 if (strcmp (fi->id, "perm") == 0)
 895                     perm = 1;
 896                 else if (strcmp (fi->id, "mode") == 0)
 897                     perm = 2;
 898             }
 899 
 900             if (color >= 0)
 901                 tty_setcolor (color);
 902             else
 903                 tty_lowlevel_setcolor (-color);
 904 
 905             if (!isstatus && panel->content_shift > -1)
 906                 prepared_text = str_fit_to_term (txt + name_offset, len, HIDE_FIT (fi->just_mode));
 907             else
 908                 prepared_text = str_fit_to_term (txt, len, fi->just_mode);
 909 
 910             if (perm != 0 && fe != NULL)
 911                 add_permission_string (prepared_text, fi->field_len, fe, attr, color, perm != 1);
 912             else
 913                 tty_print_string (prepared_text);
 914 
 915             length += len;
 916         }
 917         else
 918         {
 919             if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
 920                 tty_setcolor (SELECTED_COLOR);
 921             else
 922                 tty_setcolor (NORMAL_COLOR);
 923             tty_print_one_vline (TRUE);
 924             length++;
 925         }
 926     }
 927 
 928     if (length < width)
 929     {
 930         int y, x;
 931 
 932         tty_getyx (&y, &x);
 933         tty_draw_hline (y, x, ' ', width - length);
 934     }
 935 
 936     return res;
 937 }
 938 
 939 /* --------------------------------------------------------------------------------------------- */
 940 
 941 static void
 942 repaint_file (WPanel *panel, int file_index, file_attr_t attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 943 {
 944     Widget *w = WIDGET (panel);
 945 
 946     int nth_column = 0;
 947     int width;
 948     int offset = 0;
 949     filename_scroll_flag_t ret_frm;
 950     int ypos = 0;
 951     gboolean panel_is_split;
 952     int fln = 0;
 953 
 954     panel_is_split = panel->list_cols > 1;
 955     width = w->rect.cols - 2;
 956 
 957     if (panel_is_split)
 958     {
 959         nth_column = (file_index - panel->top) / panel_lines (panel);
 960         width /= panel->list_cols;
 961 
 962         offset = width * nth_column;
 963 
 964         if (nth_column + 1 >= panel->list_cols)
 965             width = w->rect.cols - offset - 2;
 966     }
 967 
 968     /* Nothing to paint */
 969     if (width <= 0)
 970         return;
 971 
 972     ypos = file_index - panel->top;
 973 
 974     if (panel_is_split)
 975         ypos %= panel_lines (panel);
 976 
 977     ypos += 2;                  /* top frame and header */
 978     widget_gotoyx (w, ypos, offset + 1);
 979 
 980     ret_frm = format_file (panel, file_index, width, attr, FALSE, &fln);
 981 
 982     if (panel_is_split && nth_column + 1 < panel->list_cols)
 983     {
 984         tty_setcolor (NORMAL_COLOR);
 985         tty_print_one_vline (TRUE);
 986     }
 987 
 988     if (ret_frm != FILENAME_NOSCROLL)
 989     {
 990         if (!panel_is_split && fln > 0)
 991         {
 992             if (panel->list_format != list_long)
 993                 width = fln;
 994             else
 995             {
 996                 offset = width - fln + 1;
 997                 width = fln - 1;
 998             }
 999         }
1000 
1001         widget_gotoyx (w, ypos, offset);
1002         tty_setcolor (NORMAL_COLOR);
1003         tty_print_string (panel_filename_scroll_left_char);
1004 
1005         if ((ret_frm & FILENAME_SCROLL_RIGHT) != 0)
1006         {
1007             offset += width;
1008             if (nth_column + 1 >= panel->list_cols)
1009                 offset++;
1010 
1011             widget_gotoyx (w, ypos, offset);
1012             tty_setcolor (NORMAL_COLOR);
1013             tty_print_string (panel_filename_scroll_right_char);
1014         }
1015     }
1016 }
1017 
1018 /* --------------------------------------------------------------------------------------------- */
1019 
1020 static void
1021 repaint_status (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1022 {
1023     int width;
1024 
1025     width = WIDGET (panel)->rect.cols - 2;
1026     if (width > 0)
1027     {
1028         int fln = 0;
1029 
1030         (void) format_file (panel, panel->current, width, FATTR_STATUS, TRUE, &fln);
1031     }
1032 }
1033 
1034 /* --------------------------------------------------------------------------------------------- */
1035 
1036 static void
1037 display_mini_info (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1038 {
1039     Widget *w = WIDGET (panel);
1040     const file_entry_t *fe;
1041 
1042     if (!panels_options.show_mini_info)
1043         return;
1044 
1045     widget_gotoyx (w, panel_lines (panel) + 3, 1);
1046 
1047     if (panel->quick_search.active)
1048     {
1049         tty_setcolor (INPUT_COLOR);
1050         tty_print_char ('/');
1051         tty_print_string (str_fit_to_term
1052                           (panel->quick_search.buffer->str, w->rect.cols - 3, J_LEFT));
1053         return;
1054     }
1055 
1056     /* Status resolves links and show them */
1057     set_colors (panel);
1058 
1059     fe = panel_current_entry (panel);
1060 
1061     if (fe == NULL)
1062         /* NULL is in case of filter that doesn't match anything */
1063         repaint_status (panel);
1064     else if (S_ISLNK (fe->st.st_mode))
1065     {
1066         char link_target[MC_MAXPATHLEN];
1067         vfs_path_t *lc_link_vpath;
1068         int len;
1069 
1070         lc_link_vpath = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
1071         len = mc_readlink (lc_link_vpath, link_target, MC_MAXPATHLEN - 1);
1072         vfs_path_free (lc_link_vpath, TRUE);
1073         if (len > 0)
1074         {
1075             link_target[len] = 0;
1076             tty_print_string ("-> ");
1077             tty_print_string (str_fit_to_term (link_target, w->rect.cols - 5, J_LEFT_FIT));
1078         }
1079         else
1080             tty_print_string (str_fit_to_term (_("<readlink failed>"), w->rect.cols - 2, J_LEFT));
1081     }
1082     else if (DIR_IS_DOTDOT (fe->fname->str))
1083     {
1084         /* FIXME:
1085          * while loading directory (dir_list_load() and dir_list_reload()),
1086          * the actual stat info about ".." directory isn't got;
1087          * so just don't display incorrect info about ".." directory */
1088         tty_print_string (str_fit_to_term (_("UP--DIR"), w->rect.cols - 2, J_LEFT));
1089     }
1090     else
1091         /* Default behavior */
1092         repaint_status (panel);
1093 }
1094 
1095 /* --------------------------------------------------------------------------------------------- */
1096 
1097 static void
1098 paint_dir (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1099 {
1100     int i;
1101     int items;                  /* Number of items */
1102 
1103     items = panel_items (panel);
1104     /* reset max len of filename because we have the new max length for the new file list */
1105     panel->max_shift = -1;
1106 
1107     for (i = 0; i < items; i++)
1108     {
1109         file_attr_t attr = FATTR_NORMAL;        /* Color value of the line */
1110         int n;
1111         gboolean marked;
1112 
1113         n = i + panel->top;
1114         marked = (panel->dir.list[n].f.marked != 0);
1115 
1116         if (n < panel->dir.len)
1117         {
1118             if (panel->current == n && panel->active)
1119                 attr = marked ? FATTR_MARKED_CURRENT : FATTR_CURRENT;
1120             else if (marked)
1121                 attr = FATTR_MARKED;
1122         }
1123 
1124         repaint_file (panel, n, attr);
1125     }
1126 
1127     tty_set_normal_attrs ();
1128 }
1129 
1130 /* --------------------------------------------------------------------------------------------- */
1131 
1132 static void
1133 display_total_marked_size (const WPanel *panel, int y, int x, gboolean size_only)
     /* [previous][next][first][last][top][bottom][index][help]  */
1134 {
1135     const Widget *w = CONST_WIDGET (panel);
1136 
1137     char buffer[BUF_SMALL], b_bytes[BUF_SMALL];
1138     const char *buf;
1139     int cols;
1140 
1141     if (panel->marked <= 0)
1142         return;
1143 
1144     buf = size_only ? b_bytes : buffer;
1145     cols = w->rect.cols - 2;
1146 
1147     g_strlcpy (b_bytes, size_trunc_sep (panel->total, panels_options.kilobyte_si),
1148                sizeof (b_bytes));
1149 
1150     if (!size_only)
1151         g_snprintf (buffer, sizeof (buffer),
1152                     ngettext ("%s in %d file", "%s in %d files", panel->marked),
1153                     b_bytes, panel->marked);
1154 
1155     /* don't forget spaces around buffer content */
1156     buf = str_trunc (buf, cols - 4);
1157 
1158     if (x < 0)
1159         /* center in panel */
1160         x = (w->rect.cols - str_term_width1 (buf)) / 2 - 1;
1161 
1162     /*
1163      * y == panel_lines (panel) + 2  for mini_info_separator
1164      * y == w->lines - 1             for panel bottom frame
1165      */
1166     widget_gotoyx (w, y, x);
1167     tty_setcolor (MARKED_COLOR);
1168     tty_printf (" %s ", buf);
1169 }
1170 
1171 /* --------------------------------------------------------------------------------------------- */
1172 
1173 static void
1174 mini_info_separator (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1175 {
1176     if (panels_options.show_mini_info)
1177     {
1178         const Widget *w = CONST_WIDGET (panel);
1179         int y;
1180 
1181         y = panel_lines (panel) + 2;
1182 
1183         tty_setcolor (NORMAL_COLOR);
1184         tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
1185         /* Status displays total marked size.
1186          * Centered in panel, full format. */
1187         display_total_marked_size (panel, y, -1, FALSE);
1188     }
1189 }
1190 
1191 /* --------------------------------------------------------------------------------------------- */
1192 
1193 static void
1194 show_free_space (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1195 {
1196     /* Used to figure out how many free space we have */
1197     static struct my_statfs myfs_stats;
1198     /* Old current working directory for displaying free space */
1199     static char *old_cwd = NULL;
1200 
1201     /* Don't try to stat non-local fs */
1202     if (!vfs_file_is_local (panel->cwd_vpath) || !free_space)
1203         return;
1204 
1205     if (old_cwd == NULL || strcmp (old_cwd, vfs_path_as_str (panel->cwd_vpath)) != 0)
1206     {
1207         char rpath[PATH_MAX];
1208 
1209         init_my_statfs ();
1210         g_free (old_cwd);
1211         old_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
1212 
1213         if (mc_realpath (old_cwd, rpath) == NULL)
1214             return;
1215 
1216         my_statfs (&myfs_stats, rpath);
1217     }
1218 
1219     if (myfs_stats.avail != 0 || myfs_stats.total != 0)
1220     {
1221         const Widget *w = CONST_WIDGET (panel);
1222         char buffer1[6], buffer2[6], tmp[BUF_SMALL];
1223 
1224         size_trunc_len (buffer1, sizeof (buffer1) - 1, myfs_stats.avail, 1,
1225                         panels_options.kilobyte_si);
1226         size_trunc_len (buffer2, sizeof (buffer2) - 1, myfs_stats.total, 1,
1227                         panels_options.kilobyte_si);
1228         g_snprintf (tmp, sizeof (tmp), " %s / %s (%d%%) ", buffer1, buffer2,
1229                     myfs_stats.total == 0 ? 0 :
1230                     (int) (100 * (long double) myfs_stats.avail / myfs_stats.total));
1231         widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 2 - (int) strlen (tmp));
1232         tty_setcolor (NORMAL_COLOR);
1233         tty_print_string (tmp);
1234     }
1235 }
1236 
1237 /* --------------------------------------------------------------------------------------------- */
1238 /**
1239  * Prepare path string for showing in panel's header.
1240  * Passwords will removed, also home dir will replaced by ~
1241  *
1242  * @param panel WPanel object
1243  *
1244  * @return newly allocated string.
1245  */
1246 
1247 static char *
1248 panel_correct_path_to_show (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1249 {
1250     vfs_path_t *last_vpath;
1251     const vfs_path_element_t *path_element;
1252     char *return_path;
1253     int elements_count;
1254 
1255     elements_count = vfs_path_elements_count (panel->cwd_vpath);
1256 
1257     /* get last path element */
1258     path_element = vfs_path_element_clone (vfs_path_get_by_index (panel->cwd_vpath, -1));
1259 
1260     if (elements_count > 1 && (strcmp (path_element->class->name, "cpiofs") == 0 ||
1261                                strcmp (path_element->class->name, "extfs") == 0 ||
1262                                strcmp (path_element->class->name, "tarfs") == 0))
1263     {
1264         const char *archive_name;
1265         const vfs_path_element_t *prev_path_element;
1266 
1267         /* get previous path element for catching archive name */
1268         prev_path_element = vfs_path_get_by_index (panel->cwd_vpath, -2);
1269         archive_name = strrchr (prev_path_element->path, PATH_SEP);
1270         if (archive_name != NULL)
1271             last_vpath = vfs_path_from_str_flags (archive_name + 1, VPF_NO_CANON);
1272         else
1273         {
1274             last_vpath = vfs_path_from_str_flags (prev_path_element->path, VPF_NO_CANON);
1275             last_vpath->relative = TRUE;
1276         }
1277     }
1278     else
1279         last_vpath = vfs_path_new (TRUE);
1280 
1281     vfs_path_add_element (last_vpath, path_element);
1282     return_path =
1283         vfs_path_to_str_flags (last_vpath, 0,
1284                                VPF_STRIP_HOME | VPF_STRIP_PASSWORD | VPF_HIDE_CHARSET);
1285     vfs_path_free (last_vpath, TRUE);
1286 
1287     return return_path;
1288 }
1289 
1290 /* --------------------------------------------------------------------------------------------- */
1291 /**
1292  * Get Current path element encoding
1293  *
1294  * @param panel WPanel object
1295  *
1296  * @return newly allocated string or NULL if path charset is same as system charset
1297  */
1298 
1299 #ifdef HAVE_CHARSET
1300 static char *
1301 panel_get_encoding_info_str (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1302 {
1303     char *ret_str = NULL;
1304     const vfs_path_element_t *path_element;
1305 
1306     path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
1307     if (path_element->encoding != NULL)
1308         ret_str = g_strdup_printf ("[%s]", path_element->encoding);
1309 
1310     return ret_str;
1311 }
1312 #endif
1313 
1314 /* --------------------------------------------------------------------------------------------- */
1315 
1316 static void
1317 show_dir (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1318 {
1319     const Widget *w = CONST_WIDGET (panel);
1320     gchar *tmp;
1321 
1322     set_colors (panel);
1323     tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
1324 
1325     if (panels_options.show_mini_info)
1326     {
1327         int y;
1328 
1329         y = panel_lines (panel) + 2;
1330 
1331         widget_gotoyx (w, y, 0);
1332         tty_print_alt_char (ACS_LTEE, FALSE);
1333         widget_gotoyx (w, y, w->rect.cols - 1);
1334         tty_print_alt_char (ACS_RTEE, FALSE);
1335     }
1336 
1337     widget_gotoyx (w, 0, 1);
1338     tty_print_string (panel_history_prev_item_char);
1339 
1340     tmp = panels_options.show_dot_files ? panel_hiddenfiles_show_char : panel_hiddenfiles_hide_char;
1341     tmp = g_strdup_printf ("%s[%s]%s", tmp, panel_history_show_list_char,
1342                            panel_history_next_item_char);
1343 
1344     widget_gotoyx (w, 0, w->rect.cols - 6);
1345     tty_print_string (tmp);
1346 
1347     g_free (tmp);
1348 
1349     widget_gotoyx (w, 0, 3);
1350 
1351     if (panel->is_panelized)
1352         tty_printf (" %s ", _("Panelize"));
1353 #ifdef HAVE_CHARSET
1354     else
1355     {
1356         tmp = panel_get_encoding_info_str (panel);
1357         if (tmp != NULL)
1358         {
1359             tty_printf ("%s", tmp);
1360             widget_gotoyx (w, 0, 3 + strlen (tmp));
1361             g_free (tmp);
1362         }
1363     }
1364 #endif
1365 
1366     if (panel->active)
1367         tty_setcolor (REVERSE_COLOR);
1368 
1369     tmp = panel_correct_path_to_show (panel);
1370     tty_printf (" %s ", str_term_trim (tmp, MIN (MAX (w->rect.cols - 12, 0), w->rect.cols)));
1371     g_free (tmp);
1372 
1373     if (!panels_options.show_mini_info)
1374     {
1375         if (panel->marked == 0)
1376         {
1377             const file_entry_t *fe;
1378 
1379             fe = panel_current_entry (panel);
1380 
1381             /* Show size of current file in the bottom of panel */
1382             if (fe != NULL && S_ISREG (fe->st.st_mode))
1383             {
1384                 char buffer[BUF_SMALL];
1385 
1386                 g_snprintf (buffer, sizeof (buffer), " %s ",
1387                             size_trunc_sep (fe->st.st_size, panels_options.kilobyte_si));
1388                 tty_setcolor (NORMAL_COLOR);
1389                 widget_gotoyx (w, w->rect.lines - 1, 4);
1390                 tty_print_string (buffer);
1391             }
1392         }
1393         else
1394         {
1395             /* Show total size of marked files
1396              * In the bottom of panel, display size only. */
1397             display_total_marked_size (panel, w->rect.lines - 1, 2, TRUE);
1398         }
1399     }
1400 
1401     show_free_space (panel);
1402 
1403     if (panel->active)
1404         tty_set_normal_attrs ();
1405 }
1406 
1407 /* --------------------------------------------------------------------------------------------- */
1408 
1409 static void
1410 adjust_top_file (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1411 {
1412     int items;
1413 
1414     items = panel_items (panel);
1415 
1416     if (panel->dir.len <= items || panel->current < 0)
1417     {
1418         /* If all files fit, show them all. */
1419         /* panel->current < 0 is in case of filter that doesn't match anything, keep it. */
1420         panel->top = 0;
1421     }
1422     else
1423     {
1424         int i;
1425 
1426         /* Update panel->current to avoid out of range in panel->dir.list[panel->current]. */
1427         panel->current = CLAMP (panel->current, 0, panel->dir.len - 1);
1428 
1429         /* top_file has to be in the range [current-items+1, current] so that
1430            the current file is visible.
1431            top_file should be in the range [0, count-items] so that there's
1432            no empty space wasted.
1433            Within these ranges, adjust it by as little as possible. */
1434 
1435         if (panel->top < 0)
1436             panel->top = 0;
1437 
1438         i = panel->current - items + 1;
1439         if (panel->top < i)
1440             panel->top = i;
1441 
1442         i = panel->dir.len - items;
1443         if (panel->top > i)
1444             panel->top = i;
1445 
1446         if (panel->top > panel->current)
1447             panel->top = panel->current;
1448     }
1449 }
1450 
1451 /* --------------------------------------------------------------------------------------------- */
1452 /** add "#enc:encoding" to end of path */
1453 /* if path ends width a previous #enc:, only encoding is changed no additional
1454  * #enc: is appended
1455  * return new string
1456  */
1457 
1458 static char *
1459 panel_save_name (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1460 {
1461     /* If the program is shutting down */
1462     if ((mc_global.midnight_shutdown && auto_save_setup) || saving_setup)
1463         return g_strdup (panel->name);
1464 
1465     return g_strconcat ("Temporal:", panel->name, (char *) NULL);
1466 }
1467 
1468 /* --------------------------------------------------------------------------------------------- */
1469 
1470 static void
1471 directory_history_add (WPanel *panel, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1472 {
1473     char *tmp;
1474 
1475     tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1476     panel->dir_history.list = list_append_unique (panel->dir_history.list, tmp);
1477     panel->dir_history.current = panel->dir_history.list;
1478 }
1479 
1480 /* --------------------------------------------------------------------------------------------- */
1481 
1482 /* "history_load" event handler */
1483 static gboolean
1484 panel_load_history (const gchar *event_group_name, const gchar *event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
1485                     gpointer init_data, gpointer data)
1486 {
1487     WPanel *p = PANEL (init_data);
1488     ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
1489 
1490     (void) event_group_name;
1491     (void) event_name;
1492 
1493     if (ev->receiver == NULL || ev->receiver == WIDGET (p))
1494     {
1495         if (ev->cfg != NULL)
1496             p->dir_history.list = mc_config_history_load (ev->cfg, p->dir_history.name);
1497         else
1498             p->dir_history.list = mc_config_history_get (p->dir_history.name);
1499 
1500         directory_history_add (p, p->cwd_vpath);
1501     }
1502 
1503     return TRUE;
1504 }
1505 
1506 /* --------------------------------------------------------------------------------------------- */
1507 
1508 /* "history_save" event handler */
1509 static gboolean
1510 panel_save_history (const gchar *event_group_name, const gchar *event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
1511                     gpointer init_data, gpointer data)
1512 {
1513     WPanel *p = PANEL (init_data);
1514 
1515     (void) event_group_name;
1516     (void) event_name;
1517 
1518     if (p->dir_history.list != NULL)
1519     {
1520         ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
1521 
1522         mc_config_history_save (ev->cfg, p->dir_history.name, p->dir_history.list);
1523     }
1524 
1525     return TRUE;
1526 }
1527 
1528 /* --------------------------------------------------------------------------------------------- */
1529 
1530 static void
1531 panel_destroy (WPanel *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
1532 {
1533     size_t i;
1534 
1535     if (panels_options.auto_save_setup)
1536     {
1537         char *name;
1538 
1539         name = panel_save_name (p);
1540         panel_save_setup (p, name);
1541         g_free (name);
1542     }
1543 
1544     panel_clean_dir (p);
1545 
1546     /* clean history */
1547     if (p->dir_history.list != NULL)
1548     {
1549         /* directory history is already saved before this moment */
1550         p->dir_history.list = g_list_first (p->dir_history.list);
1551         g_list_free_full (p->dir_history.list, g_free);
1552     }
1553     g_free (p->dir_history.name);
1554 
1555     file_filter_clear (&p->filter);
1556 
1557     g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
1558     g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
1559 
1560     g_free (p->user_format);
1561     for (i = 0; i < LIST_FORMATS; i++)
1562         g_free (p->user_status_format[i]);
1563 
1564     g_free (p->name);
1565 
1566     panelized_descr_free (p->panelized_descr);
1567 
1568     g_string_free (p->quick_search.buffer, TRUE);
1569     g_string_free (p->quick_search.prev_buffer, TRUE);
1570 
1571     vfs_path_free (p->lwd_vpath, TRUE);
1572     vfs_path_free (p->cwd_vpath, TRUE);
1573 }
1574 
1575 /* --------------------------------------------------------------------------------------------- */
1576 
1577 static void
1578 panel_paint_sort_info (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1579 {
1580     if (*panel->sort_field->hotkey != '\0')
1581     {
1582         const char *sort_sign =
1583             panel->sort_info.reverse ? panel_sort_up_char : panel_sort_down_char;
1584         char *str;
1585 
1586         str = g_strdup_printf ("%s%s", sort_sign, Q_ (panel->sort_field->hotkey));
1587         widget_gotoyx (panel, 1, 1);
1588         tty_print_string (str);
1589         g_free (str);
1590     }
1591 }
1592 
1593 /* --------------------------------------------------------------------------------------------- */
1594 
1595 static const char *
1596 panel_get_title_without_hotkey (const char *title)
     /* [previous][next][first][last][top][bottom][index][help]  */
1597 {
1598     static char translated_title[BUF_TINY];
1599 
1600     if (title == NULL || title[0] == '\0')
1601         translated_title[0] = '\0';
1602     else
1603     {
1604         char *hkey;
1605 
1606         g_snprintf (translated_title, sizeof (translated_title), "%s", _(title));
1607 
1608         hkey = strchr (translated_title, '&');
1609         if (hkey != NULL && hkey[1] != '\0')
1610             memmove (hkey, hkey + 1, strlen (hkey));
1611     }
1612 
1613     return translated_title;
1614 }
1615 
1616 /* --------------------------------------------------------------------------------------------- */
1617 
1618 static void
1619 panel_print_header (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1620 {
1621     const Widget *w = CONST_WIDGET (panel);
1622 
1623     int y, x;
1624     int i;
1625     GString *format_txt;
1626 
1627     widget_gotoyx (w, 1, 1);
1628     tty_getyx (&y, &x);
1629     tty_setcolor (NORMAL_COLOR);
1630     tty_draw_hline (y, x, ' ', w->rect.cols - 2);
1631 
1632     format_txt = g_string_new ("");
1633 
1634     for (i = 0; i < panel->list_cols; i++)
1635     {
1636         GSList *format;
1637 
1638         for (format = panel->format; format != NULL; format = g_slist_next (format))
1639         {
1640             format_item_t *fi = (format_item_t *) format->data;
1641 
1642             if (fi->string_fn != NULL)
1643             {
1644                 g_string_set_size (format_txt, 0);
1645 
1646                 if (panel->list_format == list_long && strcmp (fi->id, panel->sort_field->id) == 0)
1647                     g_string_append (format_txt,
1648                                      panel->sort_info.reverse
1649                                      ? panel_sort_up_char : panel_sort_down_char);
1650 
1651                 g_string_append (format_txt, fi->title);
1652 
1653                 if (panel->filter.handler != NULL && strcmp (fi->id, "name") == 0)
1654                 {
1655                     g_string_append (format_txt, " [");
1656                     g_string_append (format_txt, panel->filter.value);
1657                     g_string_append (format_txt, "]");
1658                 }
1659 
1660                 tty_setcolor (HEADER_COLOR);
1661                 tty_print_string (str_fit_to_term (format_txt->str, fi->field_len, J_CENTER_LEFT));
1662             }
1663             else
1664             {
1665                 tty_setcolor (NORMAL_COLOR);
1666                 tty_print_one_vline (TRUE);
1667             }
1668         }
1669 
1670         if (i < panel->list_cols - 1)
1671         {
1672             tty_setcolor (NORMAL_COLOR);
1673             tty_print_one_vline (TRUE);
1674         }
1675     }
1676 
1677     g_string_free (format_txt, TRUE);
1678 
1679     if (panel->list_format != list_long)
1680         panel_paint_sort_info (panel);
1681 }
1682 
1683 /* --------------------------------------------------------------------------------------------- */
1684 
1685 static const char *
1686 parse_panel_size (WPanel *panel, const char *format, gboolean isstatus)
     /* [previous][next][first][last][top][bottom][index][help]  */
1687 {
1688     panel_display_t frame = frame_half;
1689 
1690     format = skip_separators (format);
1691 
1692     if (strncmp (format, "full", 4) == 0)
1693     {
1694         frame = frame_full;
1695         format += 4;
1696     }
1697     else if (strncmp (format, "half", 4) == 0)
1698     {
1699         frame = frame_half;
1700         format += 4;
1701     }
1702 
1703     if (!isstatus)
1704     {
1705         panel->frame_size = frame;
1706         panel->list_cols = 1;
1707     }
1708 
1709     /* Now, the optional column specifier */
1710     format = skip_separators (format);
1711 
1712     if (g_ascii_isdigit (*format))
1713     {
1714         if (!isstatus)
1715         {
1716             panel->list_cols = g_ascii_digit_value (*format);
1717             if (panel->list_cols < 1)
1718                 panel->list_cols = 1;
1719         }
1720 
1721         format++;
1722     }
1723 
1724     if (!isstatus)
1725         panel_update_cols (WIDGET (panel), panel->frame_size);
1726 
1727     return skip_separators (format);
1728 }
1729 
1730 /* --------------------------------------------------------------------------------------------- */
1731 
1732 /* *INDENT-OFF* */
1733 /* Format is:
1734 
1735    all              := panel_format? format
1736    panel_format     := [full|half] [1-9]
1737    format           := one_format_item_t
1738                      | format , one_format_item_t
1739 
1740    one_format_item_t := just format.id [opt_size]
1741    just              := [<=>]
1742    opt_size          := : size [opt_expand]
1743    size              := [0-9]+
1744    opt_expand        := +
1745 
1746  */
1747 /* *INDENT-ON* */
1748 
1749 static GSList *
1750 parse_display_format (WPanel *panel, const char *format, char **error, gboolean isstatus,
     /* [previous][next][first][last][top][bottom][index][help]  */
1751                       int *res_total_cols)
1752 {
1753     GSList *home = NULL;        /* The formats we return */
1754     int total_cols = 0;         /* Used columns by the format */
1755     size_t i;
1756 
1757     static size_t i18n_timelength = 0;  /* flag: check ?Time length at startup */
1758 
1759     *error = NULL;
1760 
1761     if (i18n_timelength == 0)
1762     {
1763         i18n_timelength = i18n_checktimelength ();      /* Mustn't be 0 */
1764 
1765         for (i = 0; panel_fields[i].id != NULL; i++)
1766             if (strcmp ("time", panel_fields[i].id + 1) == 0)
1767                 panel_fields[i].min_size = i18n_timelength;
1768     }
1769 
1770     /*
1771      * This makes sure that the panel and mini status full/half mode
1772      * setting is equal
1773      */
1774     format = parse_panel_size (panel, format, isstatus);
1775 
1776     while (*format != '\0')
1777     {                           /* format can be an empty string */
1778         format_item_t *darr;
1779         align_crt_t justify;    /* Which mode. */
1780         gboolean set_justify = TRUE;    /* flag: set justification mode? */
1781         gboolean found = FALSE;
1782         size_t klen = 0;
1783 
1784         darr = g_new0 (format_item_t, 1);
1785         home = g_slist_append (home, darr);
1786 
1787         format = skip_separators (format);
1788 
1789         switch (*format)
1790         {
1791         case '<':
1792             justify = J_LEFT;
1793             format = skip_separators (format + 1);
1794             break;
1795         case '=':
1796             justify = J_CENTER;
1797             format = skip_separators (format + 1);
1798             break;
1799         case '>':
1800             justify = J_RIGHT;
1801             format = skip_separators (format + 1);
1802             break;
1803         default:
1804             justify = J_LEFT;
1805             set_justify = FALSE;
1806             break;
1807         }
1808 
1809         for (i = 0; !found && panel_fields[i].id != NULL; i++)
1810         {
1811             klen = strlen (panel_fields[i].id);
1812             found = strncmp (format, panel_fields[i].id, klen) == 0;
1813         }
1814 
1815         if (found)
1816         {
1817             i--;
1818             format += klen;
1819 
1820             darr->requested_field_len = panel_fields[i].min_size;
1821             darr->string_fn = panel_fields[i].string_fn;
1822             darr->title = g_strdup (panel_get_title_without_hotkey (panel_fields[i].title_hotkey));
1823             darr->id = panel_fields[i].id;
1824             darr->expand = panel_fields[i].expands;
1825             darr->just_mode = panel_fields[i].default_just;
1826 
1827             if (set_justify)
1828             {
1829                 if (IS_FIT (darr->just_mode))
1830                     darr->just_mode = MAKE_FIT (justify);
1831                 else
1832                     darr->just_mode = justify;
1833             }
1834 
1835             format = skip_separators (format);
1836 
1837             /* If we have a size specifier */
1838             if (*format == ':')
1839             {
1840                 int req_length;
1841 
1842                 /* If the size was specified, we don't want
1843                  * auto-expansion by default
1844                  */
1845                 darr->expand = FALSE;
1846                 format++;
1847                 req_length = atoi (format);
1848                 darr->requested_field_len = req_length;
1849 
1850                 format = skip_numbers (format);
1851 
1852                 /* Now, if they insist on expansion */
1853                 if (*format == '+')
1854                 {
1855                     darr->expand = TRUE;
1856                     format++;
1857                 }
1858             }
1859         }
1860         else
1861         {
1862             size_t pos;
1863             char *tmp_format;
1864 
1865             pos = strlen (format);
1866             if (pos > 8)
1867                 pos = 8;
1868 
1869             tmp_format = g_strndup (format, pos);
1870             g_slist_free_full (home, (GDestroyNotify) format_item_free);
1871             *error =
1872                 g_strconcat (_("Unknown tag on display format:"), " ", tmp_format, (char *) NULL);
1873             g_free (tmp_format);
1874 
1875             return NULL;
1876         }
1877 
1878         total_cols += darr->requested_field_len;
1879     }
1880 
1881     *res_total_cols = total_cols;
1882     return home;
1883 }
1884 
1885 /* --------------------------------------------------------------------------------------------- */
1886 
1887 static GSList *
1888 use_display_format (WPanel *panel, const char *format, char **error, gboolean isstatus)
     /* [previous][next][first][last][top][bottom][index][help]  */
1889 {
1890 #define MAX_EXPAND 4
1891     int expand_top = 0;         /* Max used element in expand */
1892     int usable_columns;         /* Usable columns in the panel */
1893     int total_cols = 0;
1894     GSList *darr, *home;
1895 
1896     if (format == NULL)
1897         format = DEFAULT_USER_FORMAT;
1898 
1899     home = parse_display_format (panel, format, error, isstatus, &total_cols);
1900 
1901     if (*error != NULL)
1902         return NULL;
1903 
1904     panel->dirty = TRUE;
1905 
1906     usable_columns = WIDGET (panel)->rect.cols - 2;
1907     /* Status needn't to be split */
1908     if (!isstatus)
1909     {
1910         usable_columns /= panel->list_cols;
1911         if (panel->list_cols > 1)
1912             usable_columns--;
1913     }
1914 
1915     /* Look for the expandable fields and set field_len based on the requested field len */
1916     for (darr = home; darr != NULL && expand_top < MAX_EXPAND; darr = g_slist_next (darr))
1917     {
1918         format_item_t *fi = (format_item_t *) darr->data;
1919 
1920         fi->field_len = fi->requested_field_len;
1921         if (fi->expand)
1922             expand_top++;
1923     }
1924 
1925     /* If we used more columns than the available columns, adjust that */
1926     if (total_cols > usable_columns)
1927     {
1928         int dif;
1929         int pdif = 0;
1930 
1931         dif = total_cols - usable_columns;
1932 
1933         while (dif != 0 && pdif != dif)
1934         {
1935             pdif = dif;
1936 
1937             for (darr = home; darr != NULL; darr = g_slist_next (darr))
1938             {
1939                 format_item_t *fi = (format_item_t *) darr->data;
1940 
1941                 if (dif != 0 && fi->field_len != 1)
1942                 {
1943                     fi->field_len--;
1944                     dif--;
1945                 }
1946             }
1947         }
1948 
1949         total_cols = usable_columns;    /* give up, the rest should be truncated */
1950     }
1951 
1952     /* Expand the available space */
1953     if (usable_columns > total_cols && expand_top != 0)
1954     {
1955         int i;
1956         int spaces;
1957 
1958         spaces = (usable_columns - total_cols) / expand_top;
1959 
1960         for (i = 0, darr = home; darr != NULL && i < expand_top; darr = g_slist_next (darr))
1961         {
1962             format_item_t *fi = (format_item_t *) darr->data;
1963 
1964             if (fi->expand)
1965             {
1966                 fi->field_len += spaces;
1967                 if (i == 0)
1968                     fi->field_len += (usable_columns - total_cols) % expand_top;
1969                 i++;
1970             }
1971         }
1972     }
1973 
1974     return home;
1975 }
1976 
1977 /* --------------------------------------------------------------------------------------------- */
1978 /** Given the panel->view_type returns the format string to be parsed */
1979 
1980 static const char *
1981 panel_format (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1982 {
1983     switch (panel->list_format)
1984     {
1985     case list_long:
1986         return "full perm space nlink space owner space group space size space mtime space name";
1987 
1988     case list_brief:
1989         {
1990             static char format[BUF_TINY];
1991             int brief_cols = panel->brief_cols;
1992 
1993             if (brief_cols < 1)
1994                 brief_cols = 2;
1995 
1996             if (brief_cols > 9)
1997                 brief_cols = 9;
1998 
1999             g_snprintf (format, sizeof (format), "half %d type name", brief_cols);
2000             return format;
2001         }
2002 
2003     case list_user:
2004         return panel->user_format;
2005 
2006     default:
2007     case list_full:
2008         return "half type name | size | mtime";
2009     }
2010 }
2011 
2012 /* --------------------------------------------------------------------------------------------- */
2013 
2014 static const char *
2015 mini_status_format (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2016 {
2017     if (panel->user_mini_status)
2018         return panel->user_status_format[panel->list_format];
2019 
2020     switch (panel->list_format)
2021     {
2022     case list_long:
2023         return "full perm space nlink space owner space group space size space mtime space name";
2024 
2025     case list_brief:
2026         return "half type name space bsize space perm space";
2027 
2028     case list_full:
2029         return "half type name";
2030 
2031     default:
2032     case list_user:
2033         return panel->user_format;
2034     }
2035 }
2036 
2037 /*                          */
2038 /* Panel operation commands */
2039 /*                          */
2040 
2041 /* --------------------------------------------------------------------------------------------- */
2042 
2043 static void
2044 cd_up_dir (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2045 {
2046     vfs_path_t *up_dir;
2047 
2048     up_dir = vfs_path_from_str ("..");
2049     panel_cd (panel, up_dir, cd_exact);
2050     vfs_path_free (up_dir, TRUE);
2051 }
2052 
2053 /* --------------------------------------------------------------------------------------------- */
2054 /** Used to emulate Lynx's entering leaving a directory with the arrow keys */
2055 
2056 static cb_ret_t
2057 maybe_cd (WPanel *panel, gboolean move_up_dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
2058 {
2059     if (panels_options.navigate_with_arrows && input_is_empty (cmdline))
2060     {
2061         const file_entry_t *fe;
2062 
2063         if (move_up_dir)
2064         {
2065             cd_up_dir (panel);
2066             return MSG_HANDLED;
2067         }
2068 
2069         fe = panel_current_entry (panel);
2070 
2071         if (fe != NULL && (S_ISDIR (fe->st.st_mode) || link_isdir (fe)))
2072         {
2073             vfs_path_t *vpath;
2074 
2075             vpath = vfs_path_from_str (fe->fname->str);
2076             panel_cd (panel, vpath, cd_exact);
2077             vfs_path_free (vpath, TRUE);
2078             return MSG_HANDLED;
2079         }
2080     }
2081 
2082     return MSG_NOT_HANDLED;
2083 }
2084 
2085 /* --------------------------------------------------------------------------------------------- */
2086 
2087 /* if command line is empty then do 'cd ..' */
2088 static cb_ret_t
2089 force_maybe_cd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2090 {
2091     if (input_is_empty (cmdline))
2092     {
2093         cd_up_dir (panel);
2094         return MSG_HANDLED;
2095     }
2096 
2097     return MSG_NOT_HANDLED;
2098 }
2099 
2100 /* --------------------------------------------------------------------------------------------- */
2101 
2102 static void
2103 unselect_item (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2104 {
2105     const file_entry_t *fe;
2106 
2107     fe = panel_current_entry (panel);
2108     repaint_file (panel, panel->current,
2109                   fe != NULL && fe->f.marked != 0 ? FATTR_MARKED : FATTR_NORMAL);
2110 }
2111 
2112 /* --------------------------------------------------------------------------------------------- */
2113 /** Select/unselect all the files like a current file by extension */
2114 
2115 static void
2116 panel_select_ext_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2117 {
2118     const file_entry_t *fe;
2119     gboolean do_select;
2120     char *reg_exp, *cur_file_ext;
2121     mc_search_t *search;
2122     int i;
2123 
2124     fe = panel_current_entry (panel);
2125     if (fe == NULL)
2126         return;
2127 
2128     do_select = (fe->f.marked == 0);
2129 
2130     cur_file_ext = str_regex_escape (extension (fe->fname->str));
2131     if (cur_file_ext[0] != '\0')
2132         reg_exp = g_strconcat ("^.*\\.", cur_file_ext, "$", (char *) NULL);
2133     else
2134         reg_exp = g_strdup ("^[^\\.]+$");
2135 
2136     g_free (cur_file_ext);
2137 
2138     search = mc_search_new (reg_exp, NULL);
2139     search->search_type = MC_SEARCH_T_REGEX;
2140     search->is_case_sensitive = FALSE;
2141 
2142     for (i = 0; i < panel->dir.len; i++)
2143     {
2144         fe = &panel->dir.list[i];
2145 
2146         if (DIR_IS_DOTDOT (fe->fname->str) || S_ISDIR (fe->st.st_mode))
2147             continue;
2148 
2149         if (!mc_search_run (search, fe->fname->str, 0, fe->fname->len, NULL))
2150             continue;
2151 
2152         do_file_mark (panel, i, do_select ? 1 : 0);
2153     }
2154 
2155     mc_search_free (search);
2156     g_free (reg_exp);
2157 }
2158 
2159 /* --------------------------------------------------------------------------------------------- */
2160 
2161 static void
2162 panel_set_current (WPanel *panel, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
2163 {
2164     if (i != panel->current)
2165     {
2166         panel->dirty = TRUE;
2167         panel->current = i;
2168         panel->top = panel->current - (WIDGET (panel)->rect.lines - 2) / 2;
2169         if (panel->top < 0)
2170             panel->top = 0;
2171     }
2172 }
2173 
2174 /* --------------------------------------------------------------------------------------------- */
2175 
2176 static int
2177 panel_current_at_half (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2178 {
2179     int lines, top;
2180 
2181     lines = panel_lines (panel);
2182 
2183     /* define top file of column */
2184     top = panel->top;
2185     if (panel->list_cols > 1)
2186         top += lines * ((panel->current - top) / lines);
2187 
2188     return (panel->current - top - lines / 2);
2189 }
2190 
2191 /* --------------------------------------------------------------------------------------------- */
2192 
2193 static void
2194 move_down (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2195 {
2196     int items;
2197 
2198     if (panel->dir.len == 0 || panel->current + 1 == panel->dir.len)
2199         return;
2200 
2201     unselect_item (panel);
2202     panel->current++;
2203 
2204     items = panel_items (panel);
2205 
2206     if (panels_options.scroll_pages && panel->current - panel->top == items)
2207     {
2208         /* Scroll window half screen */
2209         panel->top += items / 2;
2210         if (panel->top > panel->dir.len - items)
2211             panel->top = panel->dir.len - items;
2212         paint_dir (panel);
2213     }
2214     else if (panels_options.scroll_center && panel_current_at_half (panel) > 0)
2215     {
2216         /* Scroll window when cursor is halfway down */
2217         panel->top++;
2218         if (panel->top > panel->dir.len - items)
2219             panel->top = panel->dir.len - items;
2220     }
2221     select_item (panel);
2222 }
2223 
2224 /* --------------------------------------------------------------------------------------------- */
2225 
2226 static void
2227 move_up (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2228 {
2229     if (panel->dir.len == 0 || panel->current <= 0)
2230         return;
2231 
2232     unselect_item (panel);
2233     panel->current--;
2234 
2235     if (panels_options.scroll_pages && panel->current < panel->top)
2236     {
2237         /* Scroll window half screen */
2238         panel->top -= panel_items (panel) / 2;
2239         if (panel->top < 0)
2240             panel->top = 0;
2241         paint_dir (panel);
2242     }
2243     else if (panels_options.scroll_center && panel_current_at_half (panel) < 0)
2244     {
2245         /* Scroll window when cursor is halfway up */
2246         panel->top--;
2247         if (panel->top < 0)
2248             panel->top = 0;
2249     }
2250     select_item (panel);
2251 }
2252 
2253 /* --------------------------------------------------------------------------------------------- */
2254 /** Changes the current by lines (may be negative) */
2255 
2256 static void
2257 panel_move_current (WPanel *panel, int lines)
     /* [previous][next][first][last][top][bottom][index][help]  */
2258 {
2259     int new_pos;
2260     gboolean adjust = FALSE;
2261 
2262     if (panel->dir.len == 0 || panel->current < 0)
2263         return;
2264 
2265     new_pos = panel->current + lines;
2266     if (new_pos >= panel->dir.len)
2267         new_pos = panel->dir.len - 1;
2268 
2269     if (new_pos < 0)
2270         new_pos = 0;
2271 
2272     unselect_item (panel);
2273     panel->current = new_pos;
2274 
2275     if (panel->current - panel->top >= panel_items (panel))
2276     {
2277         panel->top += lines;
2278         adjust = TRUE;
2279     }
2280 
2281     if (panel->current - panel->top < 0)
2282     {
2283         panel->top += lines;
2284         adjust = TRUE;
2285     }
2286 
2287     if (adjust)
2288     {
2289         if (panel->top > panel->current)
2290             panel->top = panel->current;
2291         if (panel->top < 0)
2292             panel->top = 0;
2293         paint_dir (panel);
2294     }
2295     select_item (panel);
2296 }
2297 
2298 /* --------------------------------------------------------------------------------------------- */
2299 
2300 static cb_ret_t
2301 move_left (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2302 {
2303     if (panel->list_cols > 1)
2304     {
2305         panel_move_current (panel, -panel_lines (panel));
2306         return MSG_HANDLED;
2307     }
2308 
2309     return maybe_cd (panel, TRUE);      /* cd .. */
2310 }
2311 
2312 /* --------------------------------------------------------------------------------------------- */
2313 
2314 static cb_ret_t
2315 move_right (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2316 {
2317     if (panel->list_cols > 1)
2318     {
2319         panel_move_current (panel, panel_lines (panel));
2320         return MSG_HANDLED;
2321     }
2322 
2323     return maybe_cd (panel, FALSE);     /* cd (current) */
2324 }
2325 
2326 /* --------------------------------------------------------------------------------------------- */
2327 
2328 static void
2329 prev_page (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2330 {
2331     int items;
2332 
2333     if (panel->dir.len == 0 || panel->current < 0 || (panel->current == 0 && panel->top == 0))
2334         return;
2335 
2336     unselect_item (panel);
2337     items = panel_items (panel);
2338     if (panel->top < items)
2339         items = panel->top;
2340     if (items == 0)
2341         panel->current = 0;
2342     else
2343         panel->current -= items;
2344     panel->top -= items;
2345 
2346     select_item (panel);
2347     paint_dir (panel);
2348 }
2349 
2350 /* --------------------------------------------------------------------------------------------- */
2351 
2352 static void
2353 goto_parent_dir (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2354 {
2355     if (!panel->is_panelized)
2356         cd_up_dir (panel);
2357     else
2358     {
2359         const file_entry_t *fe;
2360         GString *fname;
2361         const char *bname;
2362         vfs_path_t *dname_vpath;
2363 
2364         fe = panel_current_entry (panel);
2365         if (fe == NULL)
2366             return;
2367 
2368         fname = fe->fname;
2369 
2370         if (g_path_is_absolute (fname->str))
2371             fname = mc_g_string_dup (fname);
2372         else
2373         {
2374             char *fname2;
2375 
2376             fname2 =
2377                 mc_build_filename (vfs_path_as_str (panel->panelized_descr->root_vpath), fname->str,
2378                                    (char *) NULL);
2379 
2380             fname = g_string_new_take (fname2);
2381         }
2382 
2383         bname = x_basename (fname->str);
2384 
2385         if (bname == fname->str)
2386             dname_vpath = vfs_path_from_str (".");
2387         else
2388         {
2389             g_string_truncate (fname, bname - fname->str);
2390             dname_vpath = vfs_path_from_str (fname->str);
2391         }
2392 
2393         panel_cd (panel, dname_vpath, cd_exact);
2394         panel_set_current_by_name (panel, bname);
2395 
2396         vfs_path_free (dname_vpath, TRUE);
2397         g_string_free (fname, TRUE);
2398     }
2399 }
2400 
2401 /* --------------------------------------------------------------------------------------------- */
2402 
2403 static void
2404 next_page (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2405 {
2406     int items;
2407 
2408     if (panel->dir.len == 0 || panel->current < 0 || panel->current == panel->dir.len - 1)
2409         return;
2410 
2411     unselect_item (panel);
2412     items = panel_items (panel);
2413     if (panel->top > panel->dir.len - 2 * items)
2414         items = panel->dir.len - items - panel->top;
2415     if (panel->top + items < 0)
2416         items = -panel->top;
2417     if (items == 0)
2418         panel->current = panel->dir.len - 1;
2419     else
2420         panel->current += items;
2421     panel->top += items;
2422 
2423     select_item (panel);
2424     paint_dir (panel);
2425 }
2426 
2427 /* --------------------------------------------------------------------------------------------- */
2428 
2429 static void
2430 goto_child_dir (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2431 {
2432     const file_entry_t *fe;
2433 
2434     fe = panel_current_entry (panel);
2435 
2436     if (fe != NULL && (S_ISDIR (fe->st.st_mode) || link_isdir (fe)))
2437     {
2438         vfs_path_t *vpath;
2439 
2440         vpath = vfs_path_from_str (fe->fname->str);
2441         panel_cd (panel, vpath, cd_exact);
2442         vfs_path_free (vpath, TRUE);
2443     }
2444 }
2445 
2446 /* --------------------------------------------------------------------------------------------- */
2447 
2448 static void
2449 goto_top_file (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2450 {
2451     if (panel->dir.len == 0 || panel->current < 0)
2452         return;
2453 
2454     unselect_item (panel);
2455     panel->current = panel->top;
2456     select_item (panel);
2457 }
2458 
2459 /* --------------------------------------------------------------------------------------------- */
2460 
2461 static void
2462 goto_middle_file (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2463 {
2464     if (panel->dir.len == 0 || panel->current < 0)
2465         return;
2466 
2467     unselect_item (panel);
2468     panel->current = panel->top + panel_items (panel) / 2;
2469     select_item (panel);
2470 }
2471 
2472 /* --------------------------------------------------------------------------------------------- */
2473 
2474 static void
2475 goto_bottom_file (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2476 {
2477     if (panel->dir.len == 0 || panel->current < 0)
2478         return;
2479 
2480     unselect_item (panel);
2481     panel->current = panel->top + panel_items (panel) - 1;
2482     select_item (panel);
2483 }
2484 
2485 /* --------------------------------------------------------------------------------------------- */
2486 
2487 static void
2488 move_home (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2489 {
2490     if (panel->dir.len == 0 || panel->current <= 0)
2491         return;
2492 
2493     unselect_item (panel);
2494 
2495     if (panels_options.torben_fj_mode)
2496     {
2497         int middle_pos;
2498 
2499         middle_pos = panel->top + panel_items (panel) / 2;
2500 
2501         if (panel->current > middle_pos)
2502         {
2503             goto_middle_file (panel);
2504             return;
2505         }
2506         if (panel->current != panel->top)
2507         {
2508             goto_top_file (panel);
2509             return;
2510         }
2511     }
2512 
2513     panel->top = 0;
2514     panel->current = 0;
2515 
2516     paint_dir (panel);
2517     select_item (panel);
2518 }
2519 
2520 /* --------------------------------------------------------------------------------------------- */
2521 
2522 static void
2523 move_end (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2524 {
2525     if (panel->dir.len == 0 || panel->current < 0 || panel->current == panel->dir.len - 1)
2526         return;
2527 
2528     unselect_item (panel);
2529 
2530     if (panels_options.torben_fj_mode)
2531     {
2532         int items, middle_pos;
2533 
2534         items = panel_items (panel);
2535         middle_pos = panel->top + items / 2;
2536 
2537         if (panel->current < middle_pos)
2538         {
2539             goto_middle_file (panel);
2540             return;
2541         }
2542         if (panel->current != panel->top + items - 1)
2543         {
2544             goto_bottom_file (panel);
2545             return;
2546         }
2547     }
2548 
2549     panel->current = panel->dir.len - 1;
2550     paint_dir (panel);
2551     select_item (panel);
2552 }
2553 
2554 /* --------------------------------------------------------------------------------------------- */
2555 
2556 static void
2557 do_mark_file (WPanel *panel, mark_act_t do_move)
     /* [previous][next][first][last][top][bottom][index][help]  */
2558 {
2559     const file_entry_t *fe;
2560 
2561     fe = panel_current_entry (panel);
2562     if (fe == NULL)
2563         return;
2564 
2565     do_file_mark (panel, panel->current, fe->f.marked ? 0 : 1);
2566 
2567     if ((panels_options.mark_moves_down && do_move == MARK_DOWN) || do_move == MARK_FORCE_DOWN)
2568         move_down (panel);
2569     else if (do_move == MARK_FORCE_UP)
2570         move_up (panel);
2571 }
2572 
2573 /* --------------------------------------------------------------------------------------------- */
2574 
2575 static inline void
2576 mark_file (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2577 {
2578     do_mark_file (panel, MARK_DOWN);
2579 }
2580 
2581 /* --------------------------------------------------------------------------------------------- */
2582 
2583 static inline void
2584 mark_file_up (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2585 {
2586     do_mark_file (panel, MARK_FORCE_UP);
2587 }
2588 
2589 /* --------------------------------------------------------------------------------------------- */
2590 
2591 static inline void
2592 mark_file_down (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2593 {
2594     do_mark_file (panel, MARK_FORCE_DOWN);
2595 }
2596 
2597 /* --------------------------------------------------------------------------------------------- */
2598 
2599 static void
2600 mark_file_right (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2601 {
2602     int lines;
2603 
2604     if (state_mark < 0)
2605     {
2606         const file_entry_t *fe;
2607 
2608         fe = panel_current_entry (panel);
2609         if (fe == NULL)
2610             return;
2611 
2612         state_mark = fe->f.marked ? 0 : 1;
2613     }
2614 
2615     lines = panel_lines (panel);
2616     lines = MIN (lines, panel->dir.len - panel->current - 1);
2617     for (; lines != 0; lines--)
2618     {
2619         do_file_mark (panel, panel->current, state_mark);
2620         move_down (panel);
2621     }
2622     do_file_mark (panel, panel->current, state_mark);
2623 }
2624 
2625 /* --------------------------------------------------------------------------------------------- */
2626 
2627 static void
2628 mark_file_left (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2629 {
2630     int lines;
2631 
2632     if (state_mark < 0)
2633     {
2634         const file_entry_t *fe;
2635 
2636         fe = panel_current_entry (panel);
2637         if (fe == NULL)
2638             return;
2639 
2640         state_mark = fe->f.marked ? 0 : 1;
2641     }
2642 
2643     lines = panel_lines (panel);
2644     lines = MIN (lines, panel->current + 1);
2645     for (; lines != 0; lines--)
2646     {
2647         do_file_mark (panel, panel->current, state_mark);
2648         move_up (panel);
2649     }
2650     do_file_mark (panel, panel->current, state_mark);
2651 }
2652 
2653 /* --------------------------------------------------------------------------------------------- */
2654 
2655 static mc_search_t *
2656 panel_select_unselect_files_dialog (select_flags_t *flags, const char *title,
     /* [previous][next][first][last][top][bottom][index][help]  */
2657                                     const char *history_name, const char *help_section, char **str)
2658 {
2659     gboolean files_only = (*flags & SELECT_FILES_ONLY) != 0;
2660     gboolean case_sens = (*flags & SELECT_MATCH_CASE) != 0;
2661     gboolean shell_patterns = (*flags & SELECT_SHELL_PATTERNS) != 0;
2662 
2663     char *reg_exp;
2664     mc_search_t *search;
2665 
2666     quick_widget_t quick_widgets[] = {
2667         /* *INDENT-OFF* */
2668         QUICK_INPUT (INPUT_LAST_TEXT, history_name, &reg_exp, NULL,
2669                      FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
2670         QUICK_START_COLUMNS,
2671             QUICK_CHECKBOX (N_("&Files only"), &files_only, NULL),
2672             QUICK_CHECKBOX (N_("&Using shell patterns"), &shell_patterns, NULL),
2673         QUICK_NEXT_COLUMN,
2674             QUICK_CHECKBOX (N_("&Case sensitive"), &case_sens, NULL),
2675         QUICK_STOP_COLUMNS,
2676         QUICK_END
2677         /* *INDENT-ON* */
2678     };
2679 
2680     WRect r = { -1, -1, 0, 50 };
2681 
2682     quick_dialog_t qdlg = {
2683         r, title, help_section,
2684         quick_widgets, NULL, NULL
2685     };
2686 
2687     if (quick_dialog (&qdlg) == B_CANCEL)
2688         return NULL;
2689 
2690     if (*reg_exp == '\0')
2691     {
2692         g_free (reg_exp);
2693         if (str != NULL)
2694             *str = NULL;
2695         return SELECT_RESET;
2696     }
2697 
2698     search = mc_search_new (reg_exp, NULL);
2699     search->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
2700     search->is_entire_line = TRUE;
2701     search->is_case_sensitive = case_sens;
2702 
2703     if (str != NULL)
2704         *str = reg_exp;
2705     else
2706         g_free (reg_exp);
2707 
2708     if (!mc_search_prepare (search))
2709     {
2710         message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
2711         mc_search_free (search);
2712         return SELECT_ERROR;
2713     }
2714 
2715     /* result flags */
2716     *flags = 0;
2717     if (case_sens)
2718         *flags |= SELECT_MATCH_CASE;
2719     if (files_only)
2720         *flags |= SELECT_FILES_ONLY;
2721     if (shell_patterns)
2722         *flags |= SELECT_SHELL_PATTERNS;
2723 
2724     return search;
2725 }
2726 
2727 /* --------------------------------------------------------------------------------------------- */
2728 
2729 static void
2730 panel_select_unselect_files (WPanel *panel, const char *title, const char *history_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
2731                              const char *help_section, gboolean do_select)
2732 {
2733     mc_search_t *search;
2734     gboolean files_only;
2735     int i;
2736 
2737     search = panel_select_unselect_files_dialog (&panels_options.select_flags, title, history_name,
2738                                                  help_section, NULL);
2739     if (search == NULL || search == SELECT_RESET || search == SELECT_ERROR)
2740         return;
2741 
2742     files_only = (panels_options.select_flags & SELECT_FILES_ONLY) != 0;
2743 
2744     for (i = 0; i < panel->dir.len; i++)
2745     {
2746         if (DIR_IS_DOTDOT (panel->dir.list[i].fname->str))
2747             continue;
2748         if (S_ISDIR (panel->dir.list[i].st.st_mode) && files_only)
2749             continue;
2750 
2751         if (mc_search_run
2752             (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
2753             do_file_mark (panel, i, do_select ? 1 : 0);
2754     }
2755 
2756     mc_search_free (search);
2757 }
2758 
2759 /* --------------------------------------------------------------------------------------------- */
2760 
2761 static void
2762 panel_select_files (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2763 {
2764     panel_select_unselect_files (panel, _("Select"), MC_HISTORY_FM_PANEL_SELECT,
2765                                  "[Select/Unselect Files]", TRUE);
2766 }
2767 
2768 /* --------------------------------------------------------------------------------------------- */
2769 
2770 static void
2771 panel_unselect_files (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2772 {
2773     panel_select_unselect_files (panel, _("Unselect"), MC_HISTORY_FM_PANEL_UNSELECT,
2774                                  "[Select/Unselect Files]", FALSE);
2775 }
2776 
2777 /* --------------------------------------------------------------------------------------------- */
2778 
2779 static void
2780 panel_select_invert_files (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2781 {
2782     int i;
2783 
2784     for (i = 0; i < panel->dir.len; i++)
2785     {
2786         file_entry_t *file = &panel->dir.list[i];
2787 
2788         if (!panels_options.reverse_files_only || !S_ISDIR (file->st.st_mode))
2789             do_file_mark (panel, i, file->f.marked ? 0 : 1);
2790     }
2791 }
2792 
2793 /* --------------------------------------------------------------------------------------------- */
2794 
2795 static void
2796 panel_do_set_filter (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2797 {
2798     /* *INDENT-OFF* */
2799     file_filter_t ff = { .value = NULL, .handler = NULL, .flags = panel->filter.flags };
2800     /* *INDENT-ON* */
2801 
2802     ff.handler =
2803         panel_select_unselect_files_dialog (&ff.flags, _("Filter"), MC_HISTORY_FM_PANEL_FILTER,
2804                                             "[Filter...]", &ff.value);
2805 
2806     if (ff.handler == NULL || ff.handler == SELECT_ERROR)
2807         return;
2808 
2809     if (ff.handler == SELECT_RESET)
2810         ff.handler = NULL;
2811 
2812     panel_set_filter (panel, &ff);
2813 }
2814 
2815 /* --------------------------------------------------------------------------------------------- */
2816 /** Incremental search of a file name in the panel.
2817   * @param panel instance of WPanel structure
2818   * @param c_code key code
2819   */
2820 
2821 static void
2822 do_search (WPanel *panel, int c_code)
     /* [previous][next][first][last][top][bottom][index][help]  */
2823 {
2824     int curr;
2825     int i;
2826     gboolean wrapped = FALSE;
2827     char *act;
2828     mc_search_t *search;
2829     char *reg_exp, *esc_str;
2830     gboolean is_found = FALSE;
2831 
2832     if (c_code == KEY_BACKSPACE)
2833     {
2834         if (panel->quick_search.buffer->len != 0)
2835         {
2836             act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
2837             str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
2838             g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
2839         }
2840         panel->quick_search.chpoint = 0;
2841     }
2842     else
2843     {
2844         if (c_code != 0 && (gsize) panel->quick_search.chpoint < sizeof (panel->quick_search.ch))
2845         {
2846             panel->quick_search.ch[panel->quick_search.chpoint] = c_code;
2847             panel->quick_search.chpoint++;
2848         }
2849 
2850         if (panel->quick_search.chpoint > 0)
2851         {
2852             switch (str_is_valid_char (panel->quick_search.ch, panel->quick_search.chpoint))
2853             {
2854             case -2:
2855                 return;
2856             case -1:
2857                 panel->quick_search.chpoint = 0;
2858                 return;
2859             default:
2860                 g_string_append_len (panel->quick_search.buffer, panel->quick_search.ch,
2861                                      panel->quick_search.chpoint);
2862                 panel->quick_search.chpoint = 0;
2863             }
2864         }
2865     }
2866 
2867     reg_exp = g_strdup_printf ("%s*", panel->quick_search.buffer->str);
2868     esc_str = str_escape (reg_exp, -1, ",|\\{}[]", TRUE);
2869     search = mc_search_new (esc_str, NULL);
2870     search->search_type = MC_SEARCH_T_GLOB;
2871     search->is_entire_line = TRUE;
2872 
2873     switch (panels_options.qsearch_mode)
2874     {
2875     case QSEARCH_CASE_SENSITIVE:
2876         search->is_case_sensitive = TRUE;
2877         break;
2878     case QSEARCH_CASE_INSENSITIVE:
2879         search->is_case_sensitive = FALSE;
2880         break;
2881     default:
2882         search->is_case_sensitive = panel->sort_info.case_sensitive;
2883         break;
2884     }
2885 
2886     curr = panel->current;
2887 
2888     for (i = panel->current; !wrapped || i != panel->current; i++)
2889     {
2890         if (i >= panel->dir.len)
2891         {
2892             i = 0;
2893             if (wrapped)
2894                 break;
2895             wrapped = TRUE;
2896         }
2897         if (mc_search_run
2898             (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
2899         {
2900             curr = i;
2901             is_found = TRUE;
2902             break;
2903         }
2904     }
2905     if (is_found)
2906     {
2907         unselect_item (panel);
2908         panel->current = curr;
2909         select_item (panel);
2910         widget_draw (WIDGET (panel));
2911     }
2912     else if (c_code != KEY_BACKSPACE)
2913     {
2914         act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
2915         str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
2916         g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
2917     }
2918     mc_search_free (search);
2919     g_free (reg_exp);
2920     g_free (esc_str);
2921 }
2922 
2923 /* --------------------------------------------------------------------------------------------- */
2924 /** Start new search.
2925   * @param panel instance of WPanel structure
2926   */
2927 
2928 static void
2929 start_search (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2930 {
2931     if (panel->quick_search.active)
2932     {
2933         if (panel->current == panel->dir.len - 1)
2934             panel->current = 0;
2935         else
2936             move_down (panel);
2937 
2938         /* in case if there was no search string we need to recall
2939            previous string, with which we ended previous searching */
2940         if (panel->quick_search.buffer->len == 0)
2941             mc_g_string_copy (panel->quick_search.buffer, panel->quick_search.prev_buffer);
2942 
2943         do_search (panel, 0);
2944     }
2945     else if (panel->dir.len != 0)
2946     {
2947         panel->quick_search.active = TRUE;
2948         g_string_set_size (panel->quick_search.buffer, 0);
2949         panel->quick_search.ch[0] = '\0';
2950         panel->quick_search.chpoint = 0;
2951         display_mini_info (panel);
2952     }
2953 }
2954 
2955 /* --------------------------------------------------------------------------------------------- */
2956 
2957 static void
2958 stop_search (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
2959 {
2960     if (!panel->quick_search.active)
2961         return;
2962 
2963     panel->quick_search.active = FALSE;
2964 
2965     /* if user overrdied search string, we need to store it
2966        to the quick_search.prev_buffer */
2967     if (panel->quick_search.buffer->len != 0)
2968         mc_g_string_copy (panel->quick_search.prev_buffer, panel->quick_search.buffer);
2969 
2970     display_mini_info (panel);
2971 }
2972 
2973 /* --------------------------------------------------------------------------------------------- */
2974 /** Return TRUE if the Enter key has been processed, FALSE otherwise */
2975 
2976 static gboolean
2977 do_enter_on_file_entry (WPanel *panel, const file_entry_t *fe)
     /* [previous][next][first][last][top][bottom][index][help]  */
2978 {
2979     const char *fname = fe->fname->str;
2980     char *fname_quoted;
2981     vfs_path_t *full_name_vpath;
2982     gboolean ok;
2983 
2984     /*
2985      * Directory or link to directory - change directory.
2986      * Try the same for the entries on which mc_lstat() has failed.
2987      */
2988     if (S_ISDIR (fe->st.st_mode) || link_isdir (fe) || (fe->st.st_mode == 0))
2989     {
2990         vfs_path_t *fname_vpath;
2991 
2992         fname_vpath = vfs_path_from_str (fname);
2993         if (!panel_cd (panel, fname_vpath, cd_exact))
2994             cd_error_message (fname);
2995         vfs_path_free (fname_vpath, TRUE);
2996         return TRUE;
2997     }
2998 
2999     full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
3000 
3001     /* Try associated command */
3002     ok = regex_command (full_name_vpath, "Open") != 0;
3003     vfs_path_free (full_name_vpath, TRUE);
3004     if (ok)
3005         return TRUE;
3006 
3007     /* Check if the file is executable */
3008     full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
3009     ok = (is_exe (fe->st.st_mode) && if_link_is_exe (full_name_vpath, fe));
3010     vfs_path_free (full_name_vpath, TRUE);
3011     if (!ok)
3012         return FALSE;
3013 
3014     if (confirm_execute
3015         && query_dialog (_("The Midnight Commander"), _("Do you really want to execute?"), D_NORMAL,
3016                          2, _("&Yes"), _("&No")) != 0)
3017         return TRUE;
3018 
3019     if (!vfs_current_is_local ())
3020     {
3021         int ret;
3022         vfs_path_t *tmp_vpath;
3023 
3024         tmp_vpath = vfs_path_append_new (vfs_get_raw_current_dir (), fname, (char *) NULL);
3025         ret = mc_setctl (tmp_vpath, VFS_SETCTL_RUN, NULL);
3026         vfs_path_free (tmp_vpath, TRUE);
3027         /* We took action only if the dialog was shown or the execution was successful */
3028         return confirm_execute || (ret == 0);
3029     }
3030 
3031     fname_quoted = name_quote (fname, FALSE);
3032     if (fname_quoted != NULL)
3033     {
3034         char *cmd;
3035 
3036         cmd = g_strconcat ("." PATH_SEP_STR, fname_quoted, (char *) NULL);
3037         g_free (fname_quoted);
3038 
3039         shell_execute (cmd, 0);
3040         g_free (cmd);
3041     }
3042 
3043 #ifdef HAVE_CHARSET
3044     mc_global.source_codepage = default_source_codepage;
3045 #endif
3046 
3047     return TRUE;
3048 }
3049 
3050 /* --------------------------------------------------------------------------------------------- */
3051 
3052 static inline gboolean
3053 do_enter (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3054 {
3055     const file_entry_t *fe;
3056 
3057     fe = panel_current_entry (panel);
3058 
3059     return (fe == NULL ? FALSE : do_enter_on_file_entry (panel, fe));
3060 }
3061 
3062 /* --------------------------------------------------------------------------------------------- */
3063 
3064 static void
3065 panel_cycle_listing_format (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3066 {
3067     panel->list_format = (panel->list_format + 1) % LIST_FORMATS;
3068 
3069     if (set_panel_formats (panel) == 0)
3070         do_refresh ();
3071 }
3072 
3073 /* --------------------------------------------------------------------------------------------- */
3074 
3075 static void
3076 chdir_other_panel (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3077 {
3078     const file_entry_t *entry;
3079     vfs_path_t *new_dir_vpath;
3080     char *curr_entry = NULL;
3081     WPanel *p;
3082 
3083     entry = panel_current_entry (panel);
3084     if (entry == NULL)
3085         return;
3086 
3087     if (get_other_type () != view_listing)
3088         create_panel (get_other_index (), view_listing);
3089 
3090     if (S_ISDIR (entry->st.st_mode) || link_isdir (entry))
3091         new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, entry->fname->str, (char *) NULL);
3092     else
3093     {
3094         new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, "..", (char *) NULL);
3095         curr_entry = strrchr (vfs_path_get_last_path_str (panel->cwd_vpath), PATH_SEP);
3096     }
3097 
3098     p = change_panel ();
3099     panel_cd (p, new_dir_vpath, cd_exact);
3100     vfs_path_free (new_dir_vpath, TRUE);
3101 
3102     if (curr_entry != NULL)
3103         panel_set_current_by_name (p, curr_entry);
3104     (void) change_panel ();
3105 
3106     move_down (panel);
3107 }
3108 
3109 /* --------------------------------------------------------------------------------------------- */
3110 /**
3111  * Make the current directory of the current panel also the current
3112  * directory of the other panel.  Put the other panel to the listing
3113  * mode if needed.  If the current panel is panelized, the other panel
3114  * doesn't become panelized.
3115  */
3116 
3117 static void
3118 panel_sync_other (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3119 {
3120     if (get_other_type () != view_listing)
3121         create_panel (get_other_index (), view_listing);
3122 
3123     panel_do_cd (other_panel, panel->cwd_vpath, cd_exact);
3124 
3125     /* try to set current filename on the other panel */
3126     if (!panel->is_panelized)
3127     {
3128         const file_entry_t *fe;
3129 
3130         fe = panel_current_entry (panel);
3131         if (fe != NULL)
3132             panel_set_current_by_name (other_panel, fe->fname->str);
3133     }
3134 }
3135 
3136 /* --------------------------------------------------------------------------------------------- */
3137 
3138 static void
3139 chdir_to_readlink (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3140 {
3141     const file_entry_t *fe;
3142     vfs_path_t *new_dir_vpath;
3143     char buffer[MC_MAXPATHLEN];
3144     int i;
3145     struct stat st;
3146     vfs_path_t *panel_fname_vpath;
3147     gboolean ok;
3148     WPanel *cpanel;
3149 
3150     if (get_other_type () != view_listing)
3151         return;
3152 
3153     fe = panel_current_entry (panel);
3154 
3155     if (fe == NULL || !S_ISLNK (fe->st.st_mode))
3156         return;
3157 
3158     i = readlink (fe->fname->str, buffer, MC_MAXPATHLEN - 1);
3159     if (i < 0)
3160         return;
3161 
3162     panel_fname_vpath = vfs_path_from_str (fe->fname->str);
3163     ok = (mc_stat (panel_fname_vpath, &st) >= 0);
3164     vfs_path_free (panel_fname_vpath, TRUE);
3165     if (!ok)
3166         return;
3167 
3168     buffer[i] = '\0';
3169     if (!S_ISDIR (st.st_mode))
3170     {
3171         char *p;
3172 
3173         p = strrchr (buffer, PATH_SEP);
3174         if (p != NULL && p[1] == '\0')
3175         {
3176             *p = '\0';
3177             p = strrchr (buffer, PATH_SEP);
3178         }
3179         if (p == NULL)
3180             return;
3181 
3182         p[1] = '\0';
3183     }
3184     if (IS_PATH_SEP (*buffer))
3185         new_dir_vpath = vfs_path_from_str (buffer);
3186     else
3187         new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, buffer, (char *) NULL);
3188 
3189     cpanel = change_panel ();
3190     panel_cd (cpanel, new_dir_vpath, cd_exact);
3191     vfs_path_free (new_dir_vpath, TRUE);
3192     (void) change_panel ();
3193 
3194     move_down (panel);
3195 }
3196 
3197 /* --------------------------------------------------------------------------------------------- */
3198 /**
3199    function return 0 if not found and REAL_INDEX+1 if found
3200  */
3201 
3202 static gsize
3203 panel_get_format_field_index_by_name (const WPanel *panel, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
3204 {
3205     GSList *format;
3206     gsize lc_index;
3207 
3208     for (lc_index = 1, format = panel->format;
3209          format != NULL && strcmp (((format_item_t *) format->data)->title, name) != 0;
3210          format = g_slist_next (format), lc_index++)
3211         ;
3212 
3213     if (format == NULL)
3214         lc_index = 0;
3215 
3216     return lc_index;
3217 }
3218 
3219 /* --------------------------------------------------------------------------------------------- */
3220 
3221 static const panel_field_t *
3222 panel_get_sortable_field_by_format (const WPanel *panel, gsize lc_index)
     /* [previous][next][first][last][top][bottom][index][help]  */
3223 {
3224     const panel_field_t *pfield;
3225     const format_item_t *format;
3226 
3227     format = (const format_item_t *) g_slist_nth_data (panel->format, lc_index);
3228     if (format == NULL)
3229         return NULL;
3230 
3231     pfield = panel_get_field_by_title (format->title);
3232     if (pfield == NULL)
3233         return NULL;
3234     if (pfield->sort_routine == NULL)
3235         return NULL;
3236     return pfield;
3237 }
3238 
3239 /* --------------------------------------------------------------------------------------------- */
3240 
3241 static void
3242 panel_toggle_sort_order_prev (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3243 {
3244     gsize lc_index, i;
3245     const char *title;
3246     const panel_field_t *pfield = NULL;
3247 
3248     title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
3249     lc_index = panel_get_format_field_index_by_name (panel, title);
3250 
3251     if (lc_index > 1)
3252     {
3253         /* search for prev sortable column in panel format */
3254         for (i = lc_index - 1;
3255              i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
3256             ;
3257     }
3258 
3259     if (pfield == NULL)
3260     {
3261         /* Sortable field not found. Try to search in each array */
3262         for (i = g_slist_length (panel->format);
3263              i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
3264             ;
3265     }
3266 
3267     if (pfield != NULL)
3268     {
3269         panel->sort_field = pfield;
3270         panel_set_sort_order (panel, pfield);
3271     }
3272 }
3273 
3274 /* --------------------------------------------------------------------------------------------- */
3275 
3276 static void
3277 panel_toggle_sort_order_next (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3278 {
3279     gsize lc_index, i;
3280     const panel_field_t *pfield = NULL;
3281     gsize format_field_count;
3282     const char *title;
3283 
3284     format_field_count = g_slist_length (panel->format);
3285     title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
3286     lc_index = panel_get_format_field_index_by_name (panel, title);
3287 
3288     if (lc_index != 0 && lc_index != format_field_count)
3289     {
3290         /* search for prev sortable column in panel format */
3291         for (i = lc_index;
3292              i != format_field_count
3293              && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
3294             ;
3295     }
3296 
3297     if (pfield == NULL)
3298     {
3299         /* Sortable field not found. Try to search in each array */
3300         for (i = 0;
3301              i != format_field_count
3302              && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
3303             ;
3304     }
3305 
3306     if (pfield != NULL)
3307     {
3308         panel->sort_field = pfield;
3309         panel_set_sort_order (panel, pfield);
3310     }
3311 }
3312 
3313 /* --------------------------------------------------------------------------------------------- */
3314 
3315 static void
3316 panel_select_sort_order (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3317 {
3318     const panel_field_t *sort_order;
3319 
3320     sort_order = sort_box (&panel->sort_info, panel->sort_field);
3321     if (sort_order != NULL)
3322     {
3323         panel->sort_field = sort_order;
3324         panel_set_sort_order (panel, sort_order);
3325     }
3326 }
3327 
3328 /* --------------------------------------------------------------------------------------------- */
3329 
3330 /**
3331  * panel_content_scroll_left:
3332  * @param panel the pointer to the panel on which we operate
3333  *
3334  * scroll long filename to the left (decrement scroll pointer)
3335  *
3336  */
3337 
3338 static void
3339 panel_content_scroll_left (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3340 {
3341     if (panel->content_shift > -1)
3342     {
3343         if (panel->content_shift > panel->max_shift)
3344             panel->content_shift = panel->max_shift;
3345 
3346         panel->content_shift--;
3347         show_dir (panel);
3348         paint_dir (panel);
3349     }
3350 }
3351 
3352 /* --------------------------------------------------------------------------------------------- */
3353 /**
3354  * panel_content_scroll_right:
3355  * @param panel the pointer to the panel on which we operate
3356  *
3357  * scroll long filename to the right (increment scroll pointer)
3358  *
3359  */
3360 
3361 static void
3362 panel_content_scroll_right (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3363 {
3364     if (panel->content_shift < 0 || panel->content_shift < panel->max_shift)
3365     {
3366         panel->content_shift++;
3367         show_dir (panel);
3368         paint_dir (panel);
3369     }
3370 }
3371 
3372 /* --------------------------------------------------------------------------------------------- */
3373 
3374 static void
3375 panel_set_sort_type_by_id (WPanel *panel, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
3376 {
3377     if (strcmp (panel->sort_field->id, name) == 0)
3378         panel->sort_info.reverse = !panel->sort_info.reverse;
3379     else
3380     {
3381         const panel_field_t *sort_order;
3382 
3383         sort_order = panel_get_field_by_id (name);
3384         if (sort_order == NULL)
3385             return;
3386 
3387         panel->sort_field = sort_order;
3388     }
3389 
3390     panel_set_sort_order (panel, panel->sort_field);
3391 }
3392 
3393 /* --------------------------------------------------------------------------------------------- */
3394 /**
3395  *  If we moved to the parent directory move the 'current' pointer to
3396  *  the old directory name; If we leave VFS dir, remove FS specificator.
3397  *
3398  *  You do _NOT_ want to add any vfs aware code here. <pavel@ucw.cz>
3399  */
3400 
3401 static const char *
3402 get_parent_dir_name (const vfs_path_t *cwd_vpath, const vfs_path_t *lwd_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
3403 {
3404     size_t llen, clen;
3405     const char *p, *lwd;
3406 
3407     llen = vfs_path_len (lwd_vpath);
3408     clen = vfs_path_len (cwd_vpath);
3409 
3410     if (llen <= clen)
3411         return NULL;
3412 
3413     lwd = vfs_path_as_str (lwd_vpath);
3414 
3415     p = g_strrstr (lwd, VFS_PATH_URL_DELIMITER);
3416 
3417     if (p == NULL)
3418     {
3419         const char *cwd;
3420 
3421         cwd = vfs_path_as_str (cwd_vpath);
3422 
3423         p = strrchr (lwd, PATH_SEP);
3424 
3425         if (p != NULL && strncmp (cwd, lwd, (size_t) (p - lwd)) == 0
3426             && (clen == (size_t) (p - lwd) || (p == lwd && IS_PATH_SEP (cwd[0]) && cwd[1] == '\0')))
3427             return (p + 1);
3428 
3429         return NULL;
3430     }
3431 
3432     /* skip VFS prefix */
3433     while (--p > lwd && !IS_PATH_SEP (*p))
3434         ;
3435     /* get last component */
3436     while (--p > lwd && !IS_PATH_SEP (*p))
3437         ;
3438 
3439     /* return last component */
3440     return (p != lwd || IS_PATH_SEP (*p)) ? p + 1 : p;
3441 }
3442 
3443 /* --------------------------------------------------------------------------------------------- */
3444 /** Wrapper for do_subshell_chdir, check for availability of subshell */
3445 
3446 static void
3447 subshell_chdir (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
3448 {
3449 #ifdef ENABLE_SUBSHELL
3450     if (mc_global.tty.use_subshell && vfs_current_is_local ())
3451         do_subshell_chdir (vpath, FALSE);
3452 #else /* ENABLE_SUBSHELL */
3453     (void) vpath;
3454 #endif /* ENABLE_SUBSHELL */
3455 }
3456 
3457 /* --------------------------------------------------------------------------------------------- */
3458 /**
3459  * Changes the current directory of the panel.
3460  * Don't record change in the directory history.
3461  */
3462 
3463 static gboolean
3464 panel_do_cd_int (WPanel *panel, const vfs_path_t *new_dir_vpath, enum cd_enum cd_type)
     /* [previous][next][first][last][top][bottom][index][help]  */
3465 {
3466     vfs_path_t *olddir_vpath;
3467 
3468     /* Convert *new_path to a suitable pathname, handle ~user */
3469     if (cd_type == cd_parse_command)
3470     {
3471         const vfs_path_element_t *element;
3472 
3473         element = vfs_path_get_by_index (new_dir_vpath, 0);
3474         if (strcmp (element->path, "-") == 0)
3475             new_dir_vpath = panel->lwd_vpath;
3476     }
3477 
3478     if (mc_chdir (new_dir_vpath) == -1)
3479         return FALSE;
3480 
3481     /* Success: save previous directory, shutdown status of previous dir */
3482     olddir_vpath = vfs_path_clone (panel->cwd_vpath);
3483     panel_set_lwd (panel, panel->cwd_vpath);
3484     input_complete_free (cmdline);
3485 
3486     vfs_path_free (panel->cwd_vpath, TRUE);
3487     vfs_setup_cwd ();
3488     panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
3489 
3490     vfs_release_path (olddir_vpath);
3491 
3492     subshell_chdir (panel->cwd_vpath);
3493 
3494     /* Reload current panel */
3495     panel_clean_dir (panel);
3496 
3497     if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
3498                         &panel->sort_info, &panel->filter))
3499         message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
3500 
3501     if (panel->dir.len == 0)
3502         panel_set_current (panel, -1);
3503 
3504     panel_set_current_by_name (panel, get_parent_dir_name (panel->cwd_vpath, olddir_vpath));
3505 
3506     load_hint (FALSE);
3507     panel->dirty = TRUE;
3508     update_xterm_title_path ();
3509     update_terminal_cwd ();
3510 
3511     vfs_path_free (olddir_vpath, TRUE);
3512 
3513     return TRUE;
3514 }
3515 
3516 /* --------------------------------------------------------------------------------------------- */
3517 
3518 static void
3519 directory_history_next (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3520 {
3521     gboolean ok;
3522 
3523     do
3524     {
3525         GList *next;
3526 
3527         ok = TRUE;
3528         next = g_list_next (panel->dir_history.current);
3529         if (next != NULL)
3530         {
3531             vfs_path_t *data_vpath;
3532 
3533             data_vpath = vfs_path_from_str ((char *) next->data);
3534             ok = panel_do_cd_int (panel, data_vpath, cd_exact);
3535             vfs_path_free (data_vpath, TRUE);
3536             panel->dir_history.current = next;
3537         }
3538         /* skip directories that present in history but absent in file system */
3539     }
3540     while (!ok);
3541 }
3542 
3543 /* --------------------------------------------------------------------------------------------- */
3544 
3545 static void
3546 directory_history_prev (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3547 {
3548     gboolean ok;
3549 
3550     do
3551     {
3552         GList *prev;
3553 
3554         ok = TRUE;
3555         prev = g_list_previous (panel->dir_history.current);
3556         if (prev != NULL)
3557         {
3558             vfs_path_t *data_vpath;
3559 
3560             data_vpath = vfs_path_from_str ((char *) prev->data);
3561             ok = panel_do_cd_int (panel, data_vpath, cd_exact);
3562             vfs_path_free (data_vpath, TRUE);
3563             panel->dir_history.current = prev;
3564         }
3565         /* skip directories that present in history but absent in file system */
3566     }
3567     while (!ok);
3568 }
3569 
3570 /* --------------------------------------------------------------------------------------------- */
3571 
3572 static void
3573 directory_history_list (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3574 {
3575     history_descriptor_t hd;
3576     gboolean ok = FALSE;
3577     size_t pos;
3578 
3579     pos = g_list_position (panel->dir_history.current, panel->dir_history.list);
3580 
3581     history_descriptor_init (&hd, WIDGET (panel)->rect.y, WIDGET (panel)->rect.x,
3582                              panel->dir_history.list, (int) pos);
3583     history_show (&hd);
3584 
3585     panel->dir_history.list = hd.list;
3586     if (hd.text != NULL)
3587     {
3588         vfs_path_t *s_vpath;
3589 
3590         s_vpath = vfs_path_from_str (hd.text);
3591         ok = panel_do_cd_int (panel, s_vpath, cd_exact);
3592         if (ok)
3593             directory_history_add (panel, panel->cwd_vpath);
3594         else
3595             cd_error_message (hd.text);
3596         vfs_path_free (s_vpath, TRUE);
3597         g_free (hd.text);
3598     }
3599 
3600     if (!ok)
3601     {
3602         /* Since history is fully modified in history_show(), panel->dir_history actually
3603          * points to the invalid place. Try restore current position here. */
3604 
3605         size_t i;
3606 
3607         panel->dir_history.current = panel->dir_history.list;
3608 
3609         for (i = 0; i <= pos; i++)
3610         {
3611             GList *prev;
3612 
3613             prev = g_list_previous (panel->dir_history.current);
3614             if (prev == NULL)
3615                 break;
3616 
3617             panel->dir_history.current = prev;
3618         }
3619     }
3620 }
3621 
3622 /* --------------------------------------------------------------------------------------------- */
3623 
3624 static cb_ret_t
3625 panel_execute_cmd (WPanel *panel, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
3626 {
3627     int res = MSG_HANDLED;
3628 
3629     if (command != CK_Search)
3630         stop_search (panel);
3631 
3632     switch (command)
3633     {
3634     case CK_Up:
3635     case CK_Down:
3636     case CK_Left:
3637     case CK_Right:
3638     case CK_Bottom:
3639     case CK_Top:
3640     case CK_PageDown:
3641     case CK_PageUp:
3642         /* reset state of marks flag */
3643         state_mark = -1;
3644         break;
3645     default:
3646         break;
3647     }
3648 
3649     switch (command)
3650     {
3651     case CK_CycleListingFormat:
3652         panel_cycle_listing_format (panel);
3653         break;
3654     case CK_PanelOtherCd:
3655         chdir_other_panel (panel);
3656         break;
3657     case CK_PanelOtherCdLink:
3658         chdir_to_readlink (panel);
3659         break;
3660     case CK_CopySingle:
3661         copy_cmd_local (panel);
3662         break;
3663     case CK_DeleteSingle:
3664         delete_cmd_local (panel);
3665         break;
3666     case CK_Enter:
3667         do_enter (panel);
3668         break;
3669     case CK_ViewRaw:
3670         view_raw_cmd (panel);
3671         break;
3672     case CK_EditNew:
3673         edit_cmd_new ();
3674         break;
3675     case CK_MoveSingle:
3676         rename_cmd_local (panel);
3677         break;
3678     case CK_SelectInvert:
3679         panel_select_invert_files (panel);
3680         break;
3681     case CK_Select:
3682         panel_select_files (panel);
3683         break;
3684     case CK_SelectExt:
3685         panel_select_ext_cmd (panel);
3686         break;
3687     case CK_Unselect:
3688         panel_unselect_files (panel);
3689         break;
3690     case CK_Filter:
3691         panel_do_set_filter (panel);
3692         break;
3693     case CK_PageDown:
3694         next_page (panel);
3695         break;
3696     case CK_PageUp:
3697         prev_page (panel);
3698         break;
3699     case CK_CdChild:
3700         goto_child_dir (panel);
3701         break;
3702     case CK_CdParent:
3703         goto_parent_dir (panel);
3704         break;
3705     case CK_History:
3706         directory_history_list (panel);
3707         break;
3708     case CK_HistoryNext:
3709         directory_history_next (panel);
3710         break;
3711     case CK_HistoryPrev:
3712         directory_history_prev (panel);
3713         break;
3714     case CK_BottomOnScreen:
3715         goto_bottom_file (panel);
3716         break;
3717     case CK_MiddleOnScreen:
3718         goto_middle_file (panel);
3719         break;
3720     case CK_TopOnScreen:
3721         goto_top_file (panel);
3722         break;
3723     case CK_Mark:
3724         mark_file (panel);
3725         break;
3726     case CK_MarkUp:
3727         mark_file_up (panel);
3728         break;
3729     case CK_MarkDown:
3730         mark_file_down (panel);
3731         break;
3732     case CK_MarkLeft:
3733         mark_file_left (panel);
3734         break;
3735     case CK_MarkRight:
3736         mark_file_right (panel);
3737         break;
3738     case CK_CdParentSmart:
3739         res = force_maybe_cd (panel);
3740         break;
3741     case CK_Up:
3742         move_up (panel);
3743         break;
3744     case CK_Down:
3745         move_down (panel);
3746         break;
3747     case CK_Left:
3748         res = move_left (panel);
3749         break;
3750     case CK_Right:
3751         res = move_right (panel);
3752         break;
3753     case CK_Bottom:
3754         move_end (panel);
3755         break;
3756     case CK_Top:
3757         move_home (panel);
3758         break;
3759 #ifdef HAVE_CHARSET
3760     case CK_SelectCodepage:
3761         panel_change_encoding (panel);
3762         break;
3763 #endif
3764     case CK_ScrollLeft:
3765         panel_content_scroll_left (panel);
3766         break;
3767     case CK_ScrollRight:
3768         panel_content_scroll_right (panel);
3769         break;
3770     case CK_Search:
3771         start_search (panel);
3772         break;
3773     case CK_SearchStop:
3774         break;
3775     case CK_PanelOtherSync:
3776         panel_sync_other (panel);
3777         break;
3778     case CK_Sort:
3779         panel_select_sort_order (panel);
3780         break;
3781     case CK_SortPrev:
3782         panel_toggle_sort_order_prev (panel);
3783         break;
3784     case CK_SortNext:
3785         panel_toggle_sort_order_next (panel);
3786         break;
3787     case CK_SortReverse:
3788         panel->sort_info.reverse = !panel->sort_info.reverse;
3789         panel_set_sort_order (panel, panel->sort_field);
3790         break;
3791     case CK_SortByName:
3792         panel_set_sort_type_by_id (panel, "name");
3793         break;
3794     case CK_SortByExt:
3795         panel_set_sort_type_by_id (panel, "extension");
3796         break;
3797     case CK_SortBySize:
3798         panel_set_sort_type_by_id (panel, "size");
3799         break;
3800     case CK_SortByMTime:
3801         panel_set_sort_type_by_id (panel, "mtime");
3802         break;
3803     default:
3804         res = MSG_NOT_HANDLED;
3805         break;
3806     }
3807 
3808     return res;
3809 }
3810 
3811 /* --------------------------------------------------------------------------------------------- */
3812 
3813 static cb_ret_t
3814 panel_key (WPanel *panel, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
3815 {
3816     long command;
3817 
3818     if (is_abort_char (key))
3819     {
3820         stop_search (panel);
3821         return MSG_HANDLED;
3822     }
3823 
3824     if (panel->quick_search.active && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
3825     {
3826         do_search (panel, key);
3827         return MSG_HANDLED;
3828     }
3829 
3830     command = widget_lookup_key (WIDGET (panel), key);
3831     if (command != CK_IgnoreKey)
3832         return panel_execute_cmd (panel, command);
3833 
3834     if (panels_options.torben_fj_mode && key == ALT ('h'))
3835     {
3836         goto_middle_file (panel);
3837         return MSG_HANDLED;
3838     }
3839 
3840     if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
3841     {
3842         start_search (panel);
3843         do_search (panel, key);
3844         return MSG_HANDLED;
3845     }
3846 
3847     return MSG_NOT_HANDLED;
3848 }
3849 
3850 /* --------------------------------------------------------------------------------------------- */
3851 
3852 static cb_ret_t
3853 panel_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
3854 {
3855     WPanel *panel = PANEL (w);
3856     WDialog *h = DIALOG (w->owner);
3857     WButtonBar *bb;
3858 
3859     switch (msg)
3860     {
3861     case MSG_INIT:
3862         /* subscribe to "history_load" event */
3863         mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w, NULL);
3864         /* subscribe to "history_save" event */
3865         mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w, NULL);
3866         return MSG_HANDLED;
3867 
3868     case MSG_DRAW:
3869         /* Repaint everything, including frame and separator */
3870         widget_erase (w);
3871         show_dir (panel);
3872         panel_print_header (panel);
3873         adjust_top_file (panel);
3874         paint_dir (panel);
3875         mini_info_separator (panel);
3876         display_mini_info (panel);
3877         panel->dirty = FALSE;
3878         return MSG_HANDLED;
3879 
3880     case MSG_FOCUS:
3881         state_mark = -1;
3882         current_panel = panel;
3883         panel->active = TRUE;
3884 
3885         if (mc_chdir (panel->cwd_vpath) != 0)
3886         {
3887             char *cwd;
3888 
3889             cwd = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
3890             cd_error_message (cwd);
3891             g_free (cwd);
3892         }
3893         else
3894             subshell_chdir (panel->cwd_vpath);
3895 
3896         update_xterm_title_path ();
3897         update_terminal_cwd ();
3898         select_item (panel);
3899 
3900         bb = buttonbar_find (h);
3901         midnight_set_buttonbar (bb);
3902         return MSG_HANDLED;
3903 
3904     case MSG_UNFOCUS:
3905         /* Janne: look at this for the multiple panel options */
3906         stop_search (panel);
3907         panel->active = FALSE;
3908         unselect_item (panel);
3909         return MSG_HANDLED;
3910 
3911     case MSG_KEY:
3912         return panel_key (panel, parm);
3913 
3914     case MSG_ACTION:
3915         return panel_execute_cmd (panel, parm);
3916 
3917     case MSG_DESTROY:
3918         vfs_stamp_path (panel->cwd_vpath);
3919         /* unsubscribe from "history_load" event */
3920         mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w);
3921         /* unsubscribe from "history_save" event */
3922         mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w);
3923         panel_destroy (panel);
3924         free_my_statfs ();
3925         return MSG_HANDLED;
3926 
3927     default:
3928         return widget_default_callback (w, sender, msg, parm, data);
3929     }
3930 }
3931 
3932 /* --------------------------------------------------------------------------------------------- */
3933 /*                                     */
3934 /* Panel mouse events support routines */
3935 /*                                     */
3936 
3937 static void
3938 mouse_toggle_mark (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3939 {
3940     const file_entry_t *fe;
3941 
3942     fe = panel_current_entry (panel);
3943     if (fe != NULL)
3944     {
3945         do_mark_file (panel, MARK_DONT_MOVE);
3946         mouse_marking = fe->f.marked != 0;
3947         mouse_mark_panel = current_panel;
3948     }
3949 }
3950 
3951 /* --------------------------------------------------------------------------------------------- */
3952 
3953 static void
3954 mouse_set_mark (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
3955 {
3956     if (mouse_mark_panel == panel)
3957     {
3958         const file_entry_t *fe;
3959 
3960         fe = panel_current_entry (panel);
3961         if (fe != NULL)
3962         {
3963             if (mouse_marking && fe->f.marked == 0)
3964                 do_mark_file (panel, MARK_DONT_MOVE);
3965             else if (!mouse_marking && fe->f.marked != 0)
3966                 do_mark_file (panel, MARK_DONT_MOVE);
3967         }
3968     }
3969 }
3970 
3971 /* --------------------------------------------------------------------------------------------- */
3972 
3973 static void
3974 mark_if_marking (WPanel *panel, const mouse_event_t *event, int previous_current)
     /* [previous][next][first][last][top][bottom][index][help]  */
3975 {
3976     if ((event->buttons & GPM_B_RIGHT) == 0)
3977         return;
3978 
3979     if (event->msg == MSG_MOUSE_DOWN)
3980         mouse_toggle_mark (panel);
3981     else
3982     {
3983         int pcurr, curr1, curr2;
3984 
3985         pcurr = panel->current;
3986         curr1 = MIN (previous_current, panel->current);
3987         curr2 = MAX (previous_current, panel->current);
3988 
3989         for (; curr1 <= curr2; curr1++)
3990         {
3991             panel->current = curr1;
3992             mouse_set_mark (panel);
3993         }
3994 
3995         panel->current = pcurr;
3996     }
3997 }
3998 
3999 /* --------------------------------------------------------------------------------------------- */
4000 /** Determine which column was clicked, and sort the panel on
4001  * that column, or reverse sort on that column if already
4002  * sorted on that column.
4003  */
4004 
4005 static void
4006 mouse_sort_col (WPanel *panel, int x)
     /* [previous][next][first][last][top][bottom][index][help]  */
4007 {
4008     int i = 0;
4009     GSList *format;
4010     const char *lc_sort_name = NULL;
4011     panel_field_t *col_sort_format = NULL;
4012 
4013     for (format = panel->format; format != NULL; format = g_slist_next (format))
4014     {
4015         format_item_t *fi = (format_item_t *) format->data;
4016 
4017         i += fi->field_len;
4018         if (x < i + 1)
4019         {
4020             /* found column */
4021             lc_sort_name = fi->title;
4022             break;
4023         }
4024     }
4025 
4026     if (lc_sort_name == NULL)
4027         return;
4028 
4029     for (i = 0; panel_fields[i].id != NULL; i++)
4030     {
4031         const char *title;
4032 
4033         title = panel_get_title_without_hotkey (panel_fields[i].title_hotkey);
4034         if (panel_fields[i].sort_routine != NULL && strcmp (title, lc_sort_name) == 0)
4035         {
4036             col_sort_format = &panel_fields[i];
4037             break;
4038         }
4039     }
4040 
4041     if (col_sort_format != NULL)
4042     {
4043         if (panel->sort_field == col_sort_format)
4044             /* reverse the sort if clicked column is already the sorted column */
4045             panel->sort_info.reverse = !panel->sort_info.reverse;
4046         else
4047             /* new sort is forced to be ascending */
4048             panel->sort_info.reverse = FALSE;
4049 
4050         panel_set_sort_order (panel, col_sort_format);
4051     }
4052 }
4053 
4054 /* --------------------------------------------------------------------------------------------- */
4055 
4056 static int
4057 panel_mouse_is_on_item (const WPanel *panel, int y, int x)
     /* [previous][next][first][last][top][bottom][index][help]  */
4058 {
4059     int lines, col_width, col;
4060 
4061     if (y < 0)
4062         return MOUSE_UPPER_FILE_LIST;
4063 
4064     lines = panel_lines (panel);
4065     if (y >= lines)
4066         return MOUSE_BELOW_FILE_LIST;
4067 
4068     col_width = (CONST_WIDGET (panel)->rect.cols - 2) / panel->list_cols;
4069     /* column where mouse is */
4070     col = x / col_width;
4071 
4072     y += panel->top + lines * col;
4073 
4074     /* are we below or in the next column of last file? */
4075     if (y > panel->dir.len)
4076         return MOUSE_AFTER_LAST_FILE;
4077 
4078     /* we are on item of the file file; return an index to select a file */
4079     return y;
4080 }
4081 
4082 /* --------------------------------------------------------------------------------------------- */
4083 
4084 static void
4085 panel_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
4086 {
4087     WPanel *panel = PANEL (w);
4088     gboolean is_active;
4089 
4090     is_active = widget_is_active (w);
4091 
4092     switch (msg)
4093     {
4094     case MSG_MOUSE_DOWN:
4095         if (event->y == 0)
4096         {
4097             /* top frame */
4098             if (event->x == 1)
4099                 /* "<" button */
4100                 directory_history_prev (panel);
4101             else if (event->x == w->rect.cols - 2)
4102                 /* ">" button */
4103                 directory_history_next (panel);
4104             else if (event->x >= w->rect.cols - 5 && event->x <= w->rect.cols - 3)
4105                 /* "^" button */
4106                 directory_history_list (panel);
4107             else if (event->x == w->rect.cols - 6)
4108                 /* "." button show/hide hidden files */
4109                 send_message (filemanager, NULL, MSG_ACTION, CK_ShowHidden, NULL);
4110             else
4111             {
4112                 /* no other events on 1st line, return MOU_UNHANDLED */
4113                 event->result.abort = TRUE;
4114                 /* avoid extra panel redraw */
4115                 panel->dirty = FALSE;
4116             }
4117             break;
4118         }
4119 
4120         if (event->y == 1)
4121         {
4122             /* sort on clicked column */
4123             mouse_sort_col (panel, event->x + 1);
4124             break;
4125         }
4126 
4127         if (!is_active)
4128             (void) change_panel ();
4129         MC_FALLTHROUGH;
4130 
4131     case MSG_MOUSE_DRAG:
4132         {
4133             int my_index;
4134             int previous_current;
4135 
4136             my_index = panel_mouse_is_on_item (panel, event->y - 2, event->x);
4137             previous_current = panel->current;
4138 
4139             switch (my_index)
4140             {
4141             case MOUSE_UPPER_FILE_LIST:
4142                 move_up (panel);
4143                 mark_if_marking (panel, event, previous_current);
4144                 break;
4145 
4146             case MOUSE_BELOW_FILE_LIST:
4147                 move_down (panel);
4148                 mark_if_marking (panel, event, previous_current);
4149                 break;
4150 
4151             case MOUSE_AFTER_LAST_FILE:
4152                 break;          /* do nothing */
4153 
4154             default:
4155                 if (my_index != panel->current)
4156                 {
4157                     unselect_item (panel);
4158                     panel->current = my_index;
4159                     select_item (panel);
4160                 }
4161 
4162                 mark_if_marking (panel, event, previous_current);
4163                 break;
4164             }
4165         }
4166         break;
4167 
4168     case MSG_MOUSE_UP:
4169         break;
4170 
4171     case MSG_MOUSE_CLICK:
4172         if ((event->count & GPM_DOUBLE) != 0 && (event->buttons & GPM_B_LEFT) != 0 &&
4173             panel_mouse_is_on_item (panel, event->y - 2, event->x) >= 0)
4174             do_enter (panel);
4175         break;
4176 
4177     case MSG_MOUSE_MOVE:
4178         break;
4179 
4180     case MSG_MOUSE_SCROLL_UP:
4181         if (is_active)
4182         {
4183             if (panels_options.mouse_move_pages && panel->top > 0)
4184                 prev_page (panel);
4185             else                /* We are in first page */
4186                 move_up (panel);
4187         }
4188         break;
4189 
4190     case MSG_MOUSE_SCROLL_DOWN:
4191         if (is_active)
4192         {
4193             if (panels_options.mouse_move_pages
4194                 && panel->top + panel_items (panel) < panel->dir.len)
4195                 next_page (panel);
4196             else                /* We are in last page */
4197                 move_down (panel);
4198         }
4199         break;
4200 
4201     default:
4202         break;
4203     }
4204 
4205     if (panel->dirty)
4206         widget_draw (w);
4207 }
4208 
4209 /* --------------------------------------------------------------------------------------------- */
4210 
4211 static void
4212 reload_panelized (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4213 {
4214     int i, j;
4215     dir_list *list = &panel->dir;
4216 
4217     /* refresh current VFS directory required for vfs_path_from_str() */
4218     (void) mc_chdir (panel->cwd_vpath);
4219 
4220     for (i = 0, j = 0; i < list->len; i++)
4221     {
4222         vfs_path_t *vpath;
4223 
4224         vpath = vfs_path_from_str (list->list[i].fname->str);
4225         if (mc_lstat (vpath, &list->list[i].st) != 0)
4226             g_string_free (list->list[i].fname, TRUE);
4227         else
4228         {
4229             if (j != i)
4230                 list->list[j] = list->list[i];
4231             j++;
4232         }
4233         vfs_path_free (vpath, TRUE);
4234     }
4235     if (j == 0)
4236         dir_list_init (list);
4237     else
4238         list->len = j;
4239 
4240     recalculate_panel_summary (panel);
4241 
4242     if (panel != current_panel)
4243         (void) mc_chdir (current_panel->cwd_vpath);
4244 }
4245 
4246 /* --------------------------------------------------------------------------------------------- */
4247 
4248 static void
4249 update_one_panel_widget (WPanel *panel, panel_update_flags_t flags, const char *current_file)
     /* [previous][next][first][last][top][bottom][index][help]  */
4250 {
4251     gboolean free_pointer;
4252     char *my_current_file = NULL;
4253 
4254     if ((flags & UP_RELOAD) != 0)
4255     {
4256         panel->is_panelized = FALSE;
4257         mc_setctl (panel->cwd_vpath, VFS_SETCTL_FLUSH, NULL);
4258         memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
4259     }
4260 
4261     /* If current_file == -1 (an invalid pointer) then preserve current */
4262     free_pointer = current_file == UP_KEEPSEL;
4263 
4264     if (free_pointer)
4265     {
4266         const file_entry_t *fe;
4267 
4268         fe = panel_current_entry (panel);
4269         if (fe != NULL)
4270             my_current_file = g_strndup (fe->fname->str, fe->fname->len);
4271         current_file = my_current_file;
4272     }
4273 
4274     if (panel->is_panelized)
4275         reload_panelized (panel);
4276     else
4277         panel_reload (panel);
4278 
4279     panel_set_current_by_name (panel, current_file);
4280     panel->dirty = TRUE;
4281 
4282     if (free_pointer)
4283         g_free (my_current_file);
4284 }
4285 
4286 /* --------------------------------------------------------------------------------------------- */
4287 
4288 static void
4289 update_one_panel (int which, panel_update_flags_t flags, const char *current_file)
     /* [previous][next][first][last][top][bottom][index][help]  */
4290 {
4291     if (get_panel_type (which) == view_listing)
4292     {
4293         WPanel *panel;
4294 
4295         panel = PANEL (get_panel_widget (which));
4296         if (panel->is_panelized)
4297             flags &= ~UP_RELOAD;
4298         update_one_panel_widget (panel, flags, current_file);
4299     }
4300 }
4301 
4302 /* --------------------------------------------------------------------------------------------- */
4303 
4304 /* event callback */
4305 static gboolean
4306 event_update_panels (const gchar *event_group_name, const gchar *event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
4307                      gpointer init_data, gpointer data)
4308 {
4309     (void) event_group_name;
4310     (void) event_name;
4311     (void) init_data;
4312     (void) data;
4313 
4314     update_panels (UP_RELOAD, UP_KEEPSEL);
4315 
4316     return TRUE;
4317 }
4318 
4319 /* --------------------------------------------------------------------------------------------- */
4320 
4321 /* event callback */
4322 static gboolean
4323 panel_save_current_file_to_clip_file (const gchar *event_group_name, const gchar *event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
4324                                       gpointer init_data, gpointer data)
4325 {
4326     (void) event_group_name;
4327     (void) event_name;
4328     (void) init_data;
4329     (void) data;
4330 
4331     if (current_panel->marked == 0)
4332     {
4333         const file_entry_t *fe;
4334 
4335         fe = panel_current_entry (current_panel);
4336         if (fe != NULL)
4337             mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", (gpointer) fe->fname->str);
4338     }
4339     else
4340     {
4341         int i;
4342         gboolean first = TRUE;
4343         char *flist = NULL;
4344 
4345         for (i = 0; i < current_panel->dir.len; i++)
4346         {
4347             const file_entry_t *fe = &current_panel->dir.list[i];
4348 
4349             if (fe->f.marked != 0)
4350             {                   /* Skip the unmarked ones */
4351                 if (first)
4352                 {
4353                     flist = g_strndup (fe->fname->str, fe->fname->len);
4354                     first = FALSE;
4355                 }
4356                 else
4357                 {
4358                     /* Add empty lines after the file */
4359                     char *tmp;
4360 
4361                     tmp = g_strconcat (flist, "\n", fe->fname->str, (char *) NULL);
4362                     g_free (flist);
4363                     flist = tmp;
4364                 }
4365             }
4366         }
4367 
4368         mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", (gpointer) flist);
4369         g_free (flist);
4370     }
4371     return TRUE;
4372 }
4373 
4374 /* --------------------------------------------------------------------------------------------- */
4375 
4376 static vfs_path_t *
4377 panel_recursive_cd_to_parent (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
4378 {
4379     vfs_path_t *cwd_vpath;
4380 
4381     cwd_vpath = vfs_path_clone (vpath);
4382 
4383     while (mc_chdir (cwd_vpath) < 0)
4384     {
4385         const char *panel_cwd_path;
4386         vfs_path_t *tmp_vpath;
4387 
4388         /* check if path contains only '/' */
4389         panel_cwd_path = vfs_path_as_str (cwd_vpath);
4390         if (panel_cwd_path != NULL && IS_PATH_SEP (panel_cwd_path[0]) && panel_cwd_path[1] == '\0')
4391         {
4392             vfs_path_free (cwd_vpath, TRUE);
4393             return NULL;
4394         }
4395 
4396         tmp_vpath = vfs_path_vtokens_get (cwd_vpath, 0, -1);
4397         vfs_path_free (cwd_vpath, TRUE);
4398         cwd_vpath =
4399             vfs_path_build_filename (PATH_SEP_STR, vfs_path_as_str (tmp_vpath), (char *) NULL);
4400         vfs_path_free (tmp_vpath, TRUE);
4401     }
4402 
4403     return cwd_vpath;
4404 }
4405 
4406 /* --------------------------------------------------------------------------------------------- */
4407 
4408 static void
4409 panel_dir_list_callback (dir_list_cb_state_t state, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
4410 {
4411     static int count = 0;
4412 
4413     (void) data;
4414 
4415     switch (state)
4416     {
4417     case DIR_OPEN:
4418         count = 0;
4419         break;
4420 
4421     case DIR_READ:
4422         count++;
4423         if ((count & 15) == 0)
4424             rotate_dash (TRUE);
4425         break;
4426 
4427     case DIR_CLOSE:
4428         rotate_dash (FALSE);
4429         break;
4430 
4431     default:
4432         g_assert_not_reached ();
4433     }
4434 }
4435 
4436 /* --------------------------------------------------------------------------------------------- */
4437 /*** public functions ****************************************************************************/
4438 /* --------------------------------------------------------------------------------------------- */
4439 
4440 file_entry_t *
4441 panel_current_entry (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4442 {
4443     file_entry_t *fe;
4444 
4445     if (panel->dir.len == 0 || panel->current < 0 || panel->current >= panel->dir.len)
4446         return NULL;
4447 
4448     fe = &(panel->dir.list[panel->current]);
4449 
4450     return fe->fname == NULL ? NULL : fe;
4451 }
4452 
4453 /* --------------------------------------------------------------------------------------------- */
4454 
4455 void
4456 panel_set_current_by_name (WPanel *panel, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
4457 {
4458     int i;
4459     char *subdir;
4460 
4461     if (panel->dir.len == 0)
4462     {
4463         panel_set_current (panel, -1);
4464         return;
4465     }
4466 
4467     if (name == NULL)
4468     {
4469         panel_set_current (panel, 0);
4470         return;
4471     }
4472 
4473     /* We only want the last component of the directory,
4474      * and from this only the name without suffix.
4475      * Cut prefix if the panel is not panelized */
4476     if (panel->is_panelized)
4477         subdir = vfs_strip_suffix_from_filename (name);
4478     else
4479         subdir = vfs_strip_suffix_from_filename (x_basename (name));
4480 
4481     /* Search that subdir or filename without prefix (if not panelized panel),
4482        make it current if found */
4483     for (i = 0; i < panel->dir.len; i++)
4484         if (strcmp (subdir, panel->dir.list[i].fname->str) == 0)
4485         {
4486             panel_set_current (panel, i);
4487             g_free (subdir);
4488             return;
4489         }
4490 
4491     /* Make current near the filee that is missing */
4492     if (panel->current >= panel->dir.len)
4493         panel_set_current (panel, panel->dir.len - 1);
4494     g_free (subdir);
4495 
4496     select_item (panel);
4497 }
4498 
4499 /* --------------------------------------------------------------------------------------------- */
4500 
4501 void
4502 panel_clean_dir (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4503 {
4504     panel->top = 0;
4505     panel->current = 0;
4506     panel->marked = 0;
4507     panel->dirs_marked = 0;
4508     panel->total = 0;
4509     panel->quick_search.active = FALSE;
4510     panel->is_panelized = FALSE;
4511     panel->dirty = TRUE;
4512     panel->content_shift = -1;
4513     panel->max_shift = -1;
4514 
4515     dir_list_free_list (&panel->dir);
4516 }
4517 
4518 /* --------------------------------------------------------------------------------------------- */
4519 /**
4520  * Set Up panel's current dir object
4521  *
4522  * @param panel panel object
4523  * @param path_str string contain path
4524  */
4525 
4526 void
4527 panel_set_cwd (WPanel *panel, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
4528 {
4529     if (vpath != panel->cwd_vpath)      /* check if new vpath is not the panel->cwd_vpath object */
4530     {
4531         vfs_path_free (panel->cwd_vpath, TRUE);
4532         panel->cwd_vpath = vfs_path_clone (vpath);
4533     }
4534 }
4535 
4536 /* --------------------------------------------------------------------------------------------- */
4537 /**
4538  * Set Up panel's last working dir object
4539  *
4540  * @param panel panel object
4541  * @param path_str string contain path
4542  */
4543 
4544 void
4545 panel_set_lwd (WPanel *panel, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
4546 {
4547     if (vpath != panel->lwd_vpath)      /* check if new vpath is not the panel->lwd_vpath object */
4548     {
4549         vfs_path_free (panel->lwd_vpath, TRUE);
4550         panel->lwd_vpath = vfs_path_clone (vpath);
4551     }
4552 }
4553 
4554 /* --------------------------------------------------------------------------------------------- */
4555 /**
4556  * Creatie an empty panel with specified size.
4557  *
4558  * @param panel_name name of panel for setup receiving
4559  * @param r panel areaa
4560  *
4561  * @return new instance of WPanel
4562  */
4563 
4564 WPanel *
4565 panel_sized_empty_new (const char *panel_name, const WRect *r)
     /* [previous][next][first][last][top][bottom][index][help]  */
4566 {
4567     WPanel *panel;
4568     Widget *w;
4569     char *section;
4570     int i, err;
4571 
4572     panel = g_new0 (WPanel, 1);
4573     w = WIDGET (panel);
4574     widget_init (w, r, panel_callback, panel_mouse_callback);
4575     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
4576     w->keymap = panel_map;
4577 
4578     panel->dir.size = DIR_LIST_MIN_SIZE;
4579     panel->dir.list = g_new (file_entry_t, panel->dir.size);
4580     panel->dir.len = 0;
4581     panel->dir.callback = panel_dir_list_callback;
4582 
4583     panel->list_cols = 1;
4584     panel->brief_cols = 2;
4585     panel->dirty = TRUE;
4586     panel->content_shift = -1;
4587     panel->max_shift = -1;
4588 
4589     panel->list_format = list_full;
4590     panel->user_format = g_strdup (DEFAULT_USER_FORMAT);
4591 
4592     panel->filter.flags = FILE_FILTER_DEFAULT_FLAGS;
4593 
4594     for (i = 0; i < LIST_FORMATS; i++)
4595         panel->user_status_format[i] = g_strdup (DEFAULT_USER_FORMAT);
4596 
4597 #ifdef HAVE_CHARSET
4598     panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
4599 #endif
4600 
4601     panel->frame_size = frame_half;
4602 
4603     panel->quick_search.buffer = g_string_sized_new (MC_MAXFILENAMELEN);
4604     panel->quick_search.prev_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
4605 
4606     panel->name = g_strdup (panel_name);
4607     panel->dir_history.name = g_strconcat ("Dir Hist ", panel->name, (char *) NULL);
4608     /* directories history will be get later */
4609 
4610     section = g_strconcat ("Temporal:", panel->name, (char *) NULL);
4611     if (!mc_config_has_group (mc_global.main_config, section))
4612     {
4613         g_free (section);
4614         section = g_strdup (panel->name);
4615     }
4616     panel_load_setup (panel, section);
4617     g_free (section);
4618 
4619     if (panel->filter.value != NULL)
4620     {
4621         gboolean case_sens = (panel->filter.flags & SELECT_MATCH_CASE) != 0;
4622         gboolean shell_patterns = (panel->filter.flags & SELECT_SHELL_PATTERNS) != 0;
4623 
4624         panel->filter.handler = mc_search_new (panel->filter.value, NULL);
4625         panel->filter.handler->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
4626         panel->filter.handler->is_entire_line = TRUE;
4627         panel->filter.handler->is_case_sensitive = case_sens;
4628 
4629         /* FIXME: silent check -- do not display an error message */
4630         if (!mc_search_prepare (panel->filter.handler))
4631             file_filter_clear (&panel->filter);
4632     }
4633 
4634     /* Load format strings */
4635     err = set_panel_formats (panel);
4636     if (err != 0)
4637         set_panel_formats (panel);
4638 
4639     return panel;
4640 }
4641 
4642 /* --------------------------------------------------------------------------------------------- */
4643 /**
4644  * Panel creation for specified size and directory.
4645  *
4646  * @param panel_name name of panel for setup retrieving
4647  * @param r panel areaa
4648  * @param vpath working panel directory. If NULL then current directory is used
4649  *
4650  * @return new instance of WPanel
4651  */
4652 
4653 WPanel *
4654 panel_sized_with_dir_new (const char *panel_name, const WRect *r, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
4655 {
4656     WPanel *panel;
4657     char *curdir = NULL;
4658 #ifdef HAVE_CHARSET
4659     const vfs_path_element_t *path_element;
4660 #endif
4661 
4662     panel = panel_sized_empty_new (panel_name, r);
4663 
4664     if (vpath != NULL)
4665     {
4666         curdir = vfs_get_cwd ();
4667         panel_set_cwd (panel, vpath);
4668     }
4669     else
4670     {
4671         vfs_setup_cwd ();
4672         panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
4673     }
4674 
4675     panel_set_lwd (panel, vfs_get_raw_current_dir ());
4676 
4677 #ifdef HAVE_CHARSET
4678     path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
4679     if (path_element->encoding != NULL)
4680         panel->codepage = get_codepage_index (path_element->encoding);
4681 #endif
4682 
4683     if (mc_chdir (panel->cwd_vpath) != 0)
4684     {
4685 #ifdef HAVE_CHARSET
4686         panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
4687 #endif
4688         vfs_setup_cwd ();
4689         vfs_path_free (panel->cwd_vpath, TRUE);
4690         panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
4691     }
4692 
4693     /* Load the default format */
4694     if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
4695                         &panel->sort_info, &panel->filter))
4696         message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
4697 
4698     if (panel->dir.len == 0)
4699         panel_set_current (panel, -1);
4700 
4701     /* Restore old right path */
4702     if (curdir != NULL)
4703     {
4704         vfs_path_t *tmp_vpath;
4705         int err;
4706 
4707         tmp_vpath = vfs_path_from_str (curdir);
4708         mc_chdir (tmp_vpath);
4709         vfs_path_free (tmp_vpath, TRUE);
4710         (void) err;
4711     }
4712     g_free (curdir);
4713 
4714     return panel;
4715 }
4716 
4717 /* --------------------------------------------------------------------------------------------- */
4718 
4719 void
4720 panel_reload (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4721 {
4722     struct stat current_stat;
4723     vfs_path_t *cwd_vpath;
4724 
4725     if (panels_options.fast_reload && stat (vfs_path_as_str (panel->cwd_vpath), &current_stat) == 0
4726         && current_stat.st_ctime == panel->dir_stat.st_ctime
4727         && current_stat.st_mtime == panel->dir_stat.st_mtime)
4728         return;
4729 
4730     cwd_vpath = panel_recursive_cd_to_parent (panel->cwd_vpath);
4731     vfs_path_free (panel->cwd_vpath, TRUE);
4732 
4733     if (cwd_vpath == NULL)
4734     {
4735         panel->cwd_vpath = vfs_path_from_str (PATH_SEP_STR);
4736         panel_clean_dir (panel);
4737         dir_list_init (&panel->dir);
4738         return;
4739     }
4740 
4741     panel->cwd_vpath = cwd_vpath;
4742     memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
4743     show_dir (panel);
4744 
4745     if (!dir_list_reload (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
4746                           &panel->sort_info, &panel->filter))
4747         message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
4748 
4749     panel->dirty = TRUE;
4750 
4751     if (panel->dir.len == 0)
4752         panel_set_current (panel, -1);
4753     else if (panel->current >= panel->dir.len)
4754         panel_set_current (panel, panel->dir.len - 1);
4755 
4756     recalculate_panel_summary (panel);
4757 }
4758 
4759 /* --------------------------------------------------------------------------------------------- */
4760 /* Switches the panel to the mode specified in the format           */
4761 /* Setting up both format and status string. Return: 0 - on success; */
4762 /* 1 - format error; 2 - status error; 3 - errors in both formats.  */
4763 
4764 int
4765 set_panel_formats (WPanel *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
4766 {
4767     GSList *form;
4768     char *err = NULL;
4769     int retcode = 0;
4770 
4771     form = use_display_format (p, panel_format (p), &err, FALSE);
4772 
4773     if (err != NULL)
4774     {
4775         g_free (err);
4776         retcode = 1;
4777     }
4778     else
4779     {
4780         g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
4781         p->format = form;
4782     }
4783 
4784     if (panels_options.show_mini_info)
4785     {
4786         form = use_display_format (p, mini_status_format (p), &err, TRUE);
4787 
4788         if (err != NULL)
4789         {
4790             g_free (err);
4791             retcode += 2;
4792         }
4793         else
4794         {
4795             g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
4796             p->status_format = form;
4797         }
4798     }
4799 
4800     panel_update_cols (WIDGET (p), p->frame_size);
4801 
4802     if (retcode != 0)
4803         message (D_ERROR, _("Warning"),
4804                  _("User supplied format looks invalid, reverting to default."));
4805     if ((retcode & 0x01) != 0)
4806     {
4807         g_free (p->user_format);
4808         p->user_format = g_strdup (DEFAULT_USER_FORMAT);
4809     }
4810     if ((retcode & 0x02) != 0)
4811     {
4812         g_free (p->user_status_format[p->list_format]);
4813         p->user_status_format[p->list_format] = g_strdup (DEFAULT_USER_FORMAT);
4814     }
4815 
4816     return retcode;
4817 }
4818 
4819 /* --------------------------------------------------------------------------------------------- */
4820 
4821 void
4822 panel_set_filter (WPanel *panel, const file_filter_t *filter)
     /* [previous][next][first][last][top][bottom][index][help]  */
4823 {
4824     MC_PTR_FREE (panel->filter.value);
4825     mc_search_free (panel->filter.handler);
4826     panel->filter.handler = NULL;
4827 
4828     /* NULL to clear filter */
4829     if (filter != NULL)
4830         panel->filter = *filter;
4831 
4832     reread_cmd ();
4833 }
4834 
4835 /* --------------------------------------------------------------------------------------------- */
4836 
4837 /* Select current item and readjust the panel */
4838 void
4839 select_item (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4840 {
4841     adjust_top_file (panel);
4842 
4843     panel->dirty = TRUE;
4844 
4845     execute_hooks (select_file_hook);
4846 }
4847 
4848 /* --------------------------------------------------------------------------------------------- */
4849 /** Clears all files in the panel, used only when one file was marked */
4850 void
4851 unmark_files (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4852 {
4853     if (panel->marked != 0)
4854     {
4855         int i;
4856 
4857         for (i = 0; i < panel->dir.len; i++)
4858             file_mark (panel, i, 0);
4859 
4860         panel->dirs_marked = 0;
4861         panel->marked = 0;
4862         panel->total = 0;
4863     }
4864 }
4865 
4866 /* --------------------------------------------------------------------------------------------- */
4867 /** Recalculate the panels summary information, used e.g. when marked
4868    files might have been removed by an external command */
4869 
4870 void
4871 recalculate_panel_summary (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
4872 {
4873     int i;
4874 
4875     panel->marked = 0;
4876     panel->dirs_marked = 0;
4877     panel->total = 0;
4878 
4879     for (i = 0; i < panel->dir.len; i++)
4880         if (panel->dir.list[i].f.marked != 0)
4881         {
4882             /* do_file_mark will return immediately if newmark == oldmark.
4883                So we have to first unmark it to get panel's summary information
4884                updated. (Norbert) */
4885             panel->dir.list[i].f.marked = 0;
4886             do_file_mark (panel, i, 1);
4887         }
4888 }
4889 
4890 /* --------------------------------------------------------------------------------------------- */
4891 /** This routine marks a file or a directory */
4892 
4893 void
4894 do_file_mark (WPanel *panel, int idx, int mark)
     /* [previous][next][first][last][top][bottom][index][help]  */
4895 {
4896     if (panel->dir.list[idx].f.marked == mark)
4897         return;
4898 
4899     /* Only '..' can't be marked, '.' isn't visible */
4900     if (DIR_IS_DOTDOT (panel->dir.list[idx].fname->str))
4901         return;
4902 
4903     file_mark (panel, idx, mark);
4904     if (panel->dir.list[idx].f.marked != 0)
4905     {
4906         panel->marked++;
4907 
4908         if (S_ISDIR (panel->dir.list[idx].st.st_mode))
4909         {
4910             if (panel->dir.list[idx].f.dir_size_computed != 0)
4911                 panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
4912             panel->dirs_marked++;
4913         }
4914         else
4915             panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
4916 
4917         set_colors (panel);
4918     }
4919     else
4920     {
4921         if (S_ISDIR (panel->dir.list[idx].st.st_mode))
4922         {
4923             if (panel->dir.list[idx].f.dir_size_computed != 0)
4924                 panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
4925             panel->dirs_marked--;
4926         }
4927         else
4928             panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
4929 
4930         panel->marked--;
4931     }
4932 }
4933 
4934 /* --------------------------------------------------------------------------------------------- */
4935 /**
4936  * Changes the current directory of the panel.
4937  * Record change in the directory history.
4938  */
4939 gboolean
4940 panel_do_cd (WPanel *panel, const vfs_path_t *new_dir_vpath, enum cd_enum cd_type)
     /* [previous][next][first][last][top][bottom][index][help]  */
4941 {
4942     gboolean r;
4943 
4944     r = panel_do_cd_int (panel, new_dir_vpath, cd_type);
4945     if (r)
4946         directory_history_add (panel, panel->cwd_vpath);
4947     return r;
4948 }
4949 
4950 /* --------------------------------------------------------------------------------------------- */
4951 
4952 void
4953 file_mark (WPanel *panel, int lc_index, int val)
     /* [previous][next][first][last][top][bottom][index][help]  */
4954 {
4955     if (panel->dir.list[lc_index].f.marked != val)
4956     {
4957         panel->dir.list[lc_index].f.marked = val;
4958         panel->dirty = TRUE;
4959     }
4960 }
4961 
4962 /* --------------------------------------------------------------------------------------------- */
4963 /**
4964  * Find marked file starting from the given position.
4965  *
4966  * @param panel WPanel object
4967  * @param curent_file a staring position to get current or search next marked file
4968  *
4969  * @return pointer to the name of find file or NULL if no file found or @current_file is out of range
4970  */
4971 
4972 const GString *
4973 panel_find_marked_file (const WPanel *panel, int *current_file)
     /* [previous][next][first][last][top][bottom][index][help]  */
4974 {
4975     while (panel->dir.list[*current_file].f.marked == 0 && *current_file < panel->dir.len)
4976         (*current_file)++;
4977 
4978     return (*current_file >= panel->dir.len ? NULL : panel->dir.list[*current_file].fname);
4979 }
4980 
4981 /* --------------------------------------------------------------------------------------------- */
4982 /**
4983  * Get marked file clsest to the given position.
4984  *
4985  * @param panel WPanel object
4986  * @param curent_file a staring position to get current or closest next marked file. If there are
4987  *                    no marked files in @panel, @panel->current is used.
4988  *
4989  * @return pointer to the name of find file or NULL if no file found or @current_file is out of range.
4990  */
4991 
4992 const GString *
4993 panel_get_marked_file (const WPanel *panel, int *current_file)
     /* [previous][next][first][last][top][bottom][index][help]  */
4994 {
4995     const file_entry_t *fe;
4996 
4997     if (panel->marked != 0)
4998         return panel_find_marked_file (panel, current_file);
4999 
5000     fe = panel_current_entry (panel);
5001 
5002     return (fe == NULL ? NULL : fe->fname);
5003 }
5004 
5005 /* --------------------------------------------------------------------------------------------- */
5006 
5007 void
5008 panel_re_sort (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
5009 {
5010     char *filename;
5011     const file_entry_t *fe;
5012     int i;
5013 
5014     if (panel == NULL)
5015         return;
5016 
5017     fe = panel_current_entry (panel);
5018     if (fe == NULL)
5019         return;
5020 
5021     filename = g_strndup (fe->fname->str, fe->fname->len);
5022     unselect_item (panel);
5023     dir_list_sort (&panel->dir, panel->sort_field->sort_routine, &panel->sort_info);
5024     panel->current = -1;
5025 
5026     for (i = panel->dir.len; i != 0; i--)
5027         if (strcmp (panel->dir.list[i - 1].fname->str, filename) == 0)
5028         {
5029             panel->current = i - 1;
5030             break;
5031         }
5032 
5033     g_free (filename);
5034     panel->top = panel->current - panel_items (panel) / 2;
5035     select_item (panel);
5036     panel->dirty = TRUE;
5037 }
5038 
5039 /* --------------------------------------------------------------------------------------------- */
5040 
5041 void
5042 panel_set_sort_order (WPanel *panel, const panel_field_t *sort_order)
     /* [previous][next][first][last][top][bottom][index][help]  */
5043 {
5044     if (sort_order == NULL)
5045         return;
5046 
5047     panel->sort_field = sort_order;
5048 
5049     /* The directory is already sorted, we have to load the unsorted stuff */
5050     if (sort_order->sort_routine == (GCompareFunc) unsorted)
5051     {
5052         const file_entry_t *fe;
5053         char *current_file = NULL;
5054 
5055         fe = panel_current_entry (panel);
5056         if (fe != NULL)
5057             current_file = g_strndup (fe->fname->str, fe->fname->len);
5058         panel_reload (panel);
5059         panel_set_current_by_name (panel, current_file);
5060         g_free (current_file);
5061     }
5062     panel_re_sort (panel);
5063 }
5064 
5065 /* --------------------------------------------------------------------------------------------- */
5066 
5067 #ifdef HAVE_CHARSET
5068 
5069 /**
5070  * Change panel encoding.
5071  * @param panel WPanel object
5072  */
5073 
5074 void
5075 panel_change_encoding (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
5076 {
5077     const char *encoding = NULL;
5078     char *errmsg;
5079     int r;
5080 
5081     r = select_charset (-1, -1, panel->codepage, FALSE);
5082 
5083     if (r == SELECT_CHARSET_CANCEL)
5084         return;                 /* Cancel */
5085 
5086     panel->codepage = r;
5087 
5088     if (panel->codepage == SELECT_CHARSET_NO_TRANSLATE)
5089     {
5090         /* No translation */
5091         vfs_path_t *cd_path_vpath;
5092 
5093         g_free (init_translation_table (mc_global.display_codepage, mc_global.display_codepage));
5094         cd_path_vpath = remove_encoding_from_path (panel->cwd_vpath);
5095         panel_do_cd (panel, cd_path_vpath, cd_parse_command);
5096         show_dir (panel);
5097         vfs_path_free (cd_path_vpath, TRUE);
5098         return;
5099     }
5100 
5101     errmsg = init_translation_table (panel->codepage, mc_global.display_codepage);
5102     if (errmsg != NULL)
5103     {
5104         message (D_ERROR, MSG_ERROR, "%s", errmsg);
5105         g_free (errmsg);
5106         return;
5107     }
5108 
5109     encoding = get_codepage_id (panel->codepage);
5110     if (encoding != NULL)
5111     {
5112         vfs_path_change_encoding (panel->cwd_vpath, encoding);
5113 
5114         if (!panel_do_cd (panel, panel->cwd_vpath, cd_parse_command))
5115             cd_error_message (vfs_path_as_str (panel->cwd_vpath));
5116     }
5117 }
5118 
5119 /* --------------------------------------------------------------------------------------------- */
5120 
5121 /**
5122  * Remove encode info from last path element.
5123  *
5124  */
5125 vfs_path_t *
5126 remove_encoding_from_path (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
5127 {
5128     vfs_path_t *ret_vpath;
5129     GString *tmp_conv;
5130     int indx;
5131 
5132     ret_vpath = vfs_path_new (FALSE);
5133 
5134     tmp_conv = g_string_new ("");
5135 
5136     for (indx = 0; indx < vfs_path_elements_count (vpath); indx++)
5137     {
5138         GIConv converter;
5139         vfs_path_element_t *path_element;
5140 
5141         path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, indx));
5142 
5143         if (path_element->encoding == NULL)
5144         {
5145             vfs_path_add_element (ret_vpath, path_element);
5146             continue;
5147         }
5148 
5149         converter = str_crt_conv_to (path_element->encoding);
5150         if (converter == INVALID_CONV)
5151         {
5152             vfs_path_add_element (ret_vpath, path_element);
5153             continue;
5154         }
5155 
5156         MC_PTR_FREE (path_element->encoding);
5157 
5158         str_vfs_convert_from (converter, path_element->path, tmp_conv);
5159 
5160         g_free (path_element->path);
5161         path_element->path = g_strndup (tmp_conv->str, tmp_conv->len);
5162 
5163         g_string_set_size (tmp_conv, 0);
5164 
5165         str_close_conv (converter);
5166         str_close_conv (path_element->dir.converter);
5167         path_element->dir.converter = INVALID_CONV;
5168         vfs_path_add_element (ret_vpath, path_element);
5169     }
5170     g_string_free (tmp_conv, TRUE);
5171     return ret_vpath;
5172 }
5173 #endif /* HAVE_CHARSET */
5174 
5175 /* --------------------------------------------------------------------------------------------- */
5176 
5177 /**
5178  * This routine reloads the directory in both panels. It tries to
5179  * select current_file in current_panel and other_file in other_panel.
5180  * If current_file == -1 then it automatically sets current_file and
5181  * other_file to the current files in the panels.
5182  *
5183  * If flags has the UP_ONLY_CURRENT bit toggled on, then it
5184  * will not reload the other panel.
5185  *
5186  * @param flags for reload panel
5187  * @param current_file name of the current file
5188  */
5189 
5190 void
5191 update_panels (panel_update_flags_t flags, const char *current_file)
     /* [previous][next][first][last][top][bottom][index][help]  */
5192 {
5193     WPanel *panel;
5194 
5195     /* first, update other panel... */
5196     if ((flags & UP_ONLY_CURRENT) == 0)
5197         update_one_panel (get_other_index (), flags, UP_KEEPSEL);
5198     /* ...then current one */
5199     update_one_panel (get_current_index (), flags, current_file);
5200 
5201     if (get_current_type () == view_listing)
5202         panel = PANEL (get_panel_widget (get_current_index ()));
5203     else
5204         panel = PANEL (get_panel_widget (get_other_index ()));
5205 
5206     if (!panel->is_panelized)
5207         (void) mc_chdir (panel->cwd_vpath);
5208 }
5209 
5210 /* --------------------------------------------------------------------------------------------- */
5211 
5212 gsize
5213 panel_get_num_of_sortable_fields (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
5214 {
5215     gsize ret = 0, lc_index;
5216 
5217     for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
5218         if (panel_fields[lc_index].is_user_choice)
5219             ret++;
5220     return ret;
5221 }
5222 
5223 /* --------------------------------------------------------------------------------------------- */
5224 
5225 char **
5226 panel_get_sortable_fields (gsize *array_size)
     /* [previous][next][first][last][top][bottom][index][help]  */
5227 {
5228     char **ret;
5229     gsize lc_index, i;
5230 
5231     lc_index = panel_get_num_of_sortable_fields ();
5232 
5233     ret = g_try_new0 (char *, lc_index + 1);
5234     if (ret == NULL)
5235         return NULL;
5236 
5237     if (array_size != NULL)
5238         *array_size = lc_index;
5239 
5240     lc_index = 0;
5241 
5242     for (i = 0; panel_fields[i].id != NULL; i++)
5243         if (panel_fields[i].is_user_choice)
5244             ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
5245 
5246     return ret;
5247 }
5248 
5249 /* --------------------------------------------------------------------------------------------- */
5250 
5251 const panel_field_t *
5252 panel_get_field_by_id (const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
5253 {
5254     gsize lc_index;
5255 
5256     for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
5257         if (panel_fields[lc_index].id != NULL && strcmp (name, panel_fields[lc_index].id) == 0)
5258             return &panel_fields[lc_index];
5259 
5260     return NULL;
5261 }
5262 
5263 /* --------------------------------------------------------------------------------------------- */
5264 
5265 const panel_field_t *
5266 panel_get_field_by_title_hotkey (const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
5267 {
5268     gsize lc_index;
5269 
5270     for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
5271         if (panel_fields[lc_index].title_hotkey != NULL &&
5272             strcmp (name, _(panel_fields[lc_index].title_hotkey)) == 0)
5273             return &panel_fields[lc_index];
5274 
5275     return NULL;
5276 }
5277 
5278 /* --------------------------------------------------------------------------------------------- */
5279 
5280 const panel_field_t *
5281 panel_get_field_by_title (const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
5282 {
5283     gsize lc_index;
5284 
5285     for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
5286     {
5287         const char *title;
5288 
5289         title = panel_get_title_without_hotkey (panel_fields[lc_index].title_hotkey);
5290         if (strcmp (title, name) == 0)
5291             return &panel_fields[lc_index];
5292     }
5293 
5294     return NULL;
5295 }
5296 
5297 /* --------------------------------------------------------------------------------------------- */
5298 
5299 gsize
5300 panel_get_num_of_user_possible_fields (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
5301 {
5302     gsize ret = 0, lc_index;
5303 
5304     for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
5305         if (panel_fields[lc_index].use_in_user_format)
5306             ret++;
5307 
5308     return ret;
5309 }
5310 
5311 /* --------------------------------------------------------------------------------------------- */
5312 
5313 char **
5314 panel_get_user_possible_fields (gsize *array_size)
     /* [previous][next][first][last][top][bottom][index][help]  */
5315 {
5316     char **ret;
5317     gsize lc_index, i;
5318 
5319     lc_index = panel_get_num_of_user_possible_fields ();
5320 
5321     ret = g_try_new0 (char *, lc_index + 1);
5322     if (ret == NULL)
5323         return NULL;
5324 
5325     if (array_size != NULL)
5326         *array_size = lc_index;
5327 
5328     lc_index = 0;
5329 
5330     for (i = 0; panel_fields[i].id != NULL; i++)
5331         if (panel_fields[i].use_in_user_format)
5332             ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
5333 
5334     return ret;
5335 }
5336 
5337 /* --------------------------------------------------------------------------------------------- */
5338 
5339 void
5340 panel_panelize_cd (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
5341 {
5342     WPanel *panel;
5343     int i;
5344     dir_list *list;
5345     panelized_descr_t *pdescr;
5346     dir_list *plist;
5347     gboolean panelized_same;
5348 
5349     if (!SELECTED_IS_PANEL)
5350         create_panel (MENU_PANEL_IDX, view_listing);
5351 
5352     panel = PANEL (get_panel_widget (MENU_PANEL_IDX));
5353 
5354     dir_list_clean (&panel->dir);
5355 
5356     if (panel->panelized_descr == NULL)
5357         panel->panelized_descr = panelized_descr_new ();
5358 
5359     pdescr = panel->panelized_descr;
5360     plist = &pdescr->list;
5361 
5362     if (pdescr->root_vpath == NULL)
5363         panel_panelize_change_root (panel, panel->cwd_vpath);
5364 
5365     if (plist->len < 1)
5366         dir_list_init (plist);
5367     else if (plist->len > panel->dir.size)
5368         dir_list_grow (&panel->dir, plist->len - panel->dir.size);
5369 
5370     list = &panel->dir;
5371     list->len = plist->len;
5372 
5373     panelized_same = vfs_path_equal (pdescr->root_vpath, panel->cwd_vpath);
5374 
5375     for (i = 0; i < plist->len; i++)
5376     {
5377         if (panelized_same || DIR_IS_DOTDOT (plist->list[i].fname->str))
5378             list->list[i].fname = mc_g_string_dup (plist->list[i].fname);
5379         else
5380         {
5381             vfs_path_t *tmp_vpath;
5382 
5383             tmp_vpath =
5384                 vfs_path_append_new (pdescr->root_vpath, plist->list[i].fname->str, (char *) NULL);
5385             list->list[i].fname = g_string_new_take (vfs_path_free (tmp_vpath, FALSE));
5386         }
5387         list->list[i].f.link_to_dir = plist->list[i].f.link_to_dir;
5388         list->list[i].f.stale_link = plist->list[i].f.stale_link;
5389         list->list[i].f.dir_size_computed = plist->list[i].f.dir_size_computed;
5390         list->list[i].f.marked = plist->list[i].f.marked;
5391         list->list[i].st = plist->list[i].st;
5392         list->list[i].name_sort_key = plist->list[i].name_sort_key;
5393         list->list[i].extension_sort_key = plist->list[i].extension_sort_key;
5394     }
5395 
5396     panel->is_panelized = TRUE;
5397     panel_panelize_absolutize_if_needed (panel);
5398 
5399     panel_set_current_by_name (panel, NULL);
5400 }
5401 
5402 /* --------------------------------------------------------------------------------------------- */
5403 
5404 /**
5405  * Change root directory of panelized content.
5406  * @param panel file panel
5407  * @param new_root new path
5408  */
5409 void
5410 panel_panelize_change_root (WPanel *panel, const vfs_path_t *new_root)
     /* [previous][next][first][last][top][bottom][index][help]  */
5411 {
5412     if (panel->panelized_descr == NULL)
5413         panel->panelized_descr = panelized_descr_new ();
5414     else
5415         vfs_path_free (panel->panelized_descr->root_vpath, TRUE);
5416 
5417     panel->panelized_descr->root_vpath = vfs_path_clone (new_root);
5418 }
5419 
5420 /* --------------------------------------------------------------------------------------------- */
5421 
5422 /**
5423  * Conditionally switches a panel's directory to "/" (root).
5424  *
5425  * If a panelized panel's listing contain absolute paths, this function
5426  * sets the panel's directory to "/". Otherwise it does nothing.
5427  *
5428  * Rationale:
5429  *
5430  * This makes tokenized strings like "%d/%p" work. This also makes other
5431  * places work where such naive concatenation is done in code (e.g., when
5432  * pressing ctrl+shift+enter, for CK_PutCurrentFullSelected).
5433  *
5434  * When to call:
5435  *
5436  * You should always call this function after you populate the listing
5437  * of a panelized panel.
5438  */
5439 void
5440 panel_panelize_absolutize_if_needed (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
5441 {
5442     const dir_list *const list = &panel->dir;
5443 
5444     /* Note: We don't support mixing of absolute and relative paths, which is
5445      * why it's ok for us to check only the 1st entry. */
5446     if (list->len > 1 && g_path_is_absolute (list->list[1].fname->str))
5447     {
5448         vfs_path_t *root;
5449 
5450         root = vfs_path_from_str (PATH_SEP_STR);
5451         panel_set_cwd (panel, root);
5452         if (panel == current_panel)
5453             mc_chdir (root);
5454         vfs_path_free (root, TRUE);
5455     }
5456 }
5457 
5458 /* --------------------------------------------------------------------------------------------- */
5459 
5460 void
5461 panel_panelize_save (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
5462 {
5463     int i;
5464     dir_list *list = &panel->dir;
5465     dir_list *plist;
5466 
5467     panel_panelize_change_root (panel, panel->cwd_vpath);
5468 
5469     plist = &panel->panelized_descr->list;
5470 
5471     if (plist->len > 0)
5472         dir_list_clean (plist);
5473     if (panel->dir.len == 0)
5474         return;
5475 
5476     if (panel->dir.len > plist->size)
5477         dir_list_grow (plist, panel->dir.len - plist->size);
5478     plist->len = panel->dir.len;
5479 
5480     for (i = 0; i < panel->dir.len; i++)
5481     {
5482         plist->list[i].fname = mc_g_string_dup (list->list[i].fname);
5483         plist->list[i].f.link_to_dir = list->list[i].f.link_to_dir;
5484         plist->list[i].f.stale_link = list->list[i].f.stale_link;
5485         plist->list[i].f.dir_size_computed = list->list[i].f.dir_size_computed;
5486         plist->list[i].f.marked = list->list[i].f.marked;
5487         plist->list[i].st = list->list[i].st;
5488         plist->list[i].name_sort_key = list->list[i].name_sort_key;
5489         plist->list[i].extension_sort_key = list->list[i].extension_sort_key;
5490     }
5491 }
5492 
5493 /* --------------------------------------------------------------------------------------------- */
5494 
5495 void
5496 panel_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
5497 {
5498     panel_sort_up_char = mc_skin_get ("widget-panel", "sort-up-char", "'");
5499     panel_sort_down_char = mc_skin_get ("widget-panel", "sort-down-char", ".");
5500     panel_hiddenfiles_show_char = mc_skin_get ("widget-panel", "hiddenfiles-show-char", ".");
5501     panel_hiddenfiles_hide_char = mc_skin_get ("widget-panel", "hiddenfiles-hide-char", ".");
5502     panel_history_prev_item_char = mc_skin_get ("widget-panel", "history-prev-item-char", "<");
5503     panel_history_next_item_char = mc_skin_get ("widget-panel", "history-next-item-char", ">");
5504     panel_history_show_list_char = mc_skin_get ("widget-panel", "history-show-list-char", "^");
5505     panel_filename_scroll_left_char =
5506         mc_skin_get ("widget-panel", "filename-scroll-left-char", "{");
5507     panel_filename_scroll_right_char =
5508         mc_skin_get ("widget-panel", "filename-scroll-right-char", "}");
5509 
5510     string_file_name_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
5511 
5512     mc_event_add (MCEVENT_GROUP_FILEMANAGER, "update_panels", event_update_panels, NULL, NULL);
5513     mc_event_add (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file",
5514                   panel_save_current_file_to_clip_file, NULL, NULL);
5515 }
5516 
5517 /* --------------------------------------------------------------------------------------------- */
5518 
5519 void
5520 panel_deinit (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
5521 {
5522     g_free (panel_sort_up_char);
5523     g_free (panel_sort_down_char);
5524     g_free (panel_hiddenfiles_show_char);
5525     g_free (panel_hiddenfiles_hide_char);
5526     g_free (panel_history_prev_item_char);
5527     g_free (panel_history_next_item_char);
5528     g_free (panel_history_show_list_char);
5529     g_free (panel_filename_scroll_left_char);
5530     g_free (panel_filename_scroll_right_char);
5531     g_string_free (string_file_name_buffer, TRUE);
5532 }
5533 
5534 /* --------------------------------------------------------------------------------------------- */
5535 
5536 gboolean
5537 panel_cd (WPanel *panel, const vfs_path_t *new_dir_vpath, enum cd_enum exact)
     /* [previous][next][first][last][top][bottom][index][help]  */
5538 {
5539     gboolean res;
5540     const vfs_path_t *_new_dir_vpath = new_dir_vpath;
5541 
5542     if (panel->is_panelized)
5543     {
5544         size_t new_vpath_len;
5545 
5546         new_vpath_len = vfs_path_len (new_dir_vpath);
5547         if (vfs_path_equal_len (new_dir_vpath, panel->panelized_descr->root_vpath, new_vpath_len))
5548             _new_dir_vpath = panel->panelized_descr->root_vpath;
5549     }
5550 
5551     res = panel_do_cd (panel, _new_dir_vpath, exact);
5552 
5553 #ifdef HAVE_CHARSET
5554     if (res)
5555     {
5556         const vfs_path_element_t *path_element;
5557 
5558         path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
5559         if (path_element->encoding != NULL)
5560             panel->codepage = get_codepage_index (path_element->encoding);
5561         else
5562             panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
5563     }
5564 #endif /* HAVE_CHARSET */
5565 
5566     return res;
5567 }
5568 
5569 /* --------------------------------------------------------------------------------------------- */

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