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 
  62 #include "dir.h"
  63 #include "filemanager.h"  // the_menubar
  64 #include "file.h"         // copy_dir_dir(), move_dir_dir(), erase_dir()
  65 #include "layout.h"       // command_prompt
  66 #include "treestore.h"
  67 #include "cmd.h"
  68 #include "filegui.h"
  69 #include "cd.h"  // cd_error_message()
  70 
  71 #include "tree.h"
  72 
  73 /*** global variables ****************************************************************************/
  74 
  75 /* The pointer to the tree */
  76 WTree *the_tree = NULL;
  77 
  78 /* If this is true, then when browsing the tree the other window will
  79  * automatically reload it's directory with the contents of the currently
  80  * selected directory.
  81  */
  82 gboolean xtree_mode = FALSE;
  83 
  84 /*** file scope macro definitions ****************************************************************/
  85 
  86 #define tlines(t)                                                                                  \
  87     (t->is_panel ? WIDGET (t)->rect.lines - 2 - (panels_options.show_mini_info ? 2 : 0)            \
  88                  : WIDGET (t)->rect.lines)
  89 
  90 /*** file scope type declarations ****************************************************************/
  91 
  92 struct WTree
  93 {
  94     Widget widget;
  95     struct TreeStore *store;
  96     tree_entry *selected_ptr;  // The selected directory
  97     GString *search_buffer;    // Current search string
  98     tree_entry **tree_shown;   // Entries currently on screen
  99     gboolean is_panel;         // panel or plain widget flag
 100     gboolean searching;        // Are we on searching mode?
 101     int topdiff;               /* The difference between the topmost
 102                                   shown and the selected */
 103 };
 104 
 105 /*** forward declarations (file scope functions) *************************************************/
 106 
 107 static void tree_rescan (void *data);
 108 
 109 /*** file scope variables ************************************************************************/
 110 
 111 /* Specifies the display mode: 1d or 2d */
 112 static gboolean tree_navigation_flag = FALSE;
 113 
 114 /* --------------------------------------------------------------------------------------------- */
 115 /*** file scope functions ************************************************************************/
 116 /* --------------------------------------------------------------------------------------------- */
 117 
 118 static tree_entry *
 119 back_ptr (tree_entry *ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 120 {
 121     int i;
 122 
 123     for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++)
 124         ;
 125 
 126     *count = i;
 127     return ptr;
 128 }
 129 
 130 /* --------------------------------------------------------------------------------------------- */
 131 
 132 static tree_entry *
 133 forw_ptr (tree_entry *ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 134 {
 135     int i;
 136 
 137     for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++)
 138         ;
 139 
 140     *count = i;
 141     return ptr;
 142 }
 143 
 144 /* --------------------------------------------------------------------------------------------- */
 145 
 146 static void
 147 remove_callback (tree_entry *entry, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 148 {
 149     WTree *tree = data;
 150 
 151     if (tree->selected_ptr == entry)
 152     {
 153         if (tree->selected_ptr->next != NULL)
 154             tree->selected_ptr = tree->selected_ptr->next;
 155         else
 156             tree->selected_ptr = tree->selected_ptr->prev;
 157     }
 158 }
 159 
 160 /* --------------------------------------------------------------------------------------------- */
 161 /** Save the ${XDG_CACHE_HOME}/mc/Tree file */
 162 
 163 static void
 164 save_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 165 {
 166     int error;
 167 
 168     (void) tree;
 169 
 170     error = tree_store_save ();
 171     if (error != 0)
 172     {
 173         char *tree_name;
 174 
 175         tree_name = mc_config_get_full_path (MC_TREESTORE_FILE);
 176         fprintf (stderr, _ ("Cannot open the %s file for writing:\n%s\n"), tree_name,
 177                  unix_error_string (error));
 178         g_free (tree_name);
 179     }
 180 }
 181 
 182 /* --------------------------------------------------------------------------------------------- */
 183 
 184 static void
 185 tree_remove_entry (WTree *tree, const vfs_path_t *name_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 186 {
 187     (void) tree;
 188     tree_store_remove_entry (name_vpath);
 189 }
 190 
 191 /* --------------------------------------------------------------------------------------------- */
 192 
 193 static void
 194 tree_destroy (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 195 {
 196     tree_store_remove_entry_remove_hook (remove_callback);
 197     save_tree (tree);
 198 
 199     MC_PTR_FREE (tree->tree_shown);
 200     g_string_free (tree->search_buffer, TRUE);
 201     tree->selected_ptr = NULL;
 202 }
 203 
 204 /* --------------------------------------------------------------------------------------------- */
 205 /** Loads the .mc.tree file */
 206 
 207 static void
 208 load_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 209 {
 210     vfs_path_t *vpath;
 211 
 212     tree_store_load ();
 213 
 214     tree->selected_ptr = tree->store->tree_first;
 215     vpath = vfs_path_from_str (mc_config_get_home_dir ());
 216     tree_chdir (tree, vpath);
 217     vfs_path_free (vpath, TRUE);
 218 }
 219 
 220 /* --------------------------------------------------------------------------------------------- */
 221 
 222 static void
 223 tree_show_mini_info (WTree *tree, int tree_lines, int tree_cols)
     /* [previous][next][first][last][top][bottom][index][help]  */
 224 {
 225     Widget *w = WIDGET (tree);
 226     int line;
 227 
 228     // Show mini info
 229     if (tree->is_panel)
 230     {
 231         if (!panels_options.show_mini_info)
 232             return;
 233         line = tree_lines + 2;
 234     }
 235     else
 236         line = tree_lines + 1;
 237 
 238     if (tree->searching)
 239     {
 240         // Show search string
 241         tty_setcolor (INPUT_COLOR);
 242         tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
 243         widget_gotoyx (w, line, 1);
 244         tty_print_char (PATH_SEP);
 245         tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT));
 246         tty_print_char (' ');
 247     }
 248     else
 249     {
 250         // Show full name of selected directory
 251 
 252         const int *colors;
 253 
 254         colors = widget_get_colors (w);
 255         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 256         tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
 257         widget_gotoyx (w, line, 1);
 258         tty_print_string (
 259             str_fit_to_term (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT));
 260     }
 261 }
 262 
 263 /* --------------------------------------------------------------------------------------------- */
 264 
 265 static void
 266 show_tree (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 267 {
 268     Widget *w = WIDGET (tree);
 269     tree_entry *current;
 270     int i, j;
 271     int topsublevel = 0;
 272     int x = 0, y = 0;
 273     int tree_lines, tree_cols;
 274 
 275     // Initialize
 276     tree_lines = tlines (tree);
 277     tree_cols = w->rect.cols;
 278 
 279     widget_gotoyx (w, y, x);
 280     if (tree->is_panel)
 281     {
 282         tree_cols -= 2;
 283         x = y = 1;
 284     }
 285 
 286     g_free (tree->tree_shown);
 287     tree->tree_shown = g_new0 (tree_entry *, tree_lines);
 288 
 289     if (tree->store->tree_first != NULL)
 290         topsublevel = tree->store->tree_first->sublevel;
 291 
 292     if (tree->selected_ptr == NULL)
 293     {
 294         tree->selected_ptr = tree->store->tree_first;
 295         tree->topdiff = 0;
 296     }
 297     current = tree->selected_ptr;
 298 
 299     // Calculate the directory which is to be shown on the topmost line
 300     if (!tree_navigation_flag)
 301         current = back_ptr (current, &tree->topdiff);
 302     else
 303     {
 304         i = 0;
 305 
 306         while (current->prev != NULL && i < tree->topdiff)
 307         {
 308             current = current->prev;
 309 
 310             if (current->sublevel < tree->selected_ptr->sublevel)
 311             {
 312                 if (vfs_path_equal (current->name, tree->selected_ptr->name))
 313                     i++;
 314             }
 315             else if (current->sublevel == tree->selected_ptr->sublevel)
 316             {
 317                 const char *cname;
 318 
 319                 cname = vfs_path_as_str (current->name);
 320                 for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 321                     ;
 322                 if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 323                     i++;
 324             }
 325             else if (current->sublevel == tree->selected_ptr->sublevel + 1)
 326             {
 327                 j = vfs_path_len (tree->selected_ptr->name);
 328                 if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 329                     i++;
 330             }
 331         }
 332         tree->topdiff = i;
 333     }
 334 
 335     // Loop for every line
 336     for (i = 0; i < tree_lines; i++)
 337     {
 338         const int *colors;
 339 
 340         colors = widget_get_colors (w);
 341         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 342 
 343         // Move to the beginning of the line
 344         tty_draw_hline (w->rect.y + y + i, w->rect.x + x, ' ', tree_cols);
 345 
 346         if (current == NULL)
 347             continue;
 348 
 349         if (tree->is_panel)
 350         {
 351             gboolean selected;
 352 
 353             selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr;
 354             tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR);
 355         }
 356         else
 357         {
 358             int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL;
 359 
 360             tty_setcolor (colors[idx]);
 361         }
 362 
 363         tree->tree_shown[i] = current;
 364         if (current->sublevel == topsublevel)
 365             // Show full name
 366             tty_print_string (str_fit_to_term (vfs_path_as_str (current->name),
 367                                                tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT));
 368         else
 369         {
 370             // Sub level directory
 371             tty_set_alt_charset (TRUE);
 372 
 373             // Output branch parts
 374             for (j = 0; j < current->sublevel - topsublevel - 1; j++)
 375             {
 376                 if (tree_cols - 8 - 3 * j < 9)
 377                     break;
 378                 tty_print_char (' ');
 379                 if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
 380                     tty_print_char (ACS_VLINE);
 381                 else
 382                     tty_print_char (' ');
 383                 tty_print_char (' ');
 384             }
 385             tty_print_char (' ');
 386             j++;
 387             if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
 388                 tty_print_char (ACS_LLCORNER);
 389             else
 390                 tty_print_char (ACS_LTEE);
 391             tty_print_char (ACS_HLINE);
 392             tty_set_alt_charset (FALSE);
 393 
 394             // Show sub-name
 395             tty_print_char (' ');
 396             tty_print_string (
 397                 str_fit_to_term (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
 398         }
 399 
 400         // Calculate the next value for current
 401         current = current->next;
 402         if (tree_navigation_flag)
 403             for (; current != NULL; current = current->next)
 404             {
 405                 if (current->sublevel < tree->selected_ptr->sublevel)
 406                 {
 407                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 408                                             vfs_path_len (current->name)))
 409                         break;
 410                 }
 411                 else if (current->sublevel == tree->selected_ptr->sublevel)
 412                 {
 413                     const char *cname;
 414 
 415                     cname = vfs_path_as_str (current->name);
 416                     for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 417                         ;
 418                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 419                         break;
 420                 }
 421                 else if (current->sublevel == tree->selected_ptr->sublevel + 1
 422                          && vfs_path_len (tree->selected_ptr->name) > 1)
 423                 {
 424                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 425                                             vfs_path_len (tree->selected_ptr->name)))
 426                         break;
 427                 }
 428             }
 429     }
 430 
 431     tree_show_mini_info (tree, tree_lines, tree_cols);
 432 }
 433 
 434 /* --------------------------------------------------------------------------------------------- */
 435 
 436 static void
 437 tree_check_focus (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 438 {
 439     if (tree->topdiff < 3)
 440         tree->topdiff = 3;
 441     else if (tree->topdiff >= tlines (tree) - 3)
 442         tree->topdiff = tlines (tree) - 3 - 1;
 443 }
 444 
 445 /* --------------------------------------------------------------------------------------------- */
 446 
 447 static void
 448 tree_move_backward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 449 {
 450     if (!tree_navigation_flag)
 451         tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
 452     else
 453     {
 454         tree_entry *current;
 455         int j = 0;
 456 
 457         current = tree->selected_ptr;
 458         while (j < i && current->prev != NULL
 459                && current->prev->sublevel >= tree->selected_ptr->sublevel)
 460         {
 461             current = current->prev;
 462             if (current->sublevel == tree->selected_ptr->sublevel)
 463             {
 464                 tree->selected_ptr = current;
 465                 j++;
 466             }
 467         }
 468         i = j;
 469     }
 470 
 471     tree->topdiff -= i;
 472     tree_check_focus (tree);
 473 }
 474 
 475 /* --------------------------------------------------------------------------------------------- */
 476 
 477 static void
 478 tree_move_forward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 479 {
 480     if (!tree_navigation_flag)
 481         tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
 482     else
 483     {
 484         tree_entry *current;
 485         int j = 0;
 486 
 487         current = tree->selected_ptr;
 488         while (j < i && current->next != NULL
 489                && current->next->sublevel >= tree->selected_ptr->sublevel)
 490         {
 491             current = current->next;
 492             if (current->sublevel == tree->selected_ptr->sublevel)
 493             {
 494                 tree->selected_ptr = current;
 495                 j++;
 496             }
 497         }
 498         i = j;
 499     }
 500 
 501     tree->topdiff += i;
 502     tree_check_focus (tree);
 503 }
 504 
 505 /* --------------------------------------------------------------------------------------------- */
 506 
 507 static void
 508 tree_move_to_child (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 509 {
 510     tree_entry *current;
 511 
 512     // Do we have a starting point?
 513     if (tree->selected_ptr == NULL)
 514         return;
 515 
 516     // Take the next entry
 517     current = tree->selected_ptr->next;
 518     // Is it the child of the selected entry
 519     if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 520     {
 521         // Yes -> select this entry
 522         tree->selected_ptr = current;
 523         tree->topdiff++;
 524         tree_check_focus (tree);
 525     }
 526     else
 527     {
 528         // No -> rescan and try again
 529         tree_rescan (tree);
 530         current = tree->selected_ptr->next;
 531         if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 532         {
 533             tree->selected_ptr = current;
 534             tree->topdiff++;
 535             tree_check_focus (tree);
 536         }
 537     }
 538 }
 539 
 540 /* --------------------------------------------------------------------------------------------- */
 541 
 542 static gboolean
 543 tree_move_to_parent (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 544 {
 545     tree_entry *current;
 546     tree_entry *old;
 547 
 548     if (tree->selected_ptr == NULL)
 549         return FALSE;
 550 
 551     old = tree->selected_ptr;
 552 
 553     for (current = tree->selected_ptr->prev;
 554          current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
 555          current = current->prev)
 556         tree->topdiff--;
 557 
 558     if (current == NULL)
 559         current = tree->store->tree_first;
 560     tree->selected_ptr = current;
 561     tree_check_focus (tree);
 562     return tree->selected_ptr != old;
 563 }
 564 
 565 /* --------------------------------------------------------------------------------------------- */
 566 
 567 static void
 568 tree_move_to_top (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 569 {
 570     tree->selected_ptr = tree->store->tree_first;
 571     tree->topdiff = 0;
 572 }
 573 
 574 /* --------------------------------------------------------------------------------------------- */
 575 
 576 static void
 577 tree_move_to_bottom (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 578 {
 579     tree->selected_ptr = tree->store->tree_last;
 580     tree->topdiff = tlines (tree) - 3 - 1;
 581 }
 582 
 583 /* --------------------------------------------------------------------------------------------- */
 584 
 585 static void
 586 tree_chdir_sel (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 587 {
 588     if (tree->is_panel)
 589     {
 590         WPanel *p;
 591 
 592         p = change_panel ();
 593 
 594         if (panel_cd (p, tree->selected_ptr->name, cd_exact))
 595             select_item (p);
 596         else
 597             cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
 598 
 599         widget_draw (WIDGET (p));
 600         (void) change_panel ();
 601         show_tree (tree);
 602     }
 603     else
 604     {
 605         WDialog *h = DIALOG (WIDGET (tree)->owner);
 606 
 607         h->ret_value = B_ENTER;
 608         dlg_close (h);
 609     }
 610 }
 611 
 612 /* --------------------------------------------------------------------------------------------- */
 613 
 614 static void
 615 maybe_chdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 616 {
 617     if (xtree_mode && tree->is_panel && is_idle ())
 618         tree_chdir_sel (tree);
 619 }
 620 
 621 /* --------------------------------------------------------------------------------------------- */
 622 /** Search tree for text */
 623 
 624 static gboolean
 625 search_tree (WTree *tree, const GString *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 626 {
 627     tree_entry *current = tree->selected_ptr;
 628     gboolean wrapped = FALSE;
 629     gboolean found = FALSE;
 630 
 631     while (!found && (!wrapped || current != tree->selected_ptr))
 632         if (strncmp (current->subname, text->str, text->len) == 0)
 633         {
 634             tree->selected_ptr = current;
 635             found = TRUE;
 636         }
 637         else
 638         {
 639             current = current->next;
 640             if (current == NULL)
 641             {
 642                 current = tree->store->tree_first;
 643                 wrapped = TRUE;
 644             }
 645 
 646             tree->topdiff++;
 647         }
 648 
 649     tree_check_focus (tree);
 650     return found;
 651 }
 652 
 653 /* --------------------------------------------------------------------------------------------- */
 654 
 655 static void
 656 tree_do_search (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 657 {
 658     // TODO: support multi-byte characters, see do_search() in panel.c
 659 
 660     if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
 661         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 662     else if (key != 0)
 663         g_string_append_c (tree->search_buffer, (gchar) key);
 664 
 665     if (!search_tree (tree, tree->search_buffer))
 666         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 667 
 668     show_tree (tree);
 669     maybe_chdir (tree);
 670 }
 671 
 672 /* --------------------------------------------------------------------------------------------- */
 673 
 674 static void
 675 tree_rescan (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 676 {
 677     WTree *tree = data;
 678     vfs_path_t *old_vpath;
 679 
 680     old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
 681     if (old_vpath == NULL)
 682         return;
 683 
 684     if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
 685     {
 686         int ret;
 687 
 688         tree_store_rescan (tree->selected_ptr->name);
 689         ret = mc_chdir (old_vpath);
 690         (void) ret;
 691     }
 692     vfs_path_free (old_vpath, TRUE);
 693 }
 694 
 695 /* --------------------------------------------------------------------------------------------- */
 696 
 697 static void
 698 tree_forget (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 699 {
 700     WTree *tree = data;
 701 
 702     if (tree->selected_ptr != NULL)
 703         tree_remove_entry (tree, tree->selected_ptr->name);
 704 }
 705 
 706 /* --------------------------------------------------------------------------------------------- */
 707 
 708 static void
 709 tree_copy (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 710 {
 711     char msg[BUF_MEDIUM];
 712     char *dest;
 713 
 714     if (tree->selected_ptr == NULL)
 715         return;
 716 
 717     g_snprintf (msg, sizeof (msg), _ ("Copy \"%s\" directory to:"),
 718                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 719     dest = input_expand_dialog (Q_ ("DialogTitle|Copy"), msg, MC_HISTORY_FM_TREE_COPY, default_dest,
 720                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 721 
 722     if (dest != NULL && *dest != '\0')
 723     {
 724         file_op_context_t *ctx;
 725 
 726         ctx = file_op_context_new (OP_COPY);
 727         file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
 728         ctx->ask_overwrite = FALSE;
 729         copy_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE, FALSE,
 730                       NULL);
 731         file_op_context_destroy (ctx);
 732     }
 733 
 734     g_free (dest);
 735 }
 736 
 737 /* --------------------------------------------------------------------------------------------- */
 738 
 739 static void
 740 tree_move (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 741 {
 742     char msg[BUF_MEDIUM];
 743     char *dest;
 744 
 745     if (tree->selected_ptr == NULL)
 746         return;
 747 
 748     g_snprintf (msg, sizeof (msg), _ ("Move \"%s\" directory to:"),
 749                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 750     dest = input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
 751                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 752 
 753     if (dest != NULL && *dest != '\0')
 754     {
 755         vfs_path_t *dest_vpath;
 756         struct stat buf;
 757 
 758         dest_vpath = vfs_path_from_str (dest);
 759 
 760         if (mc_stat (dest_vpath, &buf) != 0)
 761             message (D_ERROR, MSG_ERROR, _ ("Cannot stat the destination\n%s"),
 762                      unix_error_string (errno));
 763         else if (!S_ISDIR (buf.st_mode))
 764             file_error (NULL, TRUE, _ ("Destination \"%s\" must be a directory\n%s"), dest);
 765         else
 766         {
 767             file_op_context_t *ctx;
 768 
 769             ctx = file_op_context_new (OP_MOVE);
 770             file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 771             move_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
 772             file_op_context_destroy (ctx);
 773         }
 774 
 775         vfs_path_free (dest_vpath, TRUE);
 776     }
 777 
 778     g_free (dest);
 779 }
 780 
 781 /* --------------------------------------------------------------------------------------------- */
 782 
 783 #if 0
 784 static void
 785 tree_mkdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 786 {
 787     char old_dir[MC_MAXPATHLEN];
 788 
 789     if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
 790         return;
 791     /* FIXME
 792        mkdir_cmd (tree);
 793      */
 794     tree_rescan (tree);
 795     chdir (old_dir);
 796 }
 797 #endif
 798 
 799 /* --------------------------------------------------------------------------------------------- */
 800 
 801 static void
 802 tree_rmdir (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 803 {
 804     WTree *tree = data;
 805     file_op_context_t *ctx;
 806 
 807     if (tree->selected_ptr == NULL)
 808         return;
 809 
 810     if (confirm_delete)
 811     {
 812         char *buf;
 813         int result;
 814 
 815         buf = g_strdup_printf (_ ("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
 816 
 817         result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _ ("&Yes"), _ ("&No"));
 818         g_free (buf);
 819         if (result != 0)
 820             return;
 821     }
 822 
 823     ctx = file_op_context_new (OP_DELETE);
 824     file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 825     if (erase_dir (ctx, tree->selected_ptr->name) == FILE_CONT)
 826         tree_forget (tree);
 827     file_op_context_destroy (ctx);
 828 }
 829 
 830 /* --------------------------------------------------------------------------------------------- */
 831 
 832 static inline void
 833 tree_move_up (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 834 {
 835     tree_move_backward (tree, 1);
 836     show_tree (tree);
 837     maybe_chdir (tree);
 838 }
 839 
 840 /* --------------------------------------------------------------------------------------------- */
 841 
 842 static inline void
 843 tree_move_down (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 844 {
 845     tree_move_forward (tree, 1);
 846     show_tree (tree);
 847     maybe_chdir (tree);
 848 }
 849 
 850 /* --------------------------------------------------------------------------------------------- */
 851 
 852 static inline void
 853 tree_move_home (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 854 {
 855     tree_move_to_top (tree);
 856     show_tree (tree);
 857     maybe_chdir (tree);
 858 }
 859 
 860 /* --------------------------------------------------------------------------------------------- */
 861 
 862 static inline void
 863 tree_move_end (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 864 {
 865     tree_move_to_bottom (tree);
 866     show_tree (tree);
 867     maybe_chdir (tree);
 868 }
 869 
 870 /* --------------------------------------------------------------------------------------------- */
 871 
 872 static void
 873 tree_move_pgup (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 874 {
 875     tree_move_backward (tree, tlines (tree) - 1);
 876     show_tree (tree);
 877     maybe_chdir (tree);
 878 }
 879 
 880 /* --------------------------------------------------------------------------------------------- */
 881 
 882 static void
 883 tree_move_pgdn (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 884 {
 885     tree_move_forward (tree, tlines (tree) - 1);
 886     show_tree (tree);
 887     maybe_chdir (tree);
 888 }
 889 
 890 /* --------------------------------------------------------------------------------------------- */
 891 
 892 static gboolean
 893 tree_move_left (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 894 {
 895     gboolean v = FALSE;
 896 
 897     if (tree_navigation_flag)
 898     {
 899         v = tree_move_to_parent (tree);
 900         show_tree (tree);
 901         maybe_chdir (tree);
 902     }
 903 
 904     return v;
 905 }
 906 
 907 /* --------------------------------------------------------------------------------------------- */
 908 
 909 static gboolean
 910 tree_move_right (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 911 {
 912     gboolean v = FALSE;
 913 
 914     if (tree_navigation_flag)
 915     {
 916         tree_move_to_child (tree);
 917         show_tree (tree);
 918         maybe_chdir (tree);
 919         v = TRUE;
 920     }
 921 
 922     return v;
 923 }
 924 
 925 /* --------------------------------------------------------------------------------------------- */
 926 
 927 static void
 928 tree_start_search (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 929 {
 930     if (tree->searching)
 931     {
 932         if (tree->selected_ptr == tree->store->tree_last)
 933             tree_move_to_top (tree);
 934         else
 935         {
 936             gboolean i;
 937 
 938             /* set navigation mode temporarily to 'Static' because in
 939              * dynamic navigation mode tree_move_forward will not move
 940              * to a lower sublevel if necessary (sequent searches must
 941              * start with the directory followed the last found directory)
 942              */
 943             i = tree_navigation_flag;
 944             tree_navigation_flag = FALSE;
 945             tree_move_forward (tree, 1);
 946             tree_navigation_flag = i;
 947         }
 948         tree_do_search (tree, 0);
 949     }
 950     else
 951     {
 952         tree->searching = TRUE;
 953         g_string_set_size (tree->search_buffer, 0);
 954     }
 955 }
 956 
 957 /* --------------------------------------------------------------------------------------------- */
 958 
 959 static void
 960 tree_toggle_navig (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 961 {
 962     Widget *w = WIDGET (tree);
 963     WButtonBar *b;
 964 
 965     tree_navigation_flag = !tree_navigation_flag;
 966 
 967     b = buttonbar_find (DIALOG (w->owner));
 968     buttonbar_set_label (b, 4,
 969                          tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
 970                          w->keymap, w);
 971     widget_draw (WIDGET (b));
 972 }
 973 
 974 /* --------------------------------------------------------------------------------------------- */
 975 
 976 static void
 977 tree_help (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 978 {
 979     ev_help_t event_data = { NULL, "[Directory Tree]" };
 980 
 981     mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 982 }
 983 
 984 /* --------------------------------------------------------------------------------------------- */
 985 
 986 static cb_ret_t
 987 tree_execute_cmd (WTree *tree, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 988 {
 989     cb_ret_t res = MSG_HANDLED;
 990 
 991     if (command != CK_Search)
 992         tree->searching = FALSE;
 993 
 994     switch (command)
 995     {
 996     case CK_Help:
 997         tree_help ();
 998         break;
 999     case CK_Forget:
1000         tree_forget (tree);
1001         break;
1002     case CK_ToggleNavigation:
1003         tree_toggle_navig (tree);
1004         break;
1005     case CK_Copy:
1006         tree_copy (tree, "");
1007         break;
1008     case CK_Move:
1009         tree_move (tree, "");
1010         break;
1011     case CK_Up:
1012         tree_move_up (tree);
1013         break;
1014     case CK_Down:
1015         tree_move_down (tree);
1016         break;
1017     case CK_Top:
1018         tree_move_home (tree);
1019         break;
1020     case CK_Bottom:
1021         tree_move_end (tree);
1022         break;
1023     case CK_PageUp:
1024         tree_move_pgup (tree);
1025         break;
1026     case CK_PageDown:
1027         tree_move_pgdn (tree);
1028         break;
1029     case CK_Enter:
1030         tree_chdir_sel (tree);
1031         break;
1032     case CK_Reread:
1033         tree_rescan (tree);
1034         break;
1035     case CK_Search:
1036         tree_start_search (tree);
1037         break;
1038     case CK_Delete:
1039         tree_rmdir (tree);
1040         break;
1041     case CK_Quit:
1042         if (!tree->is_panel)
1043             dlg_close (DIALOG (WIDGET (tree)->owner));
1044         return res;
1045     default:
1046         res = MSG_NOT_HANDLED;
1047     }
1048 
1049     show_tree (tree);
1050 
1051     return res;
1052 }
1053 
1054 /* --------------------------------------------------------------------------------------------- */
1055 
1056 static cb_ret_t
1057 tree_key (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1058 {
1059     long command;
1060 
1061     if (is_abort_char (key))
1062     {
1063         if (tree->is_panel)
1064         {
1065             tree->searching = FALSE;
1066             show_tree (tree);
1067             return MSG_HANDLED;  // eat abort char
1068         }
1069         /* modal tree dialog: let upper layer see the
1070            abort character and close the dialog */
1071         return MSG_NOT_HANDLED;
1072     }
1073 
1074     if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1075     {
1076         tree_do_search (tree, key);
1077         show_tree (tree);
1078         return MSG_HANDLED;
1079     }
1080 
1081     command = widget_lookup_key (WIDGET (tree), key);
1082     switch (command)
1083     {
1084     case CK_IgnoreKey:
1085         break;
1086     case CK_Left:
1087         return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1088     case CK_Right:
1089         return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1090     default:
1091         tree_execute_cmd (tree, command);
1092         return MSG_HANDLED;
1093     }
1094 
1095     // Do not eat characters not meant for the tree below ' ' (e.g. C-l).
1096     if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1097     {
1098         tree_start_search (tree);
1099         tree_do_search (tree, key);
1100         return MSG_HANDLED;
1101     }
1102 
1103     return MSG_NOT_HANDLED;
1104 }
1105 
1106 /* --------------------------------------------------------------------------------------------- */
1107 
1108 static void
1109 tree_frame (WDialog *h, WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1110 {
1111     Widget *w = WIDGET (tree);
1112 
1113     (void) h;
1114 
1115     tty_setcolor (NORMAL_COLOR);
1116     widget_erase (w);
1117     if (tree->is_panel)
1118     {
1119         const char *title = _ ("Directory tree");
1120         const int len = str_term_width1 (title);
1121 
1122         tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
1123 
1124         widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
1125         tty_printf (" %s ", title);
1126 
1127         if (panels_options.show_mini_info)
1128         {
1129             int y;
1130 
1131             y = w->rect.lines - 3;
1132             widget_gotoyx (w, y, 0);
1133             tty_print_alt_char (ACS_LTEE, FALSE);
1134             widget_gotoyx (w, y, w->rect.cols - 1);
1135             tty_print_alt_char (ACS_RTEE, FALSE);
1136             tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, 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]  */