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

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