Manual pages: mc • mcdiff • mcedit • mcview

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-2026
  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 "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 (CORE_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 ? CORE_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 ? CORE_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 ? CORE_SELECTED_COLOR : CORE_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             // Output branch parts
 371             for (j = 0; j < current->sublevel - topsublevel - 1; j++)
 372             {
 373                 if (tree_cols - 8 - 3 * j < 9)
 374                     break;
 375                 tty_print_char (' ');
 376                 if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
 377                     tty_print_char (mc_tty_frm[MC_TTY_FRM_VERT]);
 378                 else
 379                     tty_print_char (' ');
 380                 tty_print_char (' ');
 381             }
 382             tty_print_char (' ');
 383             j++;
 384             if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
 385                 tty_print_char (mc_tty_frm[MC_TTY_FRM_LEFTBOTTOM]);
 386             else
 387                 tty_print_char (mc_tty_frm[MC_TTY_FRM_LEFTMIDDLE]);
 388             tty_print_char (mc_tty_frm[MC_TTY_FRM_HORIZ]);
 389 
 390             // Show sub-name
 391             tty_print_char (' ');
 392             tty_print_string (
 393                 str_fit_to_term (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
 394         }
 395 
 396         // Calculate the next value for current
 397         current = current->next;
 398         if (tree_navigation_flag)
 399             for (; current != NULL; current = current->next)
 400             {
 401                 if (current->sublevel < tree->selected_ptr->sublevel)
 402                 {
 403                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 404                                             vfs_path_len (current->name)))
 405                         break;
 406                 }
 407                 else if (current->sublevel == tree->selected_ptr->sublevel)
 408                 {
 409                     const char *cname;
 410 
 411                     cname = vfs_path_as_str (current->name);
 412                     for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 413                         ;
 414                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 415                         break;
 416                 }
 417                 else if (current->sublevel == tree->selected_ptr->sublevel + 1
 418                          && vfs_path_len (tree->selected_ptr->name) > 1)
 419                 {
 420                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 421                                             vfs_path_len (tree->selected_ptr->name)))
 422                         break;
 423                 }
 424             }
 425     }
 426 
 427     tree_show_mini_info (tree, tree_lines, tree_cols);
 428 }
 429 
 430 /* --------------------------------------------------------------------------------------------- */
 431 
 432 static void
 433 tree_check_focus (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 434 {
 435     if (tree->topdiff < 3)
 436         tree->topdiff = 3;
 437     else if (tree->topdiff >= tlines (tree) - 3)
 438         tree->topdiff = tlines (tree) - 3 - 1;
 439 }
 440 
 441 /* --------------------------------------------------------------------------------------------- */
 442 
 443 static void
 444 tree_move_backward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 445 {
 446     if (!tree_navigation_flag)
 447         tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
 448     else
 449     {
 450         tree_entry *current;
 451         int j = 0;
 452 
 453         current = tree->selected_ptr;
 454         while (j < i && current->prev != NULL
 455                && current->prev->sublevel >= tree->selected_ptr->sublevel)
 456         {
 457             current = current->prev;
 458             if (current->sublevel == tree->selected_ptr->sublevel)
 459             {
 460                 tree->selected_ptr = current;
 461                 j++;
 462             }
 463         }
 464         i = j;
 465     }
 466 
 467     tree->topdiff -= i;
 468     tree_check_focus (tree);
 469 }
 470 
 471 /* --------------------------------------------------------------------------------------------- */
 472 
 473 static void
 474 tree_move_forward (WTree *tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 475 {
 476     if (!tree_navigation_flag)
 477         tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
 478     else
 479     {
 480         tree_entry *current;
 481         int j = 0;
 482 
 483         current = tree->selected_ptr;
 484         while (j < i && current->next != NULL
 485                && current->next->sublevel >= tree->selected_ptr->sublevel)
 486         {
 487             current = current->next;
 488             if (current->sublevel == tree->selected_ptr->sublevel)
 489             {
 490                 tree->selected_ptr = current;
 491                 j++;
 492             }
 493         }
 494         i = j;
 495     }
 496 
 497     tree->topdiff += i;
 498     tree_check_focus (tree);
 499 }
 500 
 501 /* --------------------------------------------------------------------------------------------- */
 502 
 503 static void
 504 tree_move_to_child (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 505 {
 506     tree_entry *current;
 507 
 508     // Do we have a starting point?
 509     if (tree->selected_ptr == NULL)
 510         return;
 511 
 512     // Take the next entry
 513     current = tree->selected_ptr->next;
 514     // Is it the child of the selected entry
 515     if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 516     {
 517         // Yes -> select this entry
 518         tree->selected_ptr = current;
 519         tree->topdiff++;
 520         tree_check_focus (tree);
 521     }
 522     else
 523     {
 524         // No -> rescan and try again
 525         tree_rescan (tree);
 526         current = tree->selected_ptr->next;
 527         if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 528         {
 529             tree->selected_ptr = current;
 530             tree->topdiff++;
 531             tree_check_focus (tree);
 532         }
 533     }
 534 }
 535 
 536 /* --------------------------------------------------------------------------------------------- */
 537 
 538 static gboolean
 539 tree_move_to_parent (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 540 {
 541     tree_entry *current;
 542     tree_entry *old;
 543 
 544     if (tree->selected_ptr == NULL)
 545         return FALSE;
 546 
 547     old = tree->selected_ptr;
 548 
 549     for (current = tree->selected_ptr->prev;
 550          current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
 551          current = current->prev)
 552         tree->topdiff--;
 553 
 554     if (current == NULL)
 555         current = tree->store->tree_first;
 556     tree->selected_ptr = current;
 557     tree_check_focus (tree);
 558     return tree->selected_ptr != old;
 559 }
 560 
 561 /* --------------------------------------------------------------------------------------------- */
 562 
 563 static void
 564 tree_move_to_top (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 565 {
 566     tree->selected_ptr = tree->store->tree_first;
 567     tree->topdiff = 0;
 568 }
 569 
 570 /* --------------------------------------------------------------------------------------------- */
 571 
 572 static void
 573 tree_move_to_bottom (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 574 {
 575     tree->selected_ptr = tree->store->tree_last;
 576     tree->topdiff = tlines (tree) - 3 - 1;
 577 }
 578 
 579 /* --------------------------------------------------------------------------------------------- */
 580 
 581 static void
 582 tree_chdir_sel (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 583 {
 584     if (tree->is_panel)
 585     {
 586         WPanel *p;
 587 
 588         p = change_panel ();
 589 
 590         if (panel_cd (p, tree->selected_ptr->name, cd_exact))
 591             select_item (p);
 592         else
 593             cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
 594 
 595         widget_draw (WIDGET (p));
 596         (void) change_panel ();
 597         show_tree (tree);
 598     }
 599     else
 600     {
 601         WDialog *h = DIALOG (WIDGET (tree)->owner);
 602 
 603         h->ret_value = B_ENTER;
 604         dlg_close (h);
 605     }
 606 }
 607 
 608 /* --------------------------------------------------------------------------------------------- */
 609 
 610 static void
 611 maybe_chdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 612 {
 613     if (xtree_mode && tree->is_panel && is_idle ())
 614         tree_chdir_sel (tree);
 615 }
 616 
 617 /* --------------------------------------------------------------------------------------------- */
 618 /** Search tree for text */
 619 
 620 static gboolean
 621 search_tree (WTree *tree, const GString *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 622 {
 623     tree_entry *current = tree->selected_ptr;
 624     gboolean wrapped = FALSE;
 625     gboolean found = FALSE;
 626 
 627     while (!found && (!wrapped || current != tree->selected_ptr))
 628         if (strncmp (current->subname, text->str, text->len) == 0)
 629         {
 630             tree->selected_ptr = current;
 631             found = TRUE;
 632         }
 633         else
 634         {
 635             current = current->next;
 636             if (current == NULL)
 637             {
 638                 current = tree->store->tree_first;
 639                 wrapped = TRUE;
 640             }
 641 
 642             tree->topdiff++;
 643         }
 644 
 645     tree_check_focus (tree);
 646     return found;
 647 }
 648 
 649 /* --------------------------------------------------------------------------------------------- */
 650 
 651 static void
 652 tree_do_search (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 653 {
 654     // TODO: support multi-byte characters, see do_search() in panel.c
 655 
 656     if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
 657         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 658     else if (key != 0)
 659         g_string_append_c (tree->search_buffer, (gchar) key);
 660 
 661     if (!search_tree (tree, tree->search_buffer))
 662         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 663 
 664     show_tree (tree);
 665     maybe_chdir (tree);
 666 }
 667 
 668 /* --------------------------------------------------------------------------------------------- */
 669 
 670 static void
 671 tree_rescan (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 672 {
 673     WTree *tree = data;
 674     vfs_path_t *old_vpath;
 675 
 676     old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
 677     if (old_vpath == NULL)
 678         return;
 679 
 680     if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
 681     {
 682         int ret;
 683 
 684         tree_store_rescan (tree->selected_ptr->name);
 685         ret = mc_chdir (old_vpath);
 686         (void) ret;
 687     }
 688     vfs_path_free (old_vpath, TRUE);
 689 }
 690 
 691 /* --------------------------------------------------------------------------------------------- */
 692 
 693 static void
 694 tree_forget (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 695 {
 696     WTree *tree = data;
 697 
 698     if (tree->selected_ptr != NULL)
 699         tree_remove_entry (tree, tree->selected_ptr->name);
 700 }
 701 
 702 /* --------------------------------------------------------------------------------------------- */
 703 
 704 static void
 705 tree_copy (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 706 {
 707     char msg[BUF_MEDIUM];
 708     char *dest;
 709 
 710     if (tree->selected_ptr == NULL)
 711         return;
 712 
 713     g_snprintf (msg, sizeof (msg), _ ("Copy \"%s\" directory to:"),
 714                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 715     dest = input_expand_dialog (Q_ ("DialogTitle|Copy"), msg, MC_HISTORY_FM_TREE_COPY, default_dest,
 716                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 717 
 718     if (dest != NULL && *dest != '\0')
 719     {
 720         file_op_context_t *ctx;
 721 
 722         ctx = file_op_context_new (OP_COPY);
 723         file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
 724         ctx->ask_overwrite = FALSE;
 725         copy_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE, FALSE,
 726                       NULL);
 727         file_op_context_destroy (ctx);
 728     }
 729 
 730     g_free (dest);
 731 }
 732 
 733 /* --------------------------------------------------------------------------------------------- */
 734 
 735 static void
 736 tree_move (WTree *tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 737 {
 738     char msg[BUF_MEDIUM];
 739     char *dest;
 740 
 741     if (tree->selected_ptr == NULL)
 742         return;
 743 
 744     g_snprintf (msg, sizeof (msg), _ ("Move \"%s\" directory to:"),
 745                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 746     dest = input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
 747                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 748 
 749     if (dest != NULL && *dest != '\0')
 750     {
 751         vfs_path_t *dest_vpath;
 752         struct stat buf;
 753 
 754         dest_vpath = vfs_path_from_str (dest);
 755 
 756         if (mc_stat (dest_vpath, &buf) != 0)
 757             file_error_message (_ ("Cannot stat the destination\n%s"), dest);
 758         else if (!S_ISDIR (buf.st_mode))
 759         {
 760             errno = ENOTDIR;
 761             file_error_message (_ ("Destination\n%s\nmust be a directory"), dest);
 762         }
 763         else
 764         {
 765             file_op_context_t *ctx;
 766 
 767             ctx = file_op_context_new (OP_MOVE);
 768             file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 769             move_dir_dir (ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
 770             file_op_context_destroy (ctx);
 771         }
 772 
 773         vfs_path_free (dest_vpath, TRUE);
 774     }
 775 
 776     g_free (dest);
 777 }
 778 
 779 /* --------------------------------------------------------------------------------------------- */
 780 
 781 #if 0
 782 static void
 783 tree_mkdir (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 784 {
 785     char old_dir[MC_MAXPATHLEN];
 786 
 787     if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
 788         return;
 789     /* FIXME
 790        mkdir_cmd (tree);
 791      */
 792     tree_rescan (tree);
 793     chdir (old_dir);
 794 }
 795 #endif
 796 
 797 /* --------------------------------------------------------------------------------------------- */
 798 
 799 static void
 800 tree_rmdir (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 801 {
 802     WTree *tree = data;
 803     file_op_context_t *ctx;
 804 
 805     if (tree->selected_ptr == NULL)
 806         return;
 807 
 808     if (confirm_delete)
 809     {
 810         char *buf;
 811         int result;
 812 
 813         buf = g_strdup_printf (_ ("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
 814 
 815         result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _ ("&Yes"), _ ("&No"));
 816         g_free (buf);
 817         if (result != 0)
 818             return;
 819     }
 820 
 821     ctx = file_op_context_new (OP_DELETE);
 822     file_progress_ui_create (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 823     if (erase_dir (ctx, tree->selected_ptr->name) == FILE_CONT)
 824         tree_forget (tree);
 825     file_op_context_destroy (ctx);
 826 }
 827 
 828 /* --------------------------------------------------------------------------------------------- */
 829 
 830 static inline void
 831 tree_move_up (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 832 {
 833     tree_move_backward (tree, 1);
 834     show_tree (tree);
 835     maybe_chdir (tree);
 836 }
 837 
 838 /* --------------------------------------------------------------------------------------------- */
 839 
 840 static inline void
 841 tree_move_down (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 842 {
 843     tree_move_forward (tree, 1);
 844     show_tree (tree);
 845     maybe_chdir (tree);
 846 }
 847 
 848 /* --------------------------------------------------------------------------------------------- */
 849 
 850 static inline void
 851 tree_move_home (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 852 {
 853     tree_move_to_top (tree);
 854     show_tree (tree);
 855     maybe_chdir (tree);
 856 }
 857 
 858 /* --------------------------------------------------------------------------------------------- */
 859 
 860 static inline void
 861 tree_move_end (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 862 {
 863     tree_move_to_bottom (tree);
 864     show_tree (tree);
 865     maybe_chdir (tree);
 866 }
 867 
 868 /* --------------------------------------------------------------------------------------------- */
 869 
 870 static void
 871 tree_move_pgup (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 872 {
 873     tree_move_backward (tree, tlines (tree) - 1);
 874     show_tree (tree);
 875     maybe_chdir (tree);
 876 }
 877 
 878 /* --------------------------------------------------------------------------------------------- */
 879 
 880 static void
 881 tree_move_pgdn (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 882 {
 883     tree_move_forward (tree, tlines (tree) - 1);
 884     show_tree (tree);
 885     maybe_chdir (tree);
 886 }
 887 
 888 /* --------------------------------------------------------------------------------------------- */
 889 
 890 static gboolean
 891 tree_move_left (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 892 {
 893     gboolean v = FALSE;
 894 
 895     if (tree_navigation_flag)
 896     {
 897         v = tree_move_to_parent (tree);
 898         show_tree (tree);
 899         maybe_chdir (tree);
 900     }
 901 
 902     return v;
 903 }
 904 
 905 /* --------------------------------------------------------------------------------------------- */
 906 
 907 static gboolean
 908 tree_move_right (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 909 {
 910     gboolean v = FALSE;
 911 
 912     if (tree_navigation_flag)
 913     {
 914         tree_move_to_child (tree);
 915         show_tree (tree);
 916         maybe_chdir (tree);
 917         v = TRUE;
 918     }
 919 
 920     return v;
 921 }
 922 
 923 /* --------------------------------------------------------------------------------------------- */
 924 
 925 static void
 926 tree_start_search (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 927 {
 928     if (tree->searching)
 929     {
 930         if (tree->selected_ptr == tree->store->tree_last)
 931             tree_move_to_top (tree);
 932         else
 933         {
 934             gboolean i;
 935 
 936             /* set navigation mode temporarily to 'Static' because in
 937              * dynamic navigation mode tree_move_forward will not move
 938              * to a lower sublevel if necessary (sequent searches must
 939              * start with the directory followed the last found directory)
 940              */
 941             i = tree_navigation_flag;
 942             tree_navigation_flag = FALSE;
 943             tree_move_forward (tree, 1);
 944             tree_navigation_flag = i;
 945         }
 946         tree_do_search (tree, 0);
 947     }
 948     else
 949     {
 950         tree->searching = TRUE;
 951         g_string_set_size (tree->search_buffer, 0);
 952     }
 953 }
 954 
 955 /* --------------------------------------------------------------------------------------------- */
 956 
 957 static void
 958 tree_toggle_navig (WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 959 {
 960     Widget *w = WIDGET (tree);
 961     WButtonBar *b;
 962 
 963     tree_navigation_flag = !tree_navigation_flag;
 964 
 965     b = buttonbar_find (DIALOG (w->owner));
 966     buttonbar_set_label (b, 4,
 967                          tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
 968                          w->keymap, w);
 969     widget_draw (WIDGET (b));
 970 }
 971 
 972 /* --------------------------------------------------------------------------------------------- */
 973 
 974 static void
 975 tree_help (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 976 {
 977     ev_help_t event_data = { NULL, "[Directory Tree]" };
 978 
 979     mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 980 }
 981 
 982 /* --------------------------------------------------------------------------------------------- */
 983 
 984 static cb_ret_t
 985 tree_execute_cmd (WTree *tree, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 986 {
 987     cb_ret_t res = MSG_HANDLED;
 988 
 989     if (command != CK_Search)
 990         tree->searching = FALSE;
 991 
 992     switch (command)
 993     {
 994     case CK_Help:
 995         tree_help ();
 996         break;
 997     case CK_Forget:
 998         tree_forget (tree);
 999         break;
1000     case CK_ToggleNavigation:
1001         tree_toggle_navig (tree);
1002         break;
1003     case CK_Copy:
1004         tree_copy (tree, "");
1005         break;
1006     case CK_Move:
1007         tree_move (tree, "");
1008         break;
1009     case CK_Up:
1010         tree_move_up (tree);
1011         break;
1012     case CK_Down:
1013         tree_move_down (tree);
1014         break;
1015     case CK_Top:
1016         tree_move_home (tree);
1017         break;
1018     case CK_Bottom:
1019         tree_move_end (tree);
1020         break;
1021     case CK_PageUp:
1022         tree_move_pgup (tree);
1023         break;
1024     case CK_PageDown:
1025         tree_move_pgdn (tree);
1026         break;
1027     case CK_Enter:
1028         tree_chdir_sel (tree);
1029         break;
1030     case CK_Reread:
1031         tree_rescan (tree);
1032         break;
1033     case CK_Search:
1034         tree_start_search (tree);
1035         break;
1036     case CK_Delete:
1037         tree_rmdir (tree);
1038         break;
1039     case CK_Quit:
1040         if (!tree->is_panel)
1041             dlg_close (DIALOG (WIDGET (tree)->owner));
1042         return res;
1043     default:
1044         res = MSG_NOT_HANDLED;
1045     }
1046 
1047     show_tree (tree);
1048 
1049     return res;
1050 }
1051 
1052 /* --------------------------------------------------------------------------------------------- */
1053 
1054 static cb_ret_t
1055 tree_key (WTree *tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1056 {
1057     long command;
1058 
1059     if (is_abort_char (key))
1060     {
1061         if (tree->is_panel)
1062         {
1063             tree->searching = FALSE;
1064             show_tree (tree);
1065             return MSG_HANDLED;  // eat abort char
1066         }
1067         /* modal tree dialog: let upper layer see the
1068            abort character and close the dialog */
1069         return MSG_NOT_HANDLED;
1070     }
1071 
1072     if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1073     {
1074         tree_do_search (tree, key);
1075         show_tree (tree);
1076         return MSG_HANDLED;
1077     }
1078 
1079     command = widget_lookup_key (WIDGET (tree), key);
1080     switch (command)
1081     {
1082     case CK_IgnoreKey:
1083         break;
1084     case CK_Left:
1085         return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1086     case CK_Right:
1087         return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1088     default:
1089         tree_execute_cmd (tree, command);
1090         return MSG_HANDLED;
1091     }
1092 
1093     // Do not eat characters not meant for the tree below ' ' (e.g. C-l).
1094     if (!mc_global.cmdline_visible && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1095     {
1096         tree_start_search (tree);
1097         tree_do_search (tree, key);
1098         return MSG_HANDLED;
1099     }
1100 
1101     return MSG_NOT_HANDLED;
1102 }
1103 
1104 /* --------------------------------------------------------------------------------------------- */
1105 
1106 static void
1107 tree_frame (WDialog *h, WTree *tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1108 {
1109     Widget *w = WIDGET (tree);
1110 
1111     (void) h;
1112 
1113     tty_setcolor (CORE_NORMAL_COLOR);
1114     widget_erase (w);
1115     if (tree->is_panel)
1116     {
1117         const char *title = _ ("Directory tree");
1118         const int len = str_term_width1 (title);
1119 
1120         tty_setcolor (CORE_FRAME_COLOR);
1121         tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
1122 
1123         if (panels_options.show_mini_info)
1124         {
1125             int y;
1126 
1127             y = w->rect.lines - 3;
1128             widget_gotoyx (w, y, 0);
1129             tty_print_char (mc_tty_frm[MC_TTY_FRM_DLEFTMIDDLE]);
1130             widget_gotoyx (w, y, w->rect.cols - 1);
1131             tty_print_char (mc_tty_frm[MC_TTY_FRM_DRIGHTMIDDLE]);
1132             tty_draw_hline (w->rect.y + y, w->rect.x + 1, mc_tty_frm[MC_TTY_FRM_HORIZ],
1133                             w->rect.cols - 2);
1134         }
1135 
1136         const gboolean focused = widget_get_state (w, WST_FOCUSED);
1137 
1138         tty_setcolor (focused ? CORE_REVERSE_COLOR : CORE_NORMAL_COLOR);
1139         widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
1140         tty_printf (" %s ", title);
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]  */