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             // Sub level directory
 372             tty_set_alt_charset (TRUE);
 373 
 374             // Output branch parts
 375             for (j = 0; j < current->sublevel - topsublevel - 1; j++)
 376             {
 377                 if (tree_cols - 8 - 3 * j < 9)
 378                     break;
 379                 tty_print_char (' ');
 380                 if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
 381                     tty_print_char (ACS_VLINE);
 382                 else
 383                     tty_print_char (' ');
 384                 tty_print_char (' ');
 385             }
 386             tty_print_char (' ');
 387             j++;
 388             if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
 389                 tty_print_char (ACS_LLCORNER);
 390             else
 391                 tty_print_char (ACS_LTEE);
 392             tty_print_char (ACS_HLINE);
 393             tty_set_alt_charset (FALSE);
 394 
 395             // Show sub-name
 396             tty_print_char (' ');
 397             tty_print_string (
 398                 str_fit_to_term (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
 399         }
 400 
 401         // Calculate the next value for current
 402         current = current->next;
 403         if (tree_navigation_flag)
 404             for (; current != NULL; current = current->next)
 405             {
 406                 if (current->sublevel < tree->selected_ptr->sublevel)
 407                 {
 408                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 409                                             vfs_path_len (current->name)))
 410                         break;
 411                 }
 412                 else if (current->sublevel == tree->selected_ptr->sublevel)
 413                 {
 414                     const char *cname;
 415 
 416                     cname = vfs_path_as_str (current->name);
 417                     for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 418                         ;
 419                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 420                         break;
 421                 }
 422                 else if (current->sublevel == tree->selected_ptr->sublevel + 1
 423                          && vfs_path_len (tree->selected_ptr->name) > 1)
 424                 {
 425                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 426                                             vfs_path_len (tree->selected_ptr->name)))
 427                         break;
 428                 }
 429             }
 430     }
 431 
 432     tree_show_mini_info (tree, tree_lines, tree_cols);
 433 }
 434 
 435 /* --------------------------------------------------------------------------------------------- */
 436 
 437 static void
 438 tree_check_focus (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 439 {
 440     if (tree->topdiff < 3)
 441         tree->topdiff = 3;
 442     else if (tree->topdiff >= tlines (tree) - 3)
 443         tree->topdiff = tlines (tree) - 3 - 1;
 444 }
 445 
 446 /* --------------------------------------------------------------------------------------------- */
 447 
 448 static void
 449 tree_move_backward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 450 {
 451     if (!tree_navigation_flag)
 452         tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
 453     else
 454     {
 455         tree_entry *current;
 456         int j = 0;
 457 
 458         current = tree->selected_ptr;
 459         while (j < i && current->prev != NULL
 460                && current->prev->sublevel >= tree->selected_ptr->sublevel)
 461         {
 462             current = current->prev;
 463             if (current->sublevel == tree->selected_ptr->sublevel)
 464             {
 465                 tree->selected_ptr = current;
 466                 j++;
 467             }
 468         }
 469         i = j;
 470     }
 471 
 472     tree->topdiff -= i;
 473     tree_check_focus (tree);
 474 }
 475 
 476 /* --------------------------------------------------------------------------------------------- */
 477 
 478 static void
 479 tree_move_forward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 480 {
 481     if (!tree_navigation_flag)
 482         tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
 483     else
 484     {
 485         tree_entry *current;
 486         int j = 0;
 487 
 488         current = tree->selected_ptr;
 489         while (j < i && current->next != NULL
 490                && current->next->sublevel >= tree->selected_ptr->sublevel)
 491         {
 492             current = current->next;
 493             if (current->sublevel == tree->selected_ptr->sublevel)
 494             {
 495                 tree->selected_ptr = current;
 496                 j++;
 497             }
 498         }
 499         i = j;
 500     }
 501 
 502     tree->topdiff += i;
 503     tree_check_focus (tree);
 504 }
 505 
 506 /* --------------------------------------------------------------------------------------------- */
 507 
 508 static void
 509 tree_move_to_child (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 510 {
 511     tree_entry *current;
 512 
 513     // Do we have a starting point?
 514     if (tree->selected_ptr == NULL)
 515         return;
 516 
 517     // Take the next entry
 518     current = tree->selected_ptr->next;
 519     // Is it the child of the selected entry
 520     if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 521     {
 522         // Yes -> select this entry
 523         tree->selected_ptr = current;
 524         tree->topdiff++;
 525         tree_check_focus (tree);
 526     }
 527     else
 528     {
 529         // No -> rescan and try again
 530         tree_rescan (tree);
 531         current = tree->selected_ptr->next;
 532         if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 533         {
 534             tree->selected_ptr = current;
 535             tree->topdiff++;
 536             tree_check_focus (tree);
 537         }
 538     }
 539 }
 540 
 541 /* --------------------------------------------------------------------------------------------- */
 542 
 543 static gboolean
 544 tree_move_to_parent (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 545 {
 546     tree_entry *current;
 547     tree_entry *old;
 548 
 549     if (tree->selected_ptr == NULL)
 550         return FALSE;
 551 
 552     old = tree->selected_ptr;
 553 
 554     for (current = tree->selected_ptr->prev;
 555          current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
 556          current = current->prev)
 557         tree->topdiff--;
 558 
 559     if (current == NULL)
 560         current = tree->store->tree_first;
 561     tree->selected_ptr = current;
 562     tree_check_focus (tree);
 563     return tree->selected_ptr != old;
 564 }
 565 
 566 /* --------------------------------------------------------------------------------------------- */
 567 
 568 static void
 569 tree_move_to_top (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 570 {
 571     tree->selected_ptr = tree->store->tree_first;
 572     tree->topdiff = 0;
 573 }
 574 
 575 /* --------------------------------------------------------------------------------------------- */
 576 
 577 static void
 578 tree_move_to_bottom (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 579 {
 580     tree->selected_ptr = tree->store->tree_last;
 581     tree->topdiff = tlines (tree) - 3 - 1;
 582 }
 583 
 584 /* --------------------------------------------------------------------------------------------- */
 585 
 586 static void
 587 tree_chdir_sel (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 588 {
 589     if (tree->is_panel)
 590     {
 591         WPanel *p;
 592 
 593         p = change_panel ();
 594 
 595         if (panel_cd (p, tree->selected_ptr->name, cd_exact))
 596             select_item (p);
 597         else
 598             cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
 599 
 600         widget_draw (WIDGET (p));
 601         (void) change_panel ();
 602         show_tree (tree);
 603     }
 604     else
 605     {
 606         WDialog *h = DIALOG (WIDGET (tree)->owner);
 607 
 608         h->ret_value = B_ENTER;
 609         dlg_close (h);
 610     }
 611 }
 612 
 613 /* --------------------------------------------------------------------------------------------- */
 614 
 615 static void
 616 maybe_chdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 617 {
 618     if (xtree_mode && tree->is_panel && is_idle ())
 619         tree_chdir_sel (tree);
 620 }
 621 
 622 /* --------------------------------------------------------------------------------------------- */
 623 /** Search tree for text */
 624 
 625 static gboolean
 626 search_tree (WTree *tree, const GString *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 627 {
 628     tree_entry *current = tree->selected_ptr;
 629     gboolean wrapped = FALSE;
 630     gboolean found = FALSE;
 631 
 632     while (!found && (!wrapped || current != tree->selected_ptr))
 633         if (strncmp (current->subname, text->str, text->len) == 0)
 634         {
 635             tree->selected_ptr = current;
 636             found = TRUE;
 637         }
 638         else
 639         {
 640             current = current->next;
 641             if (current == NULL)
 642             {
 643                 current = tree->store->tree_first;
 644                 wrapped = TRUE;
 645             }
 646 
 647             tree->topdiff++;
 648         }
 649 
 650     tree_check_focus (tree);
 651     return found;
 652 }
 653 
 654 /* --------------------------------------------------------------------------------------------- */
 655 
 656 static void
 657 tree_do_search (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 658 {
 659     // TODO: support multi-byte characters, see do_search() in panel.c
 660 
 661     if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
 662         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 663     else if (key != 0)
 664         g_string_append_c (tree->search_buffer, (gchar) key);
 665 
 666     if (!search_tree (tree, tree->search_buffer))
 667         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 668 
 669     show_tree (tree);
 670     maybe_chdir (tree);
 671 }
 672 
 673 /* --------------------------------------------------------------------------------------------- */
 674 
 675 static void
 676 tree_rescan (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 677 {
 678     WTree *tree = data;
 679     vfs_path_t *old_vpath;
 680 
 681     old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
 682     if (old_vpath == NULL)
 683         return;
 684 
 685     if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
 686     {
 687         int ret;
 688 
 689         tree_store_rescan (tree->selected_ptr->name);
 690         ret = mc_chdir (old_vpath);
 691         (void) ret;
 692     }
 693     vfs_path_free (old_vpath, TRUE);
 694 }
 695 
 696 /* --------------------------------------------------------------------------------------------- */
 697 
 698 static void
 699 tree_forget (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 700 {
 701     WTree *tree = data;
 702 
 703     if (tree->selected_ptr != NULL)
 704         tree_remove_entry (tree, tree->selected_ptr->name);
 705 }
 706 
 707 /* --------------------------------------------------------------------------------------------- */
 708 
 709 static void
 710 tree_copy (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 711 {
 712     char msg[BUF_MEDIUM];
 713     char *dest;
 714 
 715     if (tree->selected_ptr == NULL)
 716         return;
 717 
 718     g_snprintf (msg, sizeof (msg), _ ("Copy \"%s\" directory to:"),
 719                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 720     dest = input_expand_dialog (Q_ ("DialogTitle|Copy"), msg, MC_HISTORY_FM_TREE_COPY, default_dest,
 721                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 722 
 723     if (dest != NULL && *dest != '\0')
 724     {
 725         file_op_context_t *ctx;
 726 
 727         ctx = file_op_context_new (OP_COPY);
 728         file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
 729         ctx->ask_overwrite = FALSE;
 730         copy_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE, FALSE,
 731                       NULL);
 732         file_op_context_destroy (ctx);
 733     }
 734 
 735     g_free (dest);
 736 }
 737 
 738 /* --------------------------------------------------------------------------------------------- */
 739 
 740 static void
 741 tree_move (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 742 {
 743     char msg[BUF_MEDIUM];
 744     char *dest;
 745 
 746     if (tree->selected_ptr == NULL)
 747         return;
 748 
 749     g_snprintf (msg, sizeof (msg), _ ("Move \"%s\" directory to:"),
 750                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 751     dest = input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
 752                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 753 
 754     if (dest != NULL && *dest != '\0')
 755     {
 756         vfs_path_t *dest_vpath;
 757         struct stat buf;
 758 
 759         dest_vpath = vfs_path_from_str (dest);
 760 
 761         if (mc_stat (dest_vpath, &buf) != 0)
 762             file_error_message (_ ("Cannot stat the destination\n%s"), dest);
 763         else if (!S_ISDIR (buf.st_mode))
 764         {
 765             errno = ENOTDIR;
 766             file_error_message (_ ("Destination\n%s\nmust be a directory"), dest);
 767         }
 768         else
 769         {
 770             file_op_context_t *ctx;
 771 
 772             ctx = file_op_context_new (OP_MOVE);
 773             file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 774             move_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
 775             file_op_context_destroy (ctx);
 776         }
 777 
 778         vfs_path_free (dest_vpath, TRUE);
 779     }
 780 
 781     g_free (dest);
 782 }
 783 
 784 /* --------------------------------------------------------------------------------------------- */
 785 
 786 #if 0
 787 static void
 788 tree_mkdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 789 {
 790     char old_dir[MC_MAXPATHLEN];
 791 
 792     if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
 793         return;
 794     /* FIXME
 795        mkdir_cmd (tree);
 796      */
 797     tree_rescan (tree);
 798     chdir (old_dir);
 799 }
 800 #endif
 801 
 802 /* --------------------------------------------------------------------------------------------- */
 803 
 804 static void
 805 tree_rmdir (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 806 {
 807     WTree *tree = data;
 808     file_op_context_t *ctx;
 809 
 810     if (tree->selected_ptr == NULL)
 811         return;
 812 
 813     if (confirm_delete)
 814     {
 815         char *buf;
 816         int result;
 817 
 818         buf = g_strdup_printf (_ ("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
 819 
 820         result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _ ("&Yes"), _ ("&No"));
 821         g_free (buf);
 822         if (result != 0)
 823             return;
 824     }
 825 
 826     ctx = file_op_context_new (OP_DELETE);
 827     file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 828     if (erase_dir (ctx, tree->selected_ptr->name) == FILE_CONT)
 829         tree_forget (tree);
 830     file_op_context_destroy (ctx);
 831 }
 832 
 833 /* --------------------------------------------------------------------------------------------- */
 834 
 835 static inline void
 836 tree_move_up (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 837 {
 838     tree_move_backward (tree, 1);
 839     show_tree (tree);
 840     maybe_chdir (tree);
 841 }
 842 
 843 /* --------------------------------------------------------------------------------------------- */
 844 
 845 static inline void
 846 tree_move_down (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 847 {
 848     tree_move_forward (tree, 1);
 849     show_tree (tree);
 850     maybe_chdir (tree);
 851 }
 852 
 853 /* --------------------------------------------------------------------------------------------- */
 854 
 855 static inline void
 856 tree_move_home (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 857 {
 858     tree_move_to_top (tree);
 859     show_tree (tree);
 860     maybe_chdir (tree);
 861 }
 862 
 863 /* --------------------------------------------------------------------------------------------- */
 864 
 865 static inline void
 866 tree_move_end (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 867 {
 868     tree_move_to_bottom (tree);
 869     show_tree (tree);
 870     maybe_chdir (tree);
 871 }
 872 
 873 /* --------------------------------------------------------------------------------------------- */
 874 
 875 static void
 876 tree_move_pgup (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 877 {
 878     tree_move_backward (tree, tlines (tree) - 1);
 879     show_tree (tree);
 880     maybe_chdir (tree);
 881 }
 882 
 883 /* --------------------------------------------------------------------------------------------- */
 884 
 885 static void
 886 tree_move_pgdn (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 887 {
 888     tree_move_forward (tree, tlines (tree) - 1);
 889     show_tree (tree);
 890     maybe_chdir (tree);
 891 }
 892 
 893 /* --------------------------------------------------------------------------------------------- */
 894 
 895 static gboolean
 896 tree_move_left (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 897 {
 898     gboolean v = FALSE;
 899 
 900     if (tree_navigation_flag)
 901     {
 902         v = tree_move_to_parent (tree);
 903         show_tree (tree);
 904         maybe_chdir (tree);
 905     }
 906 
 907     return v;
 908 }
 909 
 910 /* --------------------------------------------------------------------------------------------- */
 911 
 912 static gboolean
 913 tree_move_right (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 914 {
 915     gboolean v = FALSE;
 916 
 917     if (tree_navigation_flag)
 918     {
 919         tree_move_to_child (tree);
 920         show_tree (tree);
 921         maybe_chdir (tree);
 922         v = TRUE;
 923     }
 924 
 925     return v;
 926 }
 927 
 928 /* --------------------------------------------------------------------------------------------- */
 929 
 930 static void
 931 tree_start_search (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 932 {
 933     if (tree->searching)
 934     {
 935         if (tree->selected_ptr == tree->store->tree_last)
 936             tree_move_to_top (tree);
 937         else
 938         {
 939             gboolean i;
 940 
 941             /* set navigation mode temporarily to 'Static' because in
 942              * dynamic navigation mode tree_move_forward will not move
 943              * to a lower sublevel if necessary (sequent searches must
 944              * start with the directory followed the last found directory)
 945              */
 946             i = tree_navigation_flag;
 947             tree_navigation_flag = FALSE;
 948             tree_move_forward (tree, 1);
 949             tree_navigation_flag = i;
 950         }
 951         tree_do_search (tree, 0);
 952     }
 953     else
 954     {
 955         tree->searching = TRUE;
 956         g_string_set_size (tree->search_buffer, 0);
 957     }
 958 }
 959 
 960 /* --------------------------------------------------------------------------------------------- */
 961 
 962 static void
 963 tree_toggle_navig (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 964 {
 965     Widget *w = WIDGET (tree);
 966     WButtonBar *b;
 967 
 968     tree_navigation_flag = !tree_navigation_flag;
 969 
 970     b = buttonbar_find (DIALOG (w->owner));
 971     buttonbar_set_label (b, 4,
 972                          tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
 973                          w->keymap, w);
 974     widget_draw (WIDGET (b));
 975 }
 976 
 977 /* --------------------------------------------------------------------------------------------- */
 978 
 979 static void
 980 tree_help (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 981 {
 982     ev_help_t event_data = { NULL, "[Directory Tree]" };
 983 
 984     mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 985 }
 986 
 987 /* --------------------------------------------------------------------------------------------- */
 988 
 989 static cb_ret_t
 990 tree_execute_cmd (WTree *tree, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 991 {
 992     cb_ret_t res = MSG_HANDLED;
 993 
 994     if (command != CK_Search)
 995         tree->searching = FALSE;
 996 
 997     switch (command)
 998     {
 999     case CK_Help:
1000         tree_help ();
1001         break;
1002     case CK_Forget:
1003         tree_forget (tree);
1004         break;
1005     case CK_ToggleNavigation:
1006         tree_toggle_navig (tree);
1007         break;
1008     case CK_Copy:
1009         tree_copy (tree, "");
1010         break;
1011     case CK_Move:
1012         tree_move (tree, "");
1013         break;
1014     case CK_Up:
1015         tree_move_up (tree);
1016         break;
1017     case CK_Down:
1018         tree_move_down (tree);
1019         break;
1020     case CK_Top:
1021         tree_move_home (tree);
1022         break;
1023     case CK_Bottom:
1024         tree_move_end (tree);
1025         break;
1026     case CK_PageUp:
1027         tree_move_pgup (tree);
1028         break;
1029     case CK_PageDown:
1030         tree_move_pgdn (tree);
1031         break;
1032     case CK_Enter:
1033         tree_chdir_sel (tree);
1034         break;
1035     case CK_Reread:
1036         tree_rescan (tree);
1037         break;
1038     case CK_Search:
1039         tree_start_search (tree);
1040         break;
1041     case CK_Delete:
1042         tree_rmdir (tree);
1043         break;
1044     case CK_Quit:
1045         if (!tree->is_panel)
1046             dlg_close (DIALOG (WIDGET (tree)->owner));
1047         return res;
1048     default:
1049         res = MSG_NOT_HANDLED;
1050     }
1051 
1052     show_tree (tree);
1053 
1054     return res;
1055 }
1056 
1057 /* --------------------------------------------------------------------------------------------- */
1058 
1059 static cb_ret_t
1060 tree_key (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1061 {
1062     long command;
1063 
1064     if (is_abort_char (key))
1065     {
1066         if (tree->is_panel)
1067         {
1068             tree->searching = FALSE;
1069             show_tree (tree);
1070             return MSG_HANDLED;  // eat abort char
1071         }
1072         /* modal tree dialog: let upper layer see the
1073            abort character and close the dialog */
1074         return MSG_NOT_HANDLED;
1075     }
1076 
1077     if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1078     {
1079         tree_do_search (tree, key);
1080         show_tree (tree);
1081         return MSG_HANDLED;
1082     }
1083 
1084     command = widget_lookup_key (WIDGET (tree), key);
1085     switch (command)
1086     {
1087     case CK_IgnoreKey:
1088         break;
1089     case CK_Left:
1090         return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1091     case CK_Right:
1092         return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1093     default:
1094         tree_execute_cmd (tree, command);
1095         return MSG_HANDLED;
1096     }
1097 
1098     // Do not eat characters not meant for the tree below ' ' (e.g. C-l).
1099     if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1100     {
1101         tree_start_search (tree);
1102         tree_do_search (tree, key);
1103         return MSG_HANDLED;
1104     }
1105 
1106     return MSG_NOT_HANDLED;
1107 }
1108 
1109 /* --------------------------------------------------------------------------------------------- */
1110 
1111 static void
1112 tree_frame (WDialog *h, WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1113 {
1114     Widget *w = WIDGET (tree);
1115 
1116     (void) h;
1117 
1118     tty_setcolor (NORMAL_COLOR);
1119     widget_erase (w);
1120     if (tree->is_panel)
1121     {
1122         const char *title = _ ("Directory tree");
1123         const int len = str_term_width1 (title);
1124 
1125         tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
1126 
1127         widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
1128         tty_printf (" %s ", title);
1129 
1130         if (panels_options.show_mini_info)
1131         {
1132             int y;
1133 
1134             y = w->rect.lines - 3;
1135             widget_gotoyx (w, y, 0);
1136             tty_print_alt_char (ACS_LTEE, FALSE);
1137             widget_gotoyx (w, y, w->rect.cols - 1);
1138             tty_print_alt_char (ACS_RTEE, FALSE);
1139             tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
1140         }
1141     }
1142 }
1143 
1144 /* --------------------------------------------------------------------------------------------- */
1145 
1146 static cb_ret_t
1147 tree_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1148 {
1149     WTree *tree = (WTree *) w;
1150     WDialog *h = DIALOG (w->owner);
1151     WButtonBar *b;
1152 
1153     switch (msg)
1154     {
1155     case MSG_DRAW:
1156         tree_frame (h, tree);
1157         show_tree (tree);
1158         if (widget_get_state (w, WST_FOCUSED))
1159         {
1160             b = buttonbar_find (h);
1161             widget_draw (WIDGET (b));
1162         }
1163         return MSG_HANDLED;
1164 
1165     case MSG_FOCUS:
1166         b = buttonbar_find (h);
1167         buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w);
1168         buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w);
1169         buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w);
1170         buttonbar_set_label (
1171             b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
1172             w->keymap, w);
1173         buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
1174         buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w);
1175 #if 0
1176         // FIXME: mkdir is currently defunct 
1177         buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w);
1178 #else
1179         buttonbar_clear_label (b, 7, w);
1180 #endif
1181         buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w);
1182 
1183         return MSG_HANDLED;
1184 
1185     case MSG_UNFOCUS:
1186         tree->searching = FALSE;
1187         return MSG_HANDLED;
1188 
1189     case MSG_KEY:
1190         return tree_key (tree, parm);
1191 
1192     case MSG_ACTION:
1193         // command from buttonbar
1194         return tree_execute_cmd (tree, parm);
1195 
1196     case MSG_DESTROY:
1197         tree_destroy (tree);
1198         return MSG_HANDLED;
1199 
1200     default:
1201         return widget_default_callback (w, sender, msg, parm, data);
1202     }
1203 }
1204 
1205 /* --------------------------------------------------------------------------------------------- */
1206 
1207 /**
1208  * Mouse callback
1209  */
1210 static void
1211 tree_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
1212 {
1213     WTree *tree = (WTree *) w;
1214     int y;
1215 
1216     y = event->y;
1217     if (tree->is_panel)
1218         y--;
1219 
1220     switch (msg)
1221     {
1222     case MSG_MOUSE_DOWN:
1223         // rest of the upper frame - call menu
1224         if (tree->is_panel && event->y == WIDGET (w->owner)->rect.y)
1225         {
1226             // return MOU_UNHANDLED
1227             event->result.abort = TRUE;
1228         }
1229         else if (!widget_get_state (w, WST_FOCUSED))
1230             (void) change_panel ();
1231         break;
1232 
1233     case MSG_MOUSE_CLICK:
1234     {
1235         int lines;
1236 
1237         lines = tlines (tree);
1238 
1239         if (y < 0)
1240         {
1241             tree_move_backward (tree, lines - 1);
1242             show_tree (tree);
1243         }
1244         else if (y >= lines)
1245         {
1246             tree_move_forward (tree, lines - 1);
1247             show_tree (tree);
1248         }
1249         else if ((event->count & GPM_DOUBLE) != 0)
1250         {
1251             if (tree->tree_shown[y] != NULL)
1252             {
1253                 tree->selected_ptr = tree->tree_shown[y];
1254                 tree->topdiff = y;
1255             }
1256 
1257             tree_chdir_sel (tree);
1258         }
1259     }
1260     break;
1261 
1262     case MSG_MOUSE_SCROLL_UP:
1263     case MSG_MOUSE_SCROLL_DOWN:
1264         // TODO: Ticket #2218
1265         break;
1266 
1267     default:
1268         break;
1269     }
1270 }
1271 
1272 /* --------------------------------------------------------------------------------------------- */
1273 /*** public functions ****************************************************************************/
1274 /* --------------------------------------------------------------------------------------------- */
1275 
1276 WTree *
1277 tree_new (const WRect *r, gboolean is_panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1278 {
1279     WTree *tree;
1280     Widget *w;
1281 
1282     tree = g_new (WTree, 1);
1283 
1284     w = WIDGET (tree);
1285     widget_init (w, r, tree_callback, tree_mouse_callback);
1286     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
1287     w->keymap = tree_map;
1288 
1289     tree->is_panel = is_panel;
1290     tree->selected_ptr = NULL;
1291 
1292     tree->store = tree_store_get ();
1293     tree_store_add_entry_remove_hook (remove_callback, tree);
1294     tree->tree_shown = NULL;
1295     tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN);
1296     tree->topdiff = w->rect.lines / 2;
1297     tree->searching = FALSE;
1298 
1299     load_tree (tree);
1300     return tree;
1301 }
1302 
1303 /* --------------------------------------------------------------------------------------------- */
1304 
1305 void
1306 tree_chdir (WTree *tree, const vfs_path_t *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
1307 {
1308     tree_entry *current;
1309 
1310     current = tree_store_whereis (dir);
1311     if (current != NULL)
1312     {
1313         tree->selected_ptr = current;
1314         tree_check_focus (tree);
1315     }
1316 }
1317 
1318 /* --------------------------------------------------------------------------------------------- */
1319 /** Return name of the currently selected entry */
1320 
1321 const vfs_path_t *
1322 tree_selected_name (const WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1323 {
1324     return tree->selected_ptr->name;
1325 }
1326 
1327 /* --------------------------------------------------------------------------------------------- */
1328 
1329 void
1330 sync_tree (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1331 {
1332     tree_chdir (the_tree, vpath);
1333 }
1334 
1335 /* --------------------------------------------------------------------------------------------- */
1336 
1337 WTree *
1338 find_tree (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1339 {
1340     return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
1341 }
1342 
1343 /* --------------------------------------------------------------------------------------------- */

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