Manual pages: mcmcdiffmceditmcview

root/src/filemanager/tree.c

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

DEFINITIONS

This source file includes following definitions.
  1. back_ptr
  2. forw_ptr
  3. remove_callback
  4. save_tree
  5. tree_remove_entry
  6. tree_destroy
  7. load_tree
  8. tree_show_mini_info
  9. show_tree
  10. tree_check_focus
  11. tree_move_backward
  12. tree_move_forward
  13. tree_move_to_child
  14. tree_move_to_parent
  15. tree_move_to_top
  16. tree_move_to_bottom
  17. tree_chdir_sel
  18. maybe_chdir
  19. search_tree
  20. tree_do_search
  21. tree_rescan
  22. tree_forget
  23. tree_copy
  24. tree_move
  25. tree_mkdir
  26. tree_rmdir
  27. tree_move_up
  28. tree_move_down
  29. tree_move_home
  30. tree_move_end
  31. tree_move_pgup
  32. tree_move_pgdn
  33. tree_move_left
  34. tree_move_right
  35. tree_start_search
  36. tree_toggle_navig
  37. tree_help
  38. tree_execute_cmd
  39. tree_key
  40. tree_frame
  41. tree_callback
  42. tree_mouse_callback
  43. tree_new
  44. tree_chdir
  45. tree_selected_name
  46. sync_tree
  47. find_tree

   1 /*
   2    Directory tree browser for the Midnight Commander
   3    This module has been converted to be a widget.
   4 
   5    The program load and saves the tree each time the tree widget is
   6    created and destroyed.  This is required for the future vfs layer,
   7    it will be possible to have tree views over virtual file systems.
   8 
   9    Copyright (C) 1994-2025
  10    Free Software Foundation, Inc.
  11 
  12    Written by:
  13    Janne Kukonlehto, 1994, 1996
  14    Norbert Warmuth, 1997
  15    Miguel de Icaza, 1996, 1999
  16    Slava Zanko <slavazanko@gmail.com>, 2013
  17    Andrew Borodin <aborodin@vmail.ru>, 2013-2022
  18 
  19    This file is part of the Midnight Commander.
  20 
  21    The Midnight Commander is free software: you can redistribute it
  22    and/or modify it under the terms of the GNU General Public License as
  23    published by the Free Software Foundation, either version 3 of the License,
  24    or (at your option) any later version.
  25 
  26    The Midnight Commander is distributed in the hope that it will be useful,
  27    but WITHOUT ANY WARRANTY; without even the implied warranty of
  28    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  29    GNU General Public License for more details.
  30 
  31    You should have received a copy of the GNU General Public License
  32    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  33  */
  34 
  35 /** \file tree.c
  36  *  \brief Source: directory tree browser
  37  */
  38 
  39 #include <config.h>
  40 
  41 #include <errno.h>
  42 #include <stdio.h>
  43 #include <string.h>
  44 #include <sys/types.h>
  45 
  46 #include "lib/global.h"
  47 
  48 #include "lib/tty/tty.h"
  49 #include "lib/tty/key.h"
  50 #include "lib/skin.h"
  51 #include "lib/vfs/vfs.h"
  52 #include "lib/fileloc.h"
  53 #include "lib/strutil.h"
  54 #include "lib/util.h"
  55 #include "lib/widget.h"
  56 #include "lib/event.h"  // mc_event_raise()
  57 
  58 #include "src/setup.h"  // confirm_delete, panels_options
  59 #include "src/keymap.h"
  60 #include "src/history.h"
  61 #include "src/util.h"  // file_error_message()
  62 
  63 #include "dir.h"
  64 #include "filemanager.h"  // the_menubar
  65 #include "file.h"         // copy_dir_dir(), move_dir_dir(), erase_dir()
  66 #include "layout.h"       // command_prompt
  67 #include "treestore.h"
  68 #include "cmd.h"
  69 #include "filegui.h"
  70 #include "cd.h"  // cd_error_message()
  71 
  72 #include "tree.h"
  73 
  74 /*** global variables ****************************************************************************/
  75 
  76 /* The pointer to the tree */
  77 WTree *the_tree = NULL;
  78 
  79 /* If this is true, then when browsing the tree the other window will
  80  * automatically reload it's directory with the contents of the currently
  81  * selected directory.
  82  */
  83 gboolean xtree_mode = FALSE;
  84 
  85 /*** file scope macro definitions ****************************************************************/
  86 
  87 #define tlines(t)                                                                                  \
  88     (t->is_panel ? WIDGET (t)->rect.lines - 2 - (panels_options.show_mini_info ? 2 : 0)            \
  89                  : WIDGET (t)->rect.lines)
  90 
  91 /*** file scope type declarations ****************************************************************/
  92 
  93 struct WTree
  94 {
  95     Widget widget;
  96     struct TreeStore *store;
  97     tree_entry *selected_ptr;  // The selected directory
  98     GString *search_buffer;    // Current search string
  99     tree_entry **tree_shown;   // Entries currently on screen
 100     gboolean is_panel;         // panel or plain widget flag
 101     gboolean searching;        // Are we on searching mode?
 102     int topdiff;               /* The difference between the topmost
 103                                   shown and the selected */
 104 };
 105 
 106 /*** forward declarations (file scope functions) *************************************************/
 107 
 108 static void tree_rescan (void *data);
 109 
 110 /*** file scope variables ************************************************************************/
 111 
 112 /* Specifies the display mode: 1d or 2d */
 113 static gboolean tree_navigation_flag = FALSE;
 114 
 115 /* --------------------------------------------------------------------------------------------- */
 116 /*** file scope functions ************************************************************************/
 117 /* --------------------------------------------------------------------------------------------- */
 118 
 119 static tree_entry *
 120 back_ptr (tree_entry *ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 121 {
 122     int i;
 123 
 124     for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++)
 125         ;
 126 
 127     *count = i;
 128     return ptr;
 129 }
 130 
 131 /* --------------------------------------------------------------------------------------------- */
 132 
 133 static tree_entry *
 134 forw_ptr (tree_entry *ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 135 {
 136     int i;
 137 
 138     for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++)
 139         ;
 140 
 141     *count = i;
 142     return ptr;
 143 }
 144 
 145 /* --------------------------------------------------------------------------------------------- */
 146 
 147 static void
 148 remove_callback (tree_entry *entry, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 149 {
 150     WTree *tree = data;
 151 
 152     if (tree->selected_ptr == entry)
 153     {
 154         if (tree->selected_ptr->next != NULL)
 155             tree->selected_ptr = tree->selected_ptr->next;
 156         else
 157             tree->selected_ptr = tree->selected_ptr->prev;
 158     }
 159 }
 160 
 161 /* --------------------------------------------------------------------------------------------- */
 162 /** Save the ${XDG_CACHE_HOME}/mc/Tree file */
 163 
 164 static void
 165 save_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 166 {
 167     int error;
 168 
 169     (void) tree;
 170 
 171     error = tree_store_save ();
 172     if (error != 0)
 173     {
 174         char *tree_name;
 175 
 176         tree_name = mc_config_get_full_path (MC_TREESTORE_FILE);
 177         fprintf (stderr, _ ("Cannot open the %s file for writing:\n%s\n"), tree_name,
 178                  unix_error_string (error));
 179         g_free (tree_name);
 180     }
 181 }
 182 
 183 /* --------------------------------------------------------------------------------------------- */
 184 
 185 static void
 186 tree_remove_entry (WTree *tree, const vfs_path_t *name_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 187 {
 188     (void) tree;
 189     tree_store_remove_entry (name_vpath);
 190 }
 191 
 192 /* --------------------------------------------------------------------------------------------- */
 193 
 194 static void
 195 tree_destroy (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 196 {
 197     tree_store_remove_entry_remove_hook (remove_callback);
 198     save_tree (tree);
 199 
 200     MC_PTR_FREE (tree->tree_shown);
 201     g_string_free (tree->search_buffer, TRUE);
 202     tree->selected_ptr = NULL;
 203 }
 204 
 205 /* --------------------------------------------------------------------------------------------- */
 206 /** Loads the .mc.tree file */
 207 
 208 static void
 209 load_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 210 {
 211     vfs_path_t *vpath;
 212 
 213     tree_store_load ();
 214 
 215     tree->selected_ptr = tree->store->tree_first;
 216     vpath = vfs_path_from_str (mc_config_get_home_dir ());
 217     tree_chdir (tree, vpath);
 218     vfs_path_free (vpath, TRUE);
 219 }
 220 
 221 /* --------------------------------------------------------------------------------------------- */
 222 
 223 static void
 224 tree_show_mini_info (WTree *tree, int tree_lines, int tree_cols)
     /* [previous][next][first][last][top][bottom][index][help]  */
 225 {
 226     Widget *w = WIDGET (tree);
 227     int line;
 228 
 229     // Show mini info
 230     if (tree->is_panel)
 231     {
 232         if (!panels_options.show_mini_info)
 233             return;
 234         line = tree_lines + 2;
 235     }
 236     else
 237         line = tree_lines + 1;
 238 
 239     if (tree->searching)
 240     {
 241         // Show search string
 242         tty_setcolor (INPUT_COLOR);
 243         tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
 244         widget_gotoyx (w, line, 1);
 245         tty_print_char (PATH_SEP);
 246         tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT));
 247         tty_print_char (' ');
 248     }
 249     else
 250     {
 251         // Show full name of selected directory
 252 
 253         const int *colors;
 254 
 255         colors = widget_get_colors (w);
 256         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 257         tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
 258         widget_gotoyx (w, line, 1);
 259         tty_print_string (
 260             str_fit_to_term (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT));
 261     }
 262 }
 263 
 264 /* --------------------------------------------------------------------------------------------- */
 265 
 266 static void
 267 show_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 268 {
 269     Widget *w = WIDGET (tree);
 270     tree_entry *current;
 271     int i, j;
 272     int topsublevel = 0;
 273     int x = 0, y = 0;
 274     int tree_lines, tree_cols;
 275 
 276     // Initialize
 277     tree_lines = tlines (tree);
 278     tree_cols = w->rect.cols;
 279 
 280     widget_gotoyx (w, y, x);
 281     if (tree->is_panel)
 282     {
 283         tree_cols -= 2;
 284         x = y = 1;
 285     }
 286 
 287     g_free (tree->tree_shown);
 288     tree->tree_shown = g_new0 (tree_entry *, tree_lines);
 289 
 290     if (tree->store->tree_first != NULL)
 291         topsublevel = tree->store->tree_first->sublevel;
 292 
 293     if (tree->selected_ptr == NULL)
 294     {
 295         tree->selected_ptr = tree->store->tree_first;
 296         tree->topdiff = 0;
 297     }
 298     current = tree->selected_ptr;
 299 
 300     // Calculate the directory which is to be shown on the topmost line
 301     if (!tree_navigation_flag)
 302         current = back_ptr (current, &tree->topdiff);
 303     else
 304     {
 305         i = 0;
 306 
 307         while (current->prev != NULL && i < tree->topdiff)
 308         {
 309             current = current->prev;
 310 
 311             if (current->sublevel < tree->selected_ptr->sublevel)
 312             {
 313                 if (vfs_path_equal (current->name, tree->selected_ptr->name))
 314                     i++;
 315             }
 316             else if (current->sublevel == tree->selected_ptr->sublevel)
 317             {
 318                 const char *cname;
 319 
 320                 cname = vfs_path_as_str (current->name);
 321                 for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 322                     ;
 323                 if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 324                     i++;
 325             }
 326             else if (current->sublevel == tree->selected_ptr->sublevel + 1)
 327             {
 328                 j = vfs_path_len (tree->selected_ptr->name);
 329                 if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 330                     i++;
 331             }
 332         }
 333         tree->topdiff = i;
 334     }
 335 
 336     // Loop for every line
 337     for (i = 0; i < tree_lines; i++)
 338     {
 339         const int *colors;
 340 
 341         colors = widget_get_colors (w);
 342         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 343 
 344         // Move to the beginning of the line
 345         tty_draw_hline (w->rect.y + y + i, w->rect.x + x, ' ', tree_cols);
 346 
 347         if (current == NULL)
 348             continue;
 349 
 350         if (tree->is_panel)
 351         {
 352             gboolean selected;
 353 
 354             selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr;
 355             tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR);
 356         }
 357         else
 358         {
 359             int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL;
 360 
 361             tty_setcolor (colors[idx]);
 362         }
 363 
 364         tree->tree_shown[i] = current;
 365         if (current->sublevel == topsublevel)
 366             // Show full name
 367             tty_print_string (str_fit_to_term (vfs_path_as_str (current->name),
 368                                                tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT));
 369         else
 370         {
 371             // Output branch parts
 372             for (j = 0; j < current->sublevel - topsublevel - 1; j++)
 373             {
 374                 if (tree_cols - 8 - 3 * j < 9)
 375                     break;
 376                 tty_print_char (' ');
 377                 if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
 378                     tty_print_char (mc_tty_frm[MC_TTY_FRM_VERT]);
 379                 else
 380                     tty_print_char (' ');
 381                 tty_print_char (' ');
 382             }
 383             tty_print_char (' ');
 384             j++;
 385             if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
 386                 tty_print_char (mc_tty_frm[MC_TTY_FRM_LEFTBOTTOM]);
 387             else
 388                 tty_print_char (mc_tty_frm[MC_TTY_FRM_LEFTMIDDLE]);
 389             tty_print_char (mc_tty_frm[MC_TTY_FRM_HORIZ]);
 390 
 391             // Show sub-name
 392             tty_print_char (' ');
 393             tty_print_string (
 394                 str_fit_to_term (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
 395         }
 396 
 397         // Calculate the next value for current
 398         current = current->next;
 399         if (tree_navigation_flag)
 400             for (; current != NULL; current = current->next)
 401             {
 402                 if (current->sublevel < tree->selected_ptr->sublevel)
 403                 {
 404                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 405                                             vfs_path_len (current->name)))
 406                         break;
 407                 }
 408                 else if (current->sublevel == tree->selected_ptr->sublevel)
 409                 {
 410                     const char *cname;
 411 
 412                     cname = vfs_path_as_str (current->name);
 413                     for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 414                         ;
 415                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 416                         break;
 417                 }
 418                 else if (current->sublevel == tree->selected_ptr->sublevel + 1
 419                          && vfs_path_len (tree->selected_ptr->name) > 1)
 420                 {
 421                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 422                                             vfs_path_len (tree->selected_ptr->name)))
 423                         break;
 424                 }
 425             }
 426     }
 427 
 428     tree_show_mini_info (tree, tree_lines, tree_cols);
 429 }
 430 
 431 /* --------------------------------------------------------------------------------------------- */
 432 
 433 static void
 434 tree_check_focus (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 435 {
 436     if (tree->topdiff < 3)
 437         tree->topdiff = 3;
 438     else if (tree->topdiff >= tlines (tree) - 3)
 439         tree->topdiff = tlines (tree) - 3 - 1;
 440 }
 441 
 442 /* --------------------------------------------------------------------------------------------- */
 443 
 444 static void
 445 tree_move_backward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 446 {
 447     if (!tree_navigation_flag)
 448         tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
 449     else
 450     {
 451         tree_entry *current;
 452         int j = 0;
 453 
 454         current = tree->selected_ptr;
 455         while (j < i && current->prev != NULL
 456                && current->prev->sublevel >= tree->selected_ptr->sublevel)
 457         {
 458             current = current->prev;
 459             if (current->sublevel == tree->selected_ptr->sublevel)
 460             {
 461                 tree->selected_ptr = current;
 462                 j++;
 463             }
 464         }
 465         i = j;
 466     }
 467 
 468     tree->topdiff -= i;
 469     tree_check_focus (tree);
 470 }
 471 
 472 /* --------------------------------------------------------------------------------------------- */
 473 
 474 static void
 475 tree_move_forward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 476 {
 477     if (!tree_navigation_flag)
 478         tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
 479     else
 480     {
 481         tree_entry *current;
 482         int j = 0;
 483 
 484         current = tree->selected_ptr;
 485         while (j < i && current->next != NULL
 486                && current->next->sublevel >= tree->selected_ptr->sublevel)
 487         {
 488             current = current->next;
 489             if (current->sublevel == tree->selected_ptr->sublevel)
 490             {
 491                 tree->selected_ptr = current;
 492                 j++;
 493             }
 494         }
 495         i = j;
 496     }
 497 
 498     tree->topdiff += i;
 499     tree_check_focus (tree);
 500 }
 501 
 502 /* --------------------------------------------------------------------------------------------- */
 503 
 504 static void
 505 tree_move_to_child (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 506 {
 507     tree_entry *current;
 508 
 509     // Do we have a starting point?
 510     if (tree->selected_ptr == NULL)
 511         return;
 512 
 513     // Take the next entry
 514     current = tree->selected_ptr->next;
 515     // Is it the child of the selected entry
 516     if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 517     {
 518         // Yes -> select this entry
 519         tree->selected_ptr = current;
 520         tree->topdiff++;
 521         tree_check_focus (tree);
 522     }
 523     else
 524     {
 525         // No -> rescan and try again
 526         tree_rescan (tree);
 527         current = tree->selected_ptr->next;
 528         if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 529         {
 530             tree->selected_ptr = current;
 531             tree->topdiff++;
 532             tree_check_focus (tree);
 533         }
 534     }
 535 }
 536 
 537 /* --------------------------------------------------------------------------------------------- */
 538 
 539 static gboolean
 540 tree_move_to_parent (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 541 {
 542     tree_entry *current;
 543     tree_entry *old;
 544 
 545     if (tree->selected_ptr == NULL)
 546         return FALSE;
 547 
 548     old = tree->selected_ptr;
 549 
 550     for (current = tree->selected_ptr->prev;
 551          current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
 552          current = current->prev)
 553         tree->topdiff--;
 554 
 555     if (current == NULL)
 556         current = tree->store->tree_first;
 557     tree->selected_ptr = current;
 558     tree_check_focus (tree);
 559     return tree->selected_ptr != old;
 560 }
 561 
 562 /* --------------------------------------------------------------------------------------------- */
 563 
 564 static void
 565 tree_move_to_top (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 566 {
 567     tree->selected_ptr = tree->store->tree_first;
 568     tree->topdiff = 0;
 569 }
 570 
 571 /* --------------------------------------------------------------------------------------------- */
 572 
 573 static void
 574 tree_move_to_bottom (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 575 {
 576     tree->selected_ptr = tree->store->tree_last;
 577     tree->topdiff = tlines (tree) - 3 - 1;
 578 }
 579 
 580 /* --------------------------------------------------------------------------------------------- */
 581 
 582 static void
 583 tree_chdir_sel (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 584 {
 585     if (tree->is_panel)
 586     {
 587         WPanel *p;
 588 
 589         p = change_panel ();
 590 
 591         if (panel_cd (p, tree->selected_ptr->name, cd_exact))
 592             select_item (p);
 593         else
 594             cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
 595 
 596         widget_draw (WIDGET (p));
 597         (void) change_panel ();
 598         show_tree (tree);
 599     }
 600     else
 601     {
 602         WDialog *h = DIALOG (WIDGET (tree)->owner);
 603 
 604         h->ret_value = B_ENTER;
 605         dlg_close (h);
 606     }
 607 }
 608 
 609 /* --------------------------------------------------------------------------------------------- */
 610 
 611 static void
 612 maybe_chdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 613 {
 614     if (xtree_mode && tree->is_panel && is_idle ())
 615         tree_chdir_sel (tree);
 616 }
 617 
 618 /* --------------------------------------------------------------------------------------------- */
 619 /** Search tree for text */
 620 
 621 static gboolean
 622 search_tree (WTree *tree, const GString *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 623 {
 624     tree_entry *current = tree->selected_ptr;
 625     gboolean wrapped = FALSE;
 626     gboolean found = FALSE;
 627 
 628     while (!found && (!wrapped || current != tree->selected_ptr))
 629         if (strncmp (current->subname, text->str, text->len) == 0)
 630         {
 631             tree->selected_ptr = current;
 632             found = TRUE;
 633         }
 634         else
 635         {
 636             current = current->next;
 637             if (current == NULL)
 638             {
 639                 current = tree->store->tree_first;
 640                 wrapped = TRUE;
 641             }
 642 
 643             tree->topdiff++;
 644         }
 645 
 646     tree_check_focus (tree);
 647     return found;
 648 }
 649 
 650 /* --------------------------------------------------------------------------------------------- */
 651 
 652 static void
 653 tree_do_search (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 654 {
 655     // TODO: support multi-byte characters, see do_search() in panel.c
 656 
 657     if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
 658         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 659     else if (key != 0)
 660         g_string_append_c (tree->search_buffer, (gchar) key);
 661 
 662     if (!search_tree (tree, tree->search_buffer))
 663         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 664 
 665     show_tree (tree);
 666     maybe_chdir (tree);
 667 }
 668 
 669 /* --------------------------------------------------------------------------------------------- */
 670 
 671 static void
 672 tree_rescan (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 673 {
 674     WTree *tree = data;
 675     vfs_path_t *old_vpath;
 676 
 677     old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
 678     if (old_vpath == NULL)
 679         return;
 680 
 681     if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
 682     {
 683         int ret;
 684 
 685         tree_store_rescan (tree->selected_ptr->name);
 686         ret = mc_chdir (old_vpath);
 687         (void) ret;
 688     }
 689     vfs_path_free (old_vpath, TRUE);
 690 }
 691 
 692 /* --------------------------------------------------------------------------------------------- */
 693 
 694 static void
 695 tree_forget (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 696 {
 697     WTree *tree = data;
 698 
 699     if (tree->selected_ptr != NULL)
 700         tree_remove_entry (tree, tree->selected_ptr->name);
 701 }
 702 
 703 /* --------------------------------------------------------------------------------------------- */
 704 
 705 static void
 706 tree_copy (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 707 {
 708     char msg[BUF_MEDIUM];
 709     char *dest;
 710 
 711     if (tree->selected_ptr == NULL)
 712         return;
 713 
 714     g_snprintf (msg, sizeof (msg), _ ("Copy \"%s\" directory to:"),
 715                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 716     dest = input_expand_dialog (Q_ ("DialogTitle|Copy"), msg, MC_HISTORY_FM_TREE_COPY, default_dest,
 717                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 718 
 719     if (dest != NULL && *dest != '\0')
 720     {
 721         file_op_context_t *ctx;
 722 
 723         ctx = file_op_context_new (OP_COPY);
 724         file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
 725         ctx->ask_overwrite = FALSE;
 726         copy_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE, FALSE,
 727                       NULL);
 728         file_op_context_destroy (ctx);
 729     }
 730 
 731     g_free (dest);
 732 }
 733 
 734 /* --------------------------------------------------------------------------------------------- */
 735 
 736 static void
 737 tree_move (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 738 {
 739     char msg[BUF_MEDIUM];
 740     char *dest;
 741 
 742     if (tree->selected_ptr == NULL)
 743         return;
 744 
 745     g_snprintf (msg, sizeof (msg), _ ("Move \"%s\" directory to:"),
 746                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 747     dest = input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
 748                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 749 
 750     if (dest != NULL && *dest != '\0')
 751     {
 752         vfs_path_t *dest_vpath;
 753         struct stat buf;
 754 
 755         dest_vpath = vfs_path_from_str (dest);
 756 
 757         if (mc_stat (dest_vpath, &buf) != 0)
 758             file_error_message (_ ("Cannot stat the destination\n%s"), dest);
 759         else if (!S_ISDIR (buf.st_mode))
 760         {
 761             errno = ENOTDIR;
 762             file_error_message (_ ("Destination\n%s\nmust be a directory"), dest);
 763         }
 764         else
 765         {
 766             file_op_context_t *ctx;
 767 
 768             ctx = file_op_context_new (OP_MOVE);
 769             file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 770             move_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
 771             file_op_context_destroy (ctx);
 772         }
 773 
 774         vfs_path_free (dest_vpath, TRUE);
 775     }
 776 
 777     g_free (dest);
 778 }
 779 
 780 /* --------------------------------------------------------------------------------------------- */
 781 
 782 #if 0
 783 static void
 784 tree_mkdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 785 {
 786     char old_dir[MC_MAXPATHLEN];
 787 
 788     if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
 789         return;
 790     /* FIXME
 791        mkdir_cmd (tree);
 792      */
 793     tree_rescan (tree);
 794     chdir (old_dir);
 795 }
 796 #endif
 797 
 798 /* --------------------------------------------------------------------------------------------- */
 799 
 800 static void
 801 tree_rmdir (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 802 {
 803     WTree *tree = data;
 804     file_op_context_t *ctx;
 805 
 806     if (tree->selected_ptr == NULL)
 807         return;
 808 
 809     if (confirm_delete)
 810     {
 811         char *buf;
 812         int result;
 813 
 814         buf = g_strdup_printf (_ ("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
 815 
 816         result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _ ("&Yes"), _ ("&No"));
 817         g_free (buf);
 818         if (result != 0)
 819             return;
 820     }
 821 
 822     ctx = file_op_context_new (OP_DELETE);
 823     file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 824     if (erase_dir (ctx, tree->selected_ptr->name) == FILE_CONT)
 825         tree_forget (tree);
 826     file_op_context_destroy (ctx);
 827 }
 828 
 829 /* --------------------------------------------------------------------------------------------- */
 830 
 831 static inline void
 832 tree_move_up (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 833 {
 834     tree_move_backward (tree, 1);
 835     show_tree (tree);
 836     maybe_chdir (tree);
 837 }
 838 
 839 /* --------------------------------------------------------------------------------------------- */
 840 
 841 static inline void
 842 tree_move_down (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 843 {
 844     tree_move_forward (tree, 1);
 845     show_tree (tree);
 846     maybe_chdir (tree);
 847 }
 848 
 849 /* --------------------------------------------------------------------------------------------- */
 850 
 851 static inline void
 852 tree_move_home (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 853 {
 854     tree_move_to_top (tree);
 855     show_tree (tree);
 856     maybe_chdir (tree);
 857 }
 858 
 859 /* --------------------------------------------------------------------------------------------- */
 860 
 861 static inline void
 862 tree_move_end (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 863 {
 864     tree_move_to_bottom (tree);
 865     show_tree (tree);
 866     maybe_chdir (tree);
 867 }
 868 
 869 /* --------------------------------------------------------------------------------------------- */
 870 
 871 static void
 872 tree_move_pgup (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 873 {
 874     tree_move_backward (tree, tlines (tree) - 1);
 875     show_tree (tree);
 876     maybe_chdir (tree);
 877 }
 878 
 879 /* --------------------------------------------------------------------------------------------- */
 880 
 881 static void
 882 tree_move_pgdn (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 883 {
 884     tree_move_forward (tree, tlines (tree) - 1);
 885     show_tree (tree);
 886     maybe_chdir (tree);
 887 }
 888 
 889 /* --------------------------------------------------------------------------------------------- */
 890 
 891 static gboolean
 892 tree_move_left (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 893 {
 894     gboolean v = FALSE;
 895 
 896     if (tree_navigation_flag)
 897     {
 898         v = tree_move_to_parent (tree);
 899         show_tree (tree);
 900         maybe_chdir (tree);
 901     }
 902 
 903     return v;
 904 }
 905 
 906 /* --------------------------------------------------------------------------------------------- */
 907 
 908 static gboolean
 909 tree_move_right (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 910 {
 911     gboolean v = FALSE;
 912 
 913     if (tree_navigation_flag)
 914     {
 915         tree_move_to_child (tree);
 916         show_tree (tree);
 917         maybe_chdir (tree);
 918         v = TRUE;
 919     }
 920 
 921     return v;
 922 }
 923 
 924 /* --------------------------------------------------------------------------------------------- */
 925 
 926 static void
 927 tree_start_search (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 928 {
 929     if (tree->searching)
 930     {
 931         if (tree->selected_ptr == tree->store->tree_last)
 932             tree_move_to_top (tree);
 933         else
 934         {
 935             gboolean i;
 936 
 937             /* set navigation mode temporarily to 'Static' because in
 938              * dynamic navigation mode tree_move_forward will not move
 939              * to a lower sublevel if necessary (sequent searches must
 940              * start with the directory followed the last found directory)
 941              */
 942             i = tree_navigation_flag;
 943             tree_navigation_flag = FALSE;
 944             tree_move_forward (tree, 1);
 945             tree_navigation_flag = i;
 946         }
 947         tree_do_search (tree, 0);
 948     }
 949     else
 950     {
 951         tree->searching = TRUE;
 952         g_string_set_size (tree->search_buffer, 0);
 953     }
 954 }
 955 
 956 /* --------------------------------------------------------------------------------------------- */
 957 
 958 static void
 959 tree_toggle_navig (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 960 {
 961     Widget *w = WIDGET (tree);
 962     WButtonBar *b;
 963 
 964     tree_navigation_flag = !tree_navigation_flag;
 965 
 966     b = buttonbar_find (DIALOG (w->owner));
 967     buttonbar_set_label (b, 4,
 968                          tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
 969                          w->keymap, w);
 970     widget_draw (WIDGET (b));
 971 }
 972 
 973 /* --------------------------------------------------------------------------------------------- */
 974 
 975 static void
 976 tree_help (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 977 {
 978     ev_help_t event_data = { NULL, "[Directory Tree]" };
 979 
 980     mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 981 }
 982 
 983 /* --------------------------------------------------------------------------------------------- */
 984 
 985 static cb_ret_t
 986 tree_execute_cmd (WTree *tree, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 987 {
 988     cb_ret_t res = MSG_HANDLED;
 989 
 990     if (command != CK_Search)
 991         tree->searching = FALSE;
 992 
 993     switch (command)
 994     {
 995     case CK_Help:
 996         tree_help ();
 997         break;
 998     case CK_Forget:
 999         tree_forget (tree);
1000         break;
1001     case CK_ToggleNavigation:
1002         tree_toggle_navig (tree);
1003         break;
1004     case CK_Copy:
1005         tree_copy (tree, "");
1006         break;
1007     case CK_Move:
1008         tree_move (tree, "");
1009         break;
1010     case CK_Up:
1011         tree_move_up (tree);
1012         break;
1013     case CK_Down:
1014         tree_move_down (tree);
1015         break;
1016     case CK_Top:
1017         tree_move_home (tree);
1018         break;
1019     case CK_Bottom:
1020         tree_move_end (tree);
1021         break;
1022     case CK_PageUp:
1023         tree_move_pgup (tree);
1024         break;
1025     case CK_PageDown:
1026         tree_move_pgdn (tree);
1027         break;
1028     case CK_Enter:
1029         tree_chdir_sel (tree);
1030         break;
1031     case CK_Reread:
1032         tree_rescan (tree);
1033         break;
1034     case CK_Search:
1035         tree_start_search (tree);
1036         break;
1037     case CK_Delete:
1038         tree_rmdir (tree);
1039         break;
1040     case CK_Quit:
1041         if (!tree->is_panel)
1042             dlg_close (DIALOG (WIDGET (tree)->owner));
1043         return res;
1044     default:
1045         res = MSG_NOT_HANDLED;
1046     }
1047 
1048     show_tree (tree);
1049 
1050     return res;
1051 }
1052 
1053 /* --------------------------------------------------------------------------------------------- */
1054 
1055 static cb_ret_t
1056 tree_key (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1057 {
1058     long command;
1059 
1060     if (is_abort_char (key))
1061     {
1062         if (tree->is_panel)
1063         {
1064             tree->searching = FALSE;
1065             show_tree (tree);
1066             return MSG_HANDLED;  // eat abort char
1067         }
1068         /* modal tree dialog: let upper layer see the
1069            abort character and close the dialog */
1070         return MSG_NOT_HANDLED;
1071     }
1072 
1073     if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1074     {
1075         tree_do_search (tree, key);
1076         show_tree (tree);
1077         return MSG_HANDLED;
1078     }
1079 
1080     command = widget_lookup_key (WIDGET (tree), key);
1081     switch (command)
1082     {
1083     case CK_IgnoreKey:
1084         break;
1085     case CK_Left:
1086         return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1087     case CK_Right:
1088         return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1089     default:
1090         tree_execute_cmd (tree, command);
1091         return MSG_HANDLED;
1092     }
1093 
1094     // Do not eat characters not meant for the tree below ' ' (e.g. C-l).
1095     if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1096     {
1097         tree_start_search (tree);
1098         tree_do_search (tree, key);
1099         return MSG_HANDLED;
1100     }
1101 
1102     return MSG_NOT_HANDLED;
1103 }
1104 
1105 /* --------------------------------------------------------------------------------------------- */
1106 
1107 static void
1108 tree_frame (WDialog *h, WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1109 {
1110     Widget *w = WIDGET (tree);
1111 
1112     (void) h;
1113 
1114     tty_setcolor (NORMAL_COLOR);
1115     widget_erase (w);
1116     if (tree->is_panel)
1117     {
1118         const char *title = _ ("Directory tree");
1119         const int len = str_term_width1 (title);
1120 
1121         tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
1122 
1123         widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
1124         tty_printf (" %s ", title);
1125 
1126         if (panels_options.show_mini_info)
1127         {
1128             int y;
1129 
1130             y = w->rect.lines - 3;
1131             widget_gotoyx (w, y, 0);
1132             tty_print_char (mc_tty_frm[MC_TTY_FRM_DLEFTMIDDLE]);
1133             widget_gotoyx (w, y, w->rect.cols - 1);
1134             tty_print_char (mc_tty_frm[MC_TTY_FRM_DRIGHTMIDDLE]);
1135             tty_draw_hline (w->rect.y + y, w->rect.x + 1, mc_tty_frm[MC_TTY_FRM_HORIZ],
1136                             w->rect.cols - 2);
1137         }
1138     }
1139 }
1140 
1141 /* --------------------------------------------------------------------------------------------- */
1142 
1143 static cb_ret_t
1144 tree_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1145 {
1146     WTree *tree = (WTree *) w;
1147     WDialog *h = DIALOG (w->owner);
1148     WButtonBar *b;
1149 
1150     switch (msg)
1151     {
1152     case MSG_DRAW:
1153         tree_frame (h, tree);
1154         show_tree (tree);
1155         if (widget_get_state (w, WST_FOCUSED))
1156         {
1157             b = buttonbar_find (h);
1158             widget_draw (WIDGET (b));
1159         }
1160         return MSG_HANDLED;
1161 
1162     case MSG_FOCUS:
1163         b = buttonbar_find (h);
1164         buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w);
1165         buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w);
1166         buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w);
1167         buttonbar_set_label (
1168             b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
1169             w->keymap, w);
1170         buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
1171         buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w);
1172 #if 0
1173         // FIXME: mkdir is currently defunct 
1174         buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w);
1175 #else
1176         buttonbar_clear_label (b, 7, w);
1177 #endif
1178         buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w);
1179 
1180         return MSG_HANDLED;
1181 
1182     case MSG_UNFOCUS:
1183         tree->searching = FALSE;
1184         return MSG_HANDLED;
1185 
1186     case MSG_KEY:
1187         return tree_key (tree, parm);
1188 
1189     case MSG_ACTION:
1190         // command from buttonbar
1191         return tree_execute_cmd (tree, parm);
1192 
1193     case MSG_DESTROY:
1194         tree_destroy (tree);
1195         return MSG_HANDLED;
1196 
1197     default:
1198         return widget_default_callback (w, sender, msg, parm, data);
1199     }
1200 }
1201 
1202 /* --------------------------------------------------------------------------------------------- */
1203 
1204 /**
1205  * Mouse callback
1206  */
1207 static void
1208 tree_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
1209 {
1210     WTree *tree = (WTree *) w;
1211     int y;
1212 
1213     y = event->y;
1214     if (tree->is_panel)
1215         y--;
1216 
1217     switch (msg)
1218     {
1219     case MSG_MOUSE_DOWN:
1220         // rest of the upper frame - call menu
1221         if (tree->is_panel && event->y == WIDGET (w->owner)->rect.y)
1222         {
1223             // return MOU_UNHANDLED
1224             event->result.abort = TRUE;
1225         }
1226         else if (!widget_get_state (w, WST_FOCUSED))
1227             (void) change_panel ();
1228         break;
1229 
1230     case MSG_MOUSE_CLICK:
1231     {
1232         int lines;
1233 
1234         lines = tlines (tree);
1235 
1236         if (y < 0)
1237         {
1238             tree_move_backward (tree, lines - 1);
1239             show_tree (tree);
1240         }
1241         else if (y >= lines)
1242         {
1243             tree_move_forward (tree, lines - 1);
1244             show_tree (tree);
1245         }
1246         else if ((event->count & GPM_DOUBLE) != 0)
1247         {
1248             if (tree->tree_shown[y] != NULL)
1249             {
1250                 tree->selected_ptr = tree->tree_shown[y];
1251                 tree->topdiff = y;
1252             }
1253 
1254             tree_chdir_sel (tree);
1255         }
1256     }
1257     break;
1258 
1259     case MSG_MOUSE_SCROLL_UP:
1260     case MSG_MOUSE_SCROLL_DOWN:
1261         // TODO: Ticket #2218
1262         break;
1263 
1264     default:
1265         break;
1266     }
1267 }
1268 
1269 /* --------------------------------------------------------------------------------------------- */
1270 /*** public functions ****************************************************************************/
1271 /* --------------------------------------------------------------------------------------------- */
1272 
1273 WTree *
1274 tree_new (const WRect *r, gboolean is_panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1275 {
1276     WTree *tree;
1277     Widget *w;
1278 
1279     tree = g_new (WTree, 1);
1280 
1281     w = WIDGET (tree);
1282     widget_init (w, r, tree_callback, tree_mouse_callback);
1283     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
1284     w->keymap = tree_map;
1285 
1286     tree->is_panel = is_panel;
1287     tree->selected_ptr = NULL;
1288 
1289     tree->store = tree_store_get ();
1290     tree_store_add_entry_remove_hook (remove_callback, tree);
1291     tree->tree_shown = NULL;
1292     tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN);
1293     tree->topdiff = w->rect.lines / 2;
1294     tree->searching = FALSE;
1295 
1296     load_tree (tree);
1297     return tree;
1298 }
1299 
1300 /* --------------------------------------------------------------------------------------------- */
1301 
1302 void
1303 tree_chdir (WTree *tree, const vfs_path_t *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
1304 {
1305     tree_entry *current;
1306 
1307     current = tree_store_whereis (dir);
1308     if (current != NULL)
1309     {
1310         tree->selected_ptr = current;
1311         tree_check_focus (tree);
1312     }
1313 }
1314 
1315 /* --------------------------------------------------------------------------------------------- */
1316 /** Return name of the currently selected entry */
1317 
1318 const vfs_path_t *
1319 tree_selected_name (const WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1320 {
1321     return tree->selected_ptr->name;
1322 }
1323 
1324 /* --------------------------------------------------------------------------------------------- */
1325 
1326 void
1327 sync_tree (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1328 {
1329     tree_chdir (the_tree, vpath);
1330 }
1331 
1332 /* --------------------------------------------------------------------------------------------- */
1333 
1334 WTree *
1335 find_tree (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1336 {
1337     return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
1338 }
1339 
1340 /* --------------------------------------------------------------------------------------------- */

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