root/src/filemanager/find.c

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

DEFINITIONS

This source file includes following definitions.
  1. max
  2. parse_ignore_dirs
  3. find_load_options
  4. find_save_options
  5. add_to_list
  6. stop_idle
  7. status_update
  8. found_num_update
  9. get_list_info
  10. find_check_regexp
  11. find_toggle_enable_ignore_dirs
  12. find_toggle_enable_params
  13. find_toggle_enable_content
  14. find_parm_callback
  15. find_parameters
  16. push_directory
  17. pop_directory
  18. clear_stack
  19. insert_file
  20. find_add_match
  21. check_find_events
  22. search_content
  23. find_ignore_dir_search
  24. find_rotate_dash
  25. do_search
  26. init_find_vars
  27. find_do_view_edit
  28. view_edit_currently_selected_file
  29. find_calc_button_locations
  30. find_adjust_header
  31. find_relocate_buttons
  32. find_callback
  33. start_stop
  34. find_do_view_file
  35. find_do_edit_file
  36. setup_gui
  37. run_process
  38. kill_gui
  39. do_find
  40. find_file

   1 /*
   2    Find file command for the Midnight Commander
   3 
   4    Copyright (C) 1995-2019
   5    Free Software Foundation, Inc.
   6 
   7    Written  by:
   8    Miguel de Icaza, 1995
   9    Slava Zanko <slavazanko@gmail.com>, 2013
  10    Andrew Borodin <aborodin@vmail.ru>, 2013
  11 
  12    This file is part of the Midnight Commander.
  13 
  14    The Midnight Commander is free software: you can redistribute it
  15    and/or modify it under the terms of the GNU General Public License as
  16    published by the Free Software Foundation, either version 3 of the License,
  17    or (at your option) any later version.
  18 
  19    The Midnight Commander is distributed in the hope that it will be useful,
  20    but WITHOUT ANY WARRANTY; without even the implied warranty of
  21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22    GNU General Public License for more details.
  23 
  24    You should have received a copy of the GNU General Public License
  25    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  26  */
  27 
  28 /** \file find.c
  29  *  \brief Source: Find file command
  30  */
  31 
  32 #include <config.h>
  33 
  34 #include <ctype.h>
  35 #include <stdio.h>
  36 #include <stdlib.h>
  37 #include <string.h>
  38 #include <sys/stat.h>
  39 #include <sys/time.h>
  40 
  41 #include "lib/global.h"
  42 
  43 #include "lib/tty/tty.h"
  44 #include "lib/tty/key.h"
  45 #include "lib/skin.h"
  46 #include "lib/search.h"
  47 #include "lib/mcconfig.h"
  48 #include "lib/vfs/vfs.h"
  49 #include "lib/strutil.h"
  50 #include "lib/widget.h"
  51 #include "lib/util.h"           /* canonicalize_pathname() */
  52 
  53 #include "src/setup.h"          /* verbose */
  54 #include "src/history.h"        /* MC_HISTORY_SHARED_SEARCH */
  55 
  56 #include "dir.h"
  57 #include "cmd.h"                /* view_file_at_line() */
  58 #include "midnight.h"           /* current_panel */
  59 #include "boxes.h"
  60 #include "panelize.h"
  61 
  62 #include "find.h"
  63 
  64 /*** global variables ****************************************************************************/
  65 
  66 /*** file scope macro definitions ****************************************************************/
  67 
  68 #define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20)      /* 50 ms */
  69 #define MIN_REFRESH_FILE_SIZE (256 * 1024)      /* 256 KB */
  70 
  71 /*** file scope type declarations ****************************************************************/
  72 
  73 /* A couple of extra messages we need */
  74 enum
  75 {
  76     B_STOP = B_USER + 1,
  77     B_AGAIN,
  78     B_PANELIZE,
  79     B_TREE,
  80     B_VIEW
  81 };
  82 
  83 typedef enum
  84 {
  85     FIND_CONT = 0,
  86     FIND_SUSPEND,
  87     FIND_ABORT
  88 } FindProgressStatus;
  89 
  90 /* find file options */
  91 typedef struct
  92 {
  93     /* file name options */
  94     gboolean file_case_sens;
  95     gboolean file_pattern;
  96     gboolean find_recurs;
  97     gboolean skip_hidden;
  98     gboolean file_all_charsets;
  99 
 100     /* file content options */
 101     gboolean content_case_sens;
 102     gboolean content_regexp;
 103     gboolean content_first_hit;
 104     gboolean content_whole_words;
 105     gboolean content_all_charsets;
 106 
 107     /* whether use ignore dirs or not */
 108     gboolean ignore_dirs_enable;
 109     /* list of directories to be ignored, separated by ':' */
 110     char *ignore_dirs;
 111 } find_file_options_t;
 112 
 113 typedef struct
 114 {
 115     char *dir;
 116     gsize start;
 117     gsize end;
 118 } find_match_location_t;
 119 
 120 /*** file scope variables ************************************************************************/
 121 
 122 /* button callbacks */
 123 static int start_stop (WButton * button, int action);
 124 static int find_do_view_file (WButton * button, int action);
 125 static int find_do_edit_file (WButton * button, int action);
 126 
 127 /* Parsed ignore dirs */
 128 static char **find_ignore_dirs = NULL;
 129 
 130 /* static variables to remember find parameters */
 131 static WInput *in_start;        /* Start path */
 132 static WInput *in_name;         /* Filename */
 133 static WInput *in_with;         /* Text */
 134 static WInput *in_ignore;
 135 static WLabel *content_label;   /* 'Content:' label */
 136 static WCheck *file_case_sens_cbox;     /* "case sensitive" checkbox */
 137 static WCheck *file_pattern_cbox;       /* File name is glob or regexp */
 138 static WCheck *recursively_cbox;
 139 static WCheck *skip_hidden_cbox;
 140 static WCheck *content_case_sens_cbox;  /* "case sensitive" checkbox */
 141 static WCheck *content_regexp_cbox;     /* "find regular expression" checkbox */
 142 static WCheck *content_first_hit_cbox;  /* "First hit" checkbox" */
 143 static WCheck *content_whole_words_cbox;        /* "whole words" checkbox */
 144 #ifdef HAVE_CHARSET
 145 static WCheck *file_all_charsets_cbox;
 146 static WCheck *content_all_charsets_cbox;
 147 #endif
 148 static WCheck *ignore_dirs_cbox;
 149 
 150 static gboolean running = FALSE;        /* nice flag */
 151 static char *find_pattern = NULL;       /* Pattern to search */
 152 static char *content_pattern = NULL;    /* pattern to search inside files; if
 153                                            content_regexp_flag is true, it contains the
 154                                            regex pattern, else the search string. */
 155 static gboolean content_is_empty = TRUE;        /* remember content field state; initially is empty */
 156 static unsigned long matches;   /* Number of matches */
 157 static gboolean is_start = FALSE;       /* Status of the start/stop toggle button */
 158 static char *old_dir = NULL;
 159 
 160 static struct timeval last_refresh;
 161 
 162 /* Where did we stop */
 163 static gboolean resuming;
 164 static int last_line;
 165 static int last_pos;
 166 static off_t last_off;
 167 static int last_i;
 168 
 169 static size_t ignore_count = 0;
 170 
 171 static WDialog *find_dlg;       /* The dialog */
 172 static WLabel *status_label;    /* Finished, Searching etc. */
 173 static WLabel *found_num_label; /* Number of found items */
 174 
 175 /* This keeps track of the directory stack */
 176 static GQueue dir_queue = G_QUEUE_INIT;
 177 
 178 /* *INDENT-OFF* */
 179 static struct
 180 {
 181     int ret_cmd;
 182     button_flags_t flags;
 183     const char *text;
 184     int len;                    /* length including space and brackets */
 185     int x;
 186     Widget *button;
 187     bcback_fn callback;
 188 } fbuts[] =
 189 {
 190     { B_ENTER, DEFPUSH_BUTTON, N_("&Chdir"), 0, 0, NULL, NULL },
 191     { B_AGAIN, NORMAL_BUTTON, N_("&Again"), 0, 0, NULL, NULL },
 192     { B_STOP, NORMAL_BUTTON, N_("S&uspend"), 0, 0, NULL, start_stop },
 193     { B_STOP, NORMAL_BUTTON, N_("Con&tinue"), 0, 0, NULL, NULL },
 194     { B_CANCEL, NORMAL_BUTTON, N_("&Quit"), 0, 0, NULL, NULL },
 195 
 196     { B_PANELIZE, NORMAL_BUTTON, N_("Pane&lize"), 0, 0, NULL, NULL },
 197     { B_VIEW, NORMAL_BUTTON, N_("&View - F3"), 0, 0, NULL, find_do_view_file },
 198     { B_VIEW, NORMAL_BUTTON, N_("&Edit - F4"), 0, 0, NULL, find_do_edit_file }
 199 };
 200 /* *INDENT-ON* */
 201 
 202 static const size_t fbuts_num = G_N_ELEMENTS (fbuts);
 203 static const size_t quit_button = 4;    /* index of "Quit" button */
 204 
 205 static WListbox *find_list;     /* Listbox with the file list */
 206 
 207 static find_file_options_t options = {
 208     TRUE, TRUE, TRUE, FALSE, FALSE,
 209     TRUE, FALSE, FALSE, FALSE, FALSE,
 210     FALSE, NULL
 211 };
 212 
 213 static char *in_start_dir = INPUT_LAST_TEXT;
 214 
 215 static mc_search_t *search_file_handle = NULL;
 216 static mc_search_t *search_content_handle = NULL;
 217 
 218 /* --------------------------------------------------------------------------------------------- */
 219 /*** file scope functions ************************************************************************/
 220 /* --------------------------------------------------------------------------------------------- */
 221 
 222 /* don't use max macro to avoid double str_term_width1() call in widget length caclulation */
 223 #undef max
 224 
 225 static int
 226 max (int a, int b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 227 {
 228     return (a > b ? a : b);
 229 }
 230 
 231 /* --------------------------------------------------------------------------------------------- */
 232 
 233 static void
 234 parse_ignore_dirs (const char *ignore_dirs)
     /* [previous][next][first][last][top][bottom][index][help]  */
 235 {
 236     size_t r = 0, w = 0;        /* read and write iterators */
 237 
 238     if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0')
 239         return;
 240 
 241     find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
 242 
 243     /* Values like '/foo::/bar: produce holes in list.
 244      * Find and remove them */
 245     for (; find_ignore_dirs[r] != NULL; r++)
 246     {
 247         if (find_ignore_dirs[r][0] == '\0')
 248         {
 249             /* empty entry -- skip it */
 250             MC_PTR_FREE (find_ignore_dirs[r]);
 251             continue;
 252         }
 253 
 254         if (r != w)
 255         {
 256             /* copy entry to the previous free array cell */
 257             find_ignore_dirs[w] = find_ignore_dirs[r];
 258             find_ignore_dirs[r] = NULL;
 259         }
 260 
 261         canonicalize_pathname (find_ignore_dirs[w]);
 262         if (find_ignore_dirs[w][0] != '\0')
 263             w++;
 264         else
 265             MC_PTR_FREE (find_ignore_dirs[w]);
 266     }
 267 
 268     if (find_ignore_dirs[0] == NULL)
 269     {
 270         g_strfreev (find_ignore_dirs);
 271         find_ignore_dirs = NULL;
 272     }
 273 }
 274 
 275 /* --------------------------------------------------------------------------------------------- */
 276 
 277 static void
 278 find_load_options (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 279 {
 280     static gboolean loaded = FALSE;
 281 
 282     if (loaded)
 283         return;
 284 
 285     loaded = TRUE;
 286 
 287     options.file_case_sens =
 288         mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE);
 289     options.file_pattern =
 290         mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE);
 291     options.find_recurs =
 292         mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE);
 293     options.skip_hidden =
 294         mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE);
 295     options.file_all_charsets =
 296         mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE);
 297     options.content_case_sens =
 298         mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE);
 299     options.content_regexp =
 300         mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE);
 301     options.content_first_hit =
 302         mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE);
 303     options.content_whole_words =
 304         mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE);
 305     options.content_all_charsets =
 306         mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE);
 307     options.ignore_dirs_enable =
 308         mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE);
 309     options.ignore_dirs =
 310         mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", "");
 311 
 312     if (options.ignore_dirs[0] == '\0')
 313         MC_PTR_FREE (options.ignore_dirs);
 314 }
 315 
 316 /* --------------------------------------------------------------------------------------------- */
 317 
 318 static void
 319 find_save_options (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 320 {
 321     mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens",
 322                         options.file_case_sens);
 323     mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern",
 324                         options.file_pattern);
 325     mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs);
 326     mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
 327     mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets",
 328                         options.file_all_charsets);
 329     mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens",
 330                         options.content_case_sens);
 331     mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp",
 332                         options.content_regexp);
 333     mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit",
 334                         options.content_first_hit);
 335     mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words",
 336                         options.content_whole_words);
 337     mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets",
 338                         options.content_all_charsets);
 339     mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable",
 340                         options.ignore_dirs_enable);
 341     mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs);
 342 }
 343 
 344 /* --------------------------------------------------------------------------------------------- */
 345 
 346 static inline char *
 347 add_to_list (const char *text, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 348 {
 349     return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
 350 }
 351 
 352 /* --------------------------------------------------------------------------------------------- */
 353 
 354 static inline void
 355 stop_idle (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 356 {
 357     widget_idle (WIDGET (data), FALSE);
 358 }
 359 
 360 /* --------------------------------------------------------------------------------------------- */
 361 
 362 static inline void
 363 status_update (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 364 {
 365     label_set_text (status_label, text);
 366 }
 367 
 368 /* --------------------------------------------------------------------------------------------- */
 369 
 370 static void
 371 found_num_update (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 372 {
 373     char buffer[BUF_TINY];
 374 
 375     g_snprintf (buffer, sizeof (buffer), _("Found: %lu"), matches);
 376     label_set_text (found_num_label, buffer);
 377 }
 378 
 379 /* --------------------------------------------------------------------------------------------- */
 380 
 381 static void
 382 get_list_info (char **file, char **dir, gsize * start, gsize * end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 383 {
 384     find_match_location_t *location;
 385 
 386     listbox_get_current (find_list, file, (void **) &location);
 387     if (location != NULL)
 388     {
 389         if (dir != NULL)
 390             *dir = location->dir;
 391         if (start != NULL)
 392             *start = location->start;
 393         if (end != NULL)
 394             *end = location->end;
 395     }
 396     else
 397     {
 398         if (dir != NULL)
 399             *dir = NULL;
 400     }
 401 }
 402 
 403 /* --------------------------------------------------------------------------------------------- */
 404 /** check regular expression */
 405 
 406 static gboolean
 407 find_check_regexp (const char *r)
     /* [previous][next][first][last][top][bottom][index][help]  */
 408 {
 409     mc_search_t *search;
 410     gboolean regexp_ok = FALSE;
 411 
 412     search = mc_search_new (r, NULL);
 413 
 414     if (search != NULL)
 415     {
 416         search->search_type = MC_SEARCH_T_REGEX;
 417         regexp_ok = mc_search_prepare (search);
 418         mc_search_free (search);
 419     }
 420 
 421     return regexp_ok;
 422 }
 423 
 424 /* --------------------------------------------------------------------------------------------- */
 425 
 426 static void
 427 find_toggle_enable_ignore_dirs (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 428 {
 429     widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
 430 }
 431 
 432 /* --------------------------------------------------------------------------------------------- */
 433 
 434 static void
 435 find_toggle_enable_params (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 436 {
 437     gboolean disable = input_is_empty (in_name);
 438 
 439     widget_disable (WIDGET (file_pattern_cbox), disable);
 440     widget_disable (WIDGET (file_case_sens_cbox), disable);
 441 #ifdef HAVE_CHARSET
 442     widget_disable (WIDGET (file_all_charsets_cbox), disable);
 443 #endif
 444 }
 445 
 446 /* --------------------------------------------------------------------------------------------- */
 447 
 448 static void
 449 find_toggle_enable_content (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 450 {
 451     widget_disable (WIDGET (content_regexp_cbox), content_is_empty);
 452     widget_disable (WIDGET (content_case_sens_cbox), content_is_empty);
 453 #ifdef HAVE_CHARSET
 454     widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty);
 455 #endif
 456     widget_disable (WIDGET (content_whole_words_cbox), content_is_empty);
 457     widget_disable (WIDGET (content_first_hit_cbox), content_is_empty);
 458 }
 459 
 460 /* --------------------------------------------------------------------------------------------- */
 461 /**
 462  * Callback for the parameter dialog.
 463  * Validate regex, prevent closing the dialog if it's invalid.
 464  */
 465 
 466 static cb_ret_t
 467 find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 468 {
 469     /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies.
 470      * Use this time moment to check input field content. We can't do that in MSG_INIT
 471      * because history is not loaded yet.
 472      * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or
 473      * we could name it MSG_POST_INIT.
 474      *
 475      * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar
 476      * purpose. We should remember to fix those places too when we introduce the new
 477      * message.
 478      */
 479     static gboolean first_draw = TRUE;
 480 
 481     WDialog *h = DIALOG (w);
 482 
 483     switch (msg)
 484     {
 485     case MSG_INIT:
 486         first_draw = TRUE;
 487         return MSG_HANDLED;
 488 
 489     case MSG_NOTIFY:
 490         if (sender == WIDGET (ignore_dirs_cbox))
 491         {
 492             find_toggle_enable_ignore_dirs ();
 493             return MSG_HANDLED;
 494         }
 495 
 496         return MSG_NOT_HANDLED;
 497 
 498     case MSG_VALIDATE:
 499         if (h->ret_value != B_ENTER)
 500             return MSG_HANDLED;
 501 
 502         /* check filename regexp */
 503         if (!file_pattern_cbox->state && !input_is_empty (in_name)
 504             && !find_check_regexp (in_name->buffer))
 505         {
 506             /* Don't stop the dialog */
 507             widget_set_state (w, WST_ACTIVE, TRUE);
 508             message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
 509             widget_select (WIDGET (in_name));
 510             return MSG_HANDLED;
 511         }
 512 
 513         /* check content regexp */
 514         if (content_regexp_cbox->state && !content_is_empty && !find_check_regexp (in_with->buffer))
 515         {
 516             /* Don't stop the dialog */
 517             widget_set_state (w, WST_ACTIVE, TRUE);
 518             message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
 519             widget_select (WIDGET (in_with));
 520             return MSG_HANDLED;
 521         }
 522 
 523         return MSG_HANDLED;
 524 
 525     case MSG_POST_KEY:
 526         if (h->current->data == in_name)
 527             find_toggle_enable_params ();
 528         else if (h->current->data == in_with)
 529         {
 530             content_is_empty = input_is_empty (in_with);
 531             find_toggle_enable_content ();
 532         }
 533         return MSG_HANDLED;
 534 
 535     case MSG_DRAW:
 536         if (first_draw)
 537         {
 538             find_toggle_enable_ignore_dirs ();
 539             find_toggle_enable_params ();
 540             find_toggle_enable_content ();
 541         }
 542 
 543         first_draw = FALSE;
 544         MC_FALLTHROUGH;         /* to call MSG_DRAW default handler */
 545 
 546     default:
 547         return dlg_default_callback (w, sender, msg, parm, data);
 548     }
 549 }
 550 
 551 /* --------------------------------------------------------------------------------------------- */
 552 /**
 553  * find_parameters: gets information from the user
 554  *
 555  * If the return value is TRUE, then the following holds:
 556  *
 557  * start_dir, ignore_dirs, pattern and content contain the information provided by the user.
 558  * They are newly allocated strings and must be freed when uneeded.
 559  *
 560  * start_dir_len is -1 when user entered an absolute path, otherwise it is a length
 561  * of start_dir (which is absolute). It is used to get a relative pats of find results.
 562  */
 563 
 564 static gboolean
 565 find_parameters (char **start_dir, ssize_t * start_dir_len,
     /* [previous][next][first][last][top][bottom][index][help]  */
 566                  char **ignore_dirs, char **pattern, char **content)
 567 {
 568     /* Size of the find parameters window */
 569 #ifdef HAVE_CHARSET
 570     const int lines = 18;
 571 #else
 572     const int lines = 17;
 573 #endif
 574     int cols = 68;
 575 
 576     gboolean return_value;
 577 
 578     /* file name */
 579     const char *file_name_label = N_("File name:");
 580     const char *file_recurs_label = N_("&Find recursively");
 581     const char *file_pattern_label = N_("&Using shell patterns");
 582 #ifdef HAVE_CHARSET
 583     const char *file_all_charsets_label = N_("&All charsets");
 584 #endif
 585     const char *file_case_label = N_("Cas&e sensitive");
 586     const char *file_skip_hidden_label = N_("S&kip hidden");
 587 
 588     /* file content */
 589     const char *content_content_label = N_("Content:");
 590     const char *content_use_label = N_("Sea&rch for content");
 591     const char *content_regexp_label = N_("Re&gular expression");
 592     const char *content_case_label = N_("Case sens&itive");
 593 #ifdef HAVE_CHARSET
 594     const char *content_all_charsets_label = N_("A&ll charsets");
 595 #endif
 596     const char *content_whole_words_label = N_("&Whole words");
 597     const char *content_first_hit_label = N_("Fir&st hit");
 598 
 599     const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") };
 600 
 601     /* button lengths */
 602     int b0, b1, b2, b12;
 603     int y1, y2, x1, x2;
 604     /* column width */
 605     int cw;
 606 
 607 #ifdef ENABLE_NLS
 608     {
 609         size_t i;
 610 
 611         file_name_label = _(file_name_label);
 612         file_recurs_label = _(file_recurs_label);
 613         file_pattern_label = _(file_pattern_label);
 614 #ifdef HAVE_CHARSET
 615         file_all_charsets_label = _(file_all_charsets_label);
 616 #endif
 617         file_case_label = _(file_case_label);
 618         file_skip_hidden_label = _(file_skip_hidden_label);
 619 
 620         /* file content */
 621         content_content_label = _(content_content_label);
 622         content_use_label = _(content_use_label);
 623         content_regexp_label = _(content_regexp_label);
 624         content_case_label = _(content_case_label);
 625 #ifdef HAVE_CHARSET
 626         content_all_charsets_label = _(content_all_charsets_label);
 627 #endif
 628         content_whole_words_label = _(content_whole_words_label);
 629         content_first_hit_label = _(content_first_hit_label);
 630 
 631         for (i = 0; i < G_N_ELEMENTS (buts); i++)
 632             buts[i] = _(buts[i]);
 633     }
 634 #endif /* ENABLE_NLS */
 635 
 636     /* caclulate dialog width */
 637 
 638     /* widget widths */
 639     cw = str_term_width1 (file_name_label);
 640     cw = max (cw, str_term_width1 (file_recurs_label) + 4);
 641     cw = max (cw, str_term_width1 (file_pattern_label) + 4);
 642 #ifdef HAVE_CHARSET
 643     cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
 644 #endif
 645     cw = max (cw, str_term_width1 (file_case_label) + 4);
 646     cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
 647 
 648     cw = max (cw, str_term_width1 (content_content_label) + 4);
 649     cw = max (cw, str_term_width1 (content_use_label) + 4);
 650     cw = max (cw, str_term_width1 (content_regexp_label) + 4);
 651     cw = max (cw, str_term_width1 (content_case_label) + 4);
 652 #ifdef HAVE_CHARSET
 653     cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
 654 #endif
 655     cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
 656     cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
 657 
 658     /* button width */
 659     b0 = str_term_width1 (buts[0]) + 3;
 660     b1 = str_term_width1 (buts[1]) + 5; /* default button */
 661     b2 = str_term_width1 (buts[2]) + 3;
 662     b12 = b1 + b2 + 1;
 663 
 664     cols = max (cols, max (b12, cw * 2 + 1) + 6);
 665 
 666     find_load_options ();
 667 
 668     if (in_start_dir == NULL)
 669         in_start_dir = g_strdup (".");
 670 
 671     find_dlg =
 672         dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback,
 673                     NULL, "[Find File]", _("Find File"));
 674 
 675     x1 = 3;
 676     x2 = cols / 2 + 1;
 677     cw = (cols - 7) / 2;
 678     y1 = 2;
 679 
 680     add_widget (find_dlg, label_new (y1++, x1, _("Start at:")));
 681     in_start =
 682         input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
 683                    INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 684     add_widget (find_dlg, in_start);
 685 
 686     add_widget (find_dlg, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
 687 
 688     ignore_dirs_cbox =
 689         check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:"));
 690     add_widget (find_dlg, ignore_dirs_cbox);
 691 
 692     in_ignore =
 693         input_new (y1++, x1, input_colors, cols - 6,
 694                    options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
 695                    INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 696     add_widget (find_dlg, in_ignore);
 697 
 698     add_widget (find_dlg, hline_new (y1++, -1, -1));
 699 
 700     y2 = y1;
 701 
 702     /* Start 1st column */
 703     add_widget (find_dlg, label_new (y1++, x1, file_name_label));
 704     in_name =
 705         input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
 706                    INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 707     add_widget (find_dlg, in_name);
 708 
 709     /* Start 2nd column */
 710     content_label = label_new (y2++, x2, content_content_label);
 711     add_widget (find_dlg, content_label);
 712     in_with =
 713         input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
 714                    MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
 715     in_with->label = content_label;
 716     add_widget (find_dlg, in_with);
 717 
 718     /* Continue 1st column */
 719     recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
 720     add_widget (find_dlg, recursively_cbox);
 721 
 722     file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
 723     add_widget (find_dlg, file_pattern_cbox);
 724 
 725     file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
 726     add_widget (find_dlg, file_case_sens_cbox);
 727 
 728 #ifdef HAVE_CHARSET
 729     file_all_charsets_cbox =
 730         check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
 731     add_widget (find_dlg, file_all_charsets_cbox);
 732 #endif
 733 
 734     skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
 735     add_widget (find_dlg, skip_hidden_cbox);
 736 
 737     /* Continue 2nd column */
 738     content_whole_words_cbox =
 739         check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
 740     add_widget (find_dlg, content_whole_words_cbox);
 741 
 742     content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
 743     add_widget (find_dlg, content_regexp_cbox);
 744 
 745     content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
 746     add_widget (find_dlg, content_case_sens_cbox);
 747 
 748 #ifdef HAVE_CHARSET
 749     content_all_charsets_cbox =
 750         check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
 751     add_widget (find_dlg, content_all_charsets_cbox);
 752 #endif
 753 
 754     content_first_hit_cbox =
 755         check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
 756     add_widget (find_dlg, content_first_hit_cbox);
 757 
 758     /* buttons */
 759     y1 = max (y1, y2);
 760     x1 = (cols - b12) / 2;
 761     add_widget (find_dlg, hline_new (y1++, -1, -1));
 762     add_widget (find_dlg, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
 763     add_widget (find_dlg, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
 764 
 765   find_par_start:
 766     widget_select (WIDGET (in_name));
 767 
 768     switch (dlg_run (find_dlg))
 769     {
 770     case B_CANCEL:
 771         return_value = FALSE;
 772         break;
 773 
 774     case B_TREE:
 775         {
 776             char *temp_dir;
 777 
 778             temp_dir = in_start->buffer;
 779             if (*temp_dir == '\0' || DIR_IS_DOT (temp_dir))
 780                 temp_dir = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
 781             else
 782                 temp_dir = g_strdup (temp_dir);
 783 
 784             if (in_start_dir != INPUT_LAST_TEXT)
 785                 g_free (in_start_dir);
 786             in_start_dir = tree_box (temp_dir);
 787             if (in_start_dir == NULL)
 788                 in_start_dir = temp_dir;
 789             else
 790                 g_free (temp_dir);
 791 
 792             input_assign_text (in_start, in_start_dir);
 793 
 794             /* Warning: Dreadful goto */
 795             goto find_par_start;
 796         }
 797 
 798     default:
 799         {
 800             char *s;
 801 
 802 #ifdef HAVE_CHARSET
 803             options.file_all_charsets = file_all_charsets_cbox->state;
 804             options.content_all_charsets = content_all_charsets_cbox->state;
 805 #endif
 806             options.content_case_sens = content_case_sens_cbox->state;
 807             options.content_regexp = content_regexp_cbox->state;
 808             options.content_first_hit = content_first_hit_cbox->state;
 809             options.content_whole_words = content_whole_words_cbox->state;
 810             options.find_recurs = recursively_cbox->state;
 811             options.file_pattern = file_pattern_cbox->state;
 812             options.file_case_sens = file_case_sens_cbox->state;
 813             options.skip_hidden = skip_hidden_cbox->state;
 814             options.ignore_dirs_enable = ignore_dirs_cbox->state;
 815             g_free (options.ignore_dirs);
 816             options.ignore_dirs = g_strdup (in_ignore->buffer);
 817 
 818             *content = !input_is_empty (in_with) ? g_strdup (in_with->buffer) : NULL;
 819             if (!input_is_empty (in_name))
 820                 *pattern = g_strdup (in_name->buffer);
 821             else
 822                 *pattern = g_strdup (options.file_pattern ? "*" : ".*");
 823             *start_dir = !input_is_empty (in_start) ? in_start->buffer : (char *) ".";
 824             if (in_start_dir != INPUT_LAST_TEXT)
 825                 g_free (in_start_dir);
 826             in_start_dir = g_strdup (*start_dir);
 827 
 828             s = tilde_expand (*start_dir);
 829             canonicalize_pathname (s);
 830 
 831             if (DIR_IS_DOT (s))
 832             {
 833                 *start_dir = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
 834                 /* FIXME: is current_panel->cwd_vpath canonicalized? */
 835                 /* relative paths will be used in panelization */
 836                 *start_dir_len = (ssize_t) strlen (*start_dir);
 837                 g_free (s);
 838             }
 839             else if (g_path_is_absolute (s))
 840             {
 841                 *start_dir = s;
 842                 *start_dir_len = -1;
 843             }
 844             else
 845             {
 846                 /* relative paths will be used in panelization */
 847                 *start_dir =
 848                     mc_build_filename (vfs_path_as_str (current_panel->cwd_vpath), s,
 849                                        (char *) NULL);
 850                 *start_dir_len = (ssize_t) strlen (vfs_path_as_str (current_panel->cwd_vpath));
 851                 g_free (s);
 852             }
 853 
 854             if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
 855                 || DIR_IS_DOT (in_ignore->buffer))
 856                 *ignore_dirs = NULL;
 857             else
 858                 *ignore_dirs = g_strdup (in_ignore->buffer);
 859 
 860             find_save_options ();
 861 
 862             return_value = TRUE;
 863         }
 864     }
 865 
 866     dlg_destroy (find_dlg);
 867 
 868     return return_value;
 869 }
 870 
 871 /* --------------------------------------------------------------------------------------------- */
 872 
 873 static inline void
 874 push_directory (vfs_path_t * dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
 875 {
 876     g_queue_push_head (&dir_queue, (void *) dir);
 877 }
 878 
 879 /* --------------------------------------------------------------------------------------------- */
 880 
 881 static inline vfs_path_t *
 882 pop_directory (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 883 {
 884     return (vfs_path_t *) g_queue_pop_head (&dir_queue);
 885 }
 886 
 887 /* --------------------------------------------------------------------------------------------- */
 888 /** Remove all the items from the stack */
 889 
 890 static void
 891 clear_stack (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 892 {
 893     g_queue_clear_full (&dir_queue, (GDestroyNotify) vfs_path_free);
 894 }
 895 
 896 /* --------------------------------------------------------------------------------------------- */
 897 
 898 static void
 899 insert_file (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 900 {
 901     char *tmp_name = NULL;
 902     static char *dirname = NULL;
 903     find_match_location_t *location;
 904 
 905     while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
 906         dir++;
 907 
 908     if (old_dir != NULL)
 909     {
 910         if (strcmp (old_dir, dir) != 0)
 911         {
 912             g_free (old_dir);
 913             old_dir = g_strdup (dir);
 914             dirname = add_to_list (dir, NULL);
 915         }
 916     }
 917     else
 918     {
 919         old_dir = g_strdup (dir);
 920         dirname = add_to_list (dir, NULL);
 921     }
 922 
 923     tmp_name = g_strdup_printf ("    %s", file);
 924     location = g_malloc (sizeof (*location));
 925     location->dir = dirname;
 926     location->start = start;
 927     location->end = end;
 928     add_to_list (tmp_name, location);
 929     g_free (tmp_name);
 930 }
 931 
 932 /* --------------------------------------------------------------------------------------------- */
 933 
 934 static void
 935 find_add_match (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 936 {
 937     insert_file (dir, file, start, end);
 938 
 939     /* Don't scroll */
 940     if (matches == 0)
 941         listbox_select_first (find_list);
 942     widget_redraw (WIDGET (find_list));
 943 
 944     matches++;
 945     found_num_update ();
 946 }
 947 
 948 /* --------------------------------------------------------------------------------------------- */
 949 
 950 static FindProgressStatus
 951 check_find_events (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 952 {
 953     Gpm_Event event;
 954     int c;
 955 
 956     event.x = -1;
 957     c = tty_get_event (&event, h->mouse_status == MOU_REPEAT, FALSE);
 958     if (c != EV_NONE)
 959     {
 960         dlg_process_event (h, c, &event);
 961         if (h->ret_value == B_ENTER
 962             || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
 963         {
 964             /* dialog terminated */
 965             return FIND_ABORT;
 966         }
 967         if (!widget_get_state (WIDGET (h), WST_IDLE))
 968         {
 969             /* searching suspended */
 970             return FIND_SUSPEND;
 971         }
 972     }
 973 
 974     return FIND_CONT;
 975 }
 976 
 977 /* --------------------------------------------------------------------------------------------- */
 978 /**
 979  * search_content:
 980  *
 981  * Search the content_pattern string in the DIRECTORY/FILE.
 982  * It will add the found entries to the find listbox.
 983  *
 984  * returns FALSE if do_search should look for another file
 985  *         TRUE if do_search should exit and proceed to the event handler
 986  */
 987 
 988 static gboolean
 989 search_content (WDialog * h, const char *directory, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 990 {
 991     struct stat s;
 992     char buffer[BUF_4K];        /* raw input buffer */
 993     int file_fd;
 994     gboolean ret_val = FALSE;
 995     vfs_path_t *vpath;
 996     struct timeval tv;
 997     time_t seconds;
 998     suseconds_t useconds;
 999     gboolean status_updated = FALSE;
1000 
1001     vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
1002 
1003     if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode))
1004     {
1005         vfs_path_free (vpath);
1006         return FALSE;
1007     }
1008 
1009     file_fd = mc_open (vpath, O_RDONLY);
1010     vfs_path_free (vpath);
1011 
1012     if (file_fd == -1)
1013         return FALSE;
1014 
1015     /* get time elapsed from last refresh */
1016     if (gettimeofday (&tv, NULL) == -1)
1017     {
1018         tv.tv_sec = 0;
1019         tv.tv_usec = 0;
1020         last_refresh = tv;
1021     }
1022     seconds = tv.tv_sec - last_refresh.tv_sec;
1023     useconds = tv.tv_usec - last_refresh.tv_usec;
1024     if (useconds < 0)
1025     {
1026         seconds--;
1027         useconds += G_USEC_PER_SEC;
1028     }
1029 
1030     if (s.st_size >= MIN_REFRESH_FILE_SIZE || seconds > 0 || useconds > MAX_REFRESH_INTERVAL)
1031     {
1032         g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename);
1033         status_update (str_trunc (buffer, WIDGET (h)->cols - 8));
1034         mc_refresh ();
1035         last_refresh = tv;
1036         status_updated = TRUE;
1037     }
1038 
1039     tty_enable_interrupt_key ();
1040     tty_got_interrupt ();
1041 
1042     {
1043         int line = 1;
1044         int pos = 0;
1045         int n_read = 0;
1046         off_t off = 0;          /* file_fd's offset corresponding to strbuf[0] */
1047         gboolean found = FALSE;
1048         gsize found_len;
1049         gsize found_start;
1050         char result[BUF_MEDIUM];
1051         char *strbuf = NULL;    /* buffer for fetched string */
1052         int strbuf_size = 0;
1053         int i = -1;             /* compensate for a newline we'll add when we first enter the loop */
1054 
1055         if (resuming)
1056         {
1057             /* We've been previously suspended, start from the previous position */
1058             resuming = FALSE;
1059             line = last_line;
1060             pos = last_pos;
1061             off = last_off;
1062             i = last_i;
1063         }
1064 
1065         while (!ret_val)
1066         {
1067             char ch = '\0';
1068 
1069             off += i + 1;       /* the previous line, plus a newline character */
1070             i = 0;
1071 
1072             /* read to buffer and get line from there */
1073             while (TRUE)
1074             {
1075                 if (pos >= n_read)
1076                 {
1077                     pos = 0;
1078                     n_read = mc_read (file_fd, buffer, sizeof (buffer));
1079                     if (n_read <= 0)
1080                         break;
1081                 }
1082 
1083                 ch = buffer[pos++];
1084                 if (ch == '\0')
1085                 {
1086                     /* skip possible leading zero(s) */
1087                     if (i == 0)
1088                     {
1089                         off++;
1090                         continue;
1091                     }
1092                     break;
1093                 }
1094 
1095                 if (i >= strbuf_size - 1)
1096                 {
1097                     strbuf_size += 128;
1098                     strbuf = g_realloc (strbuf, strbuf_size);
1099                 }
1100 
1101                 /* Strip newline */
1102                 if (ch == '\n')
1103                     break;
1104 
1105                 strbuf[i++] = ch;
1106             }
1107 
1108             if (i == 0)
1109             {
1110                 if (ch == '\0')
1111                     break;
1112 
1113                 /* if (ch == '\n'): do not search in empty strings */
1114                 goto skip_search;
1115             }
1116 
1117             strbuf[i] = '\0';
1118 
1119             if (!found          /* Search in binary line once */
1120                 && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
1121             {
1122                 if (!status_updated)
1123                 {
1124                     /* if we add results for a file, we have to ensure that
1125                        name of this file is shown in status bar */
1126                     g_snprintf (result, sizeof (result), _("Grepping in %s"), filename);
1127                     status_update (str_trunc (result, WIDGET (h)->cols - 8));
1128                     mc_refresh ();
1129                     last_refresh = tv;
1130                     status_updated = TRUE;
1131                 }
1132 
1133                 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
1134                 found_start = off + search_content_handle->normal_offset + 1;   /* off by one: ticket 3280 */
1135                 find_add_match (directory, result, found_start, found_start + found_len);
1136                 found = TRUE;
1137             }
1138 
1139             if (found && options.content_first_hit)
1140                 break;
1141 
1142             if (ch == '\n')
1143             {
1144               skip_search:
1145                 found = FALSE;
1146                 line++;
1147             }
1148 
1149             if ((line & 0xff) == 0)
1150             {
1151                 FindProgressStatus res;
1152 
1153                 res = check_find_events (h);
1154                 switch (res)
1155                 {
1156                 case FIND_ABORT:
1157                     stop_idle (h);
1158                     ret_val = TRUE;
1159                     break;
1160                 case FIND_SUSPEND:
1161                     resuming = TRUE;
1162                     last_line = line;
1163                     last_pos = pos;
1164                     last_off = off;
1165                     last_i = i;
1166                     ret_val = TRUE;
1167                     break;
1168                 default:
1169                     break;
1170                 }
1171             }
1172         }
1173 
1174         g_free (strbuf);
1175     }
1176 
1177     tty_disable_interrupt_key ();
1178     mc_close (file_fd);
1179     return ret_val;
1180 }
1181 
1182 /* --------------------------------------------------------------------------------------------- */
1183 
1184 /**
1185   If dir is absolute, this means we're within dir and searching file here.
1186   If dir is relative, this means we're going to add dir to the directory stack.
1187 **/
1188 static gboolean
1189 find_ignore_dir_search (const char *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
1190 {
1191     if (find_ignore_dirs != NULL)
1192     {
1193         const size_t dlen = strlen (dir);
1194         const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
1195 
1196         char **ignore_dir;
1197 
1198         for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
1199         {
1200             const size_t ilen = strlen (*ignore_dir);
1201             const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
1202 
1203             /* ignore dir is too long -- skip it */
1204             if (dlen < ilen)
1205                 continue;
1206 
1207             /* handle absolute and relative paths */
1208             switch (iabs | dabs)
1209             {
1210             case 0:            /* both paths are relative */
1211             case 3:            /* both paths are abolute */
1212                 /* if ignore dir is not a path  of dir -- skip it */
1213                 if (strncmp (dir, *ignore_dir, ilen) == 0)
1214                 {
1215                     /* be sure that ignore dir is not a part of dir like:
1216                        ignore dir is "h", dir is "home" */
1217                     if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
1218                         return TRUE;
1219                 }
1220                 break;
1221             case 1:            /* dir is absolute, ignore_dir is relative */
1222                 {
1223                     char *d;
1224 
1225                     d = strstr (dir, *ignore_dir);
1226                     if (d != NULL && IS_PATH_SEP (d[-1])
1227                         && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1228                         return TRUE;
1229                 }
1230                 break;
1231             case 2:            /* dir is relative, ignore_dir is absolute */
1232                 /* FIXME: skip this case */
1233                 break;
1234             default:           /* this cannot occurs */
1235                 return FALSE;
1236             }
1237         }
1238     }
1239 
1240     return FALSE;
1241 }
1242 
1243 /* --------------------------------------------------------------------------------------------- */
1244 
1245 static void
1246 find_rotate_dash (const WDialog * h, gboolean show)
     /* [previous][next][first][last][top][bottom][index][help]  */
1247 {
1248     static size_t pos = 0;
1249     static const char rotating_dash[4] = "|/-\\";
1250     const Widget *w = CONST_WIDGET (h);
1251 
1252     tty_setcolor (h->color[DLG_COLOR_NORMAL]);
1253     widget_move (h, w->lines - 7, w->cols - 4);
1254     tty_print_char (show ? rotating_dash[pos] : ' ');
1255     pos = (pos + 1) % sizeof (rotating_dash);
1256     mc_refresh ();
1257 }
1258 
1259 /* --------------------------------------------------------------------------------------------- */
1260 
1261 static int
1262 do_search (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1263 {
1264     static struct dirent *dp = NULL;
1265     static DIR *dirp = NULL;
1266     static char *directory = NULL;
1267     struct stat tmp_stat;
1268     gsize bytes_found;
1269     unsigned short count;
1270 
1271     if (h == NULL)
1272     {                           /* someone forces me to close dirp */
1273         if (dirp != NULL)
1274         {
1275             mc_closedir (dirp);
1276             dirp = NULL;
1277         }
1278         MC_PTR_FREE (directory);
1279         dp = NULL;
1280         return 1;
1281     }
1282 
1283     for (count = 0; count < 32; count++)
1284     {
1285         while (dp == NULL)
1286         {
1287             if (dirp != NULL)
1288             {
1289                 mc_closedir (dirp);
1290                 dirp = NULL;
1291             }
1292 
1293             while (dirp == NULL)
1294             {
1295                 vfs_path_t *tmp_vpath = NULL;
1296 
1297                 tty_setcolor (REVERSE_COLOR);
1298 
1299                 while (TRUE)
1300                 {
1301                     tmp_vpath = pop_directory ();
1302                     if (tmp_vpath == NULL)
1303                     {
1304                         running = FALSE;
1305                         if (ignore_count == 0)
1306                             status_update (_("Finished"));
1307                         else
1308                         {
1309                             char msg[BUF_SMALL];
1310 
1311                             g_snprintf (msg, sizeof (msg),
1312                                         ngettext ("Finished (ignored %zu directory)",
1313                                                   "Finished (ignored %zu directories)",
1314                                                   ignore_count), ignore_count);
1315                             status_update (msg);
1316                         }
1317                         if (verbose)
1318                             find_rotate_dash (h, FALSE);
1319                         stop_idle (h);
1320                         return 0;
1321                     }
1322 
1323                     /* handle absolute ignore dirs here */
1324                     {
1325                         gboolean ok;
1326 
1327                         ok = find_ignore_dir_search (vfs_path_as_str (tmp_vpath));
1328                         if (!ok)
1329                             break;
1330                     }
1331 
1332                     vfs_path_free (tmp_vpath);
1333                     ignore_count++;
1334                 }
1335 
1336                 g_free (directory);
1337                 directory = g_strdup (vfs_path_as_str (tmp_vpath));
1338 
1339                 if (verbose)
1340                 {
1341                     char buffer[BUF_MEDIUM];
1342 
1343                     g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory);
1344                     status_update (str_trunc (directory, WIDGET (h)->cols - 8));
1345                 }
1346 
1347                 dirp = mc_opendir (tmp_vpath);
1348                 vfs_path_free (tmp_vpath);
1349             }                   /* while (!dirp) */
1350 
1351             /* skip invalid filenames */
1352             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1353                 ;
1354         }                       /* while (!dp) */
1355 
1356         if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1357         {
1358             /* skip invalid filenames */
1359             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1360                 ;
1361 
1362             return 1;
1363         }
1364 
1365         if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1366         {
1367             gboolean search_ok;
1368 
1369             if (options.find_recurs && (directory != NULL))
1370             {                   /* Can directory be NULL ? */
1371                 /* handle relative ignore dirs here */
1372                 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name))
1373                     ignore_count++;
1374                 else
1375                 {
1376                     vfs_path_t *tmp_vpath;
1377 
1378                     tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1379 
1380                     if (mc_lstat (tmp_vpath, &tmp_stat) == 0 && S_ISDIR (tmp_stat.st_mode))
1381                         push_directory (tmp_vpath);
1382                     else
1383                         vfs_path_free (tmp_vpath);
1384                 }
1385             }
1386 
1387             search_ok = mc_search_run (search_file_handle, dp->d_name,
1388                                        0, strlen (dp->d_name), &bytes_found);
1389 
1390             if (search_ok)
1391             {
1392                 if (content_pattern == NULL)
1393                     find_add_match (directory, dp->d_name, 0, 0);
1394                 else if (search_content (h, directory, dp->d_name))
1395                     return 1;
1396             }
1397         }
1398 
1399         /* skip invalid filenames */
1400         while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1401             ;
1402     }                           /* for */
1403 
1404     if (verbose)
1405         find_rotate_dash (h, TRUE);
1406 
1407     return 1;
1408 }
1409 
1410 /* --------------------------------------------------------------------------------------------- */
1411 
1412 static void
1413 init_find_vars (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1414 {
1415     MC_PTR_FREE (old_dir);
1416     matches = 0;
1417     ignore_count = 0;
1418 
1419     /* Remove all the items from the stack */
1420     clear_stack ();
1421 
1422     g_strfreev (find_ignore_dirs);
1423     find_ignore_dirs = NULL;
1424 }
1425 
1426 /* --------------------------------------------------------------------------------------------- */
1427 
1428 static void
1429 find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
     /* [previous][next][first][last][top][bottom][index][help]  */
1430                    off_t search_end)
1431 {
1432     const char *filename = NULL;
1433     int line;
1434     vfs_path_t *fullname_vpath;
1435 
1436     if (content_pattern != NULL)
1437     {
1438         filename = strchr (file + 4, ':') + 1;
1439         line = atoi (file + 4);
1440     }
1441     else
1442     {
1443         filename = file + 4;
1444         line = 0;
1445     }
1446 
1447     fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1448     if (edit)
1449         edit_file_at_line (fullname_vpath, use_internal_edit, line);
1450     else
1451         view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1452                            search_end);
1453     vfs_path_free (fullname_vpath);
1454 }
1455 
1456 /* --------------------------------------------------------------------------------------------- */
1457 
1458 static cb_ret_t
1459 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
1460 {
1461     char *text = NULL;
1462     find_match_location_t *location;
1463 
1464     listbox_get_current (find_list, &text, (void **) &location);
1465 
1466     if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1467         return MSG_NOT_HANDLED;
1468 
1469     find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1470     return MSG_HANDLED;
1471 }
1472 
1473 /* --------------------------------------------------------------------------------------------- */
1474 
1475 static void
1476 find_calc_button_locations (const WDialog * h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1477 {
1478     const int cols = CONST_WIDGET (h)->cols;
1479 
1480     int l1, l2;
1481 
1482     l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1483     l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1484 
1485     fbuts[0].x = (cols - l1) / 2;
1486     fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1487     fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1488     fbuts[3].x = fbuts[2].x;
1489     fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1490 
1491     if (all_buttons)
1492     {
1493         fbuts[5].x = (cols - l2) / 2;
1494         fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1495         fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1496     }
1497 }
1498 
1499 /* --------------------------------------------------------------------------------------------- */
1500 
1501 static void
1502 find_adjust_header (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1503 {
1504     char title[BUF_MEDIUM];
1505     int title_len;
1506 
1507     if (content_pattern != NULL)
1508         g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern,
1509                     content_pattern);
1510     else
1511         g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern);
1512 
1513     title_len = str_term_width1 (title);
1514     if (title_len > WIDGET (h)->cols - 6)
1515     {
1516         /* title is too wide, truncate it */
1517         title_len = WIDGET (h)->cols - 6;
1518         title_len = str_column_to_pos (title, title_len);
1519         title_len -= 3;         /* reserve space for three dots */
1520         title_len = str_offset_to_pos (title, title_len);
1521         /* mark that title is truncated */
1522         memmove (title + title_len, "...", 4);
1523     }
1524 
1525     dlg_set_title (h, title);
1526 }
1527 
1528 /* --------------------------------------------------------------------------------------------- */
1529 
1530 static void
1531 find_relocate_buttons (const WDialog * h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1532 {
1533     size_t i;
1534 
1535     find_calc_button_locations (h, all_buttons);
1536 
1537     for (i = 0; i < fbuts_num; i++)
1538         fbuts[i].button->x = CONST_WIDGET (h)->x + fbuts[i].x;
1539 }
1540 
1541 /* --------------------------------------------------------------------------------------------- */
1542 
1543 static cb_ret_t
1544 find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1545 {
1546     WDialog *h = DIALOG (w);
1547 
1548     switch (msg)
1549     {
1550     case MSG_INIT:
1551         find_adjust_header (h);
1552         return MSG_HANDLED;
1553 
1554     case MSG_KEY:
1555         if (parm == KEY_F (3) || parm == KEY_F (13))
1556         {
1557             gboolean unparsed_view = (parm == KEY_F (13));
1558 
1559             return view_edit_currently_selected_file (unparsed_view, FALSE);
1560         }
1561         if (parm == KEY_F (4))
1562             return view_edit_currently_selected_file (FALSE, TRUE);
1563         return MSG_NOT_HANDLED;
1564 
1565     case MSG_RESIZE:
1566         dlg_set_size (h, LINES - 4, COLS - 16);
1567         find_adjust_header (h);
1568         find_relocate_buttons (h, TRUE);
1569         return MSG_HANDLED;
1570 
1571     case MSG_IDLE:
1572         do_search (h);
1573         return MSG_HANDLED;
1574 
1575     default:
1576         return dlg_default_callback (w, sender, msg, parm, data);
1577     }
1578 }
1579 
1580 /* --------------------------------------------------------------------------------------------- */
1581 /** Handles the Stop/Start button in the find window */
1582 
1583 static int
1584 start_stop (WButton * button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1585 {
1586     Widget *w = WIDGET (button);
1587 
1588     (void) action;
1589 
1590     running = is_start;
1591     widget_idle (WIDGET (find_dlg), running);
1592     is_start = !is_start;
1593 
1594     status_update (is_start ? _("Stopped") : _("Searching"));
1595     button_set_text (button, fbuts[is_start ? 3 : 2].text);
1596 
1597     find_relocate_buttons (w->owner, FALSE);
1598     dlg_redraw (w->owner);
1599 
1600     return 0;
1601 }
1602 
1603 /* --------------------------------------------------------------------------------------------- */
1604 /** Handle view command, when invoked as a button */
1605 
1606 static int
1607 find_do_view_file (WButton * button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1608 {
1609     (void) button;
1610     (void) action;
1611 
1612     view_edit_currently_selected_file (FALSE, FALSE);
1613     return 0;
1614 }
1615 
1616 /* --------------------------------------------------------------------------------------------- */
1617 /** Handle edit command, when invoked as a button */
1618 
1619 static int
1620 find_do_edit_file (WButton * button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1621 {
1622     (void) button;
1623     (void) action;
1624 
1625     view_edit_currently_selected_file (FALSE, TRUE);
1626     return 0;
1627 }
1628 
1629 /* --------------------------------------------------------------------------------------------- */
1630 
1631 static void
1632 setup_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1633 {
1634     size_t i;
1635     int lines, cols;
1636     int y;
1637 
1638     static gboolean i18n_flag = FALSE;
1639 
1640     if (!i18n_flag)
1641     {
1642         for (i = 0; i < fbuts_num; i++)
1643         {
1644 #ifdef ENABLE_NLS
1645             fbuts[i].text = _(fbuts[i].text);
1646 #endif /* ENABLE_NLS */
1647             fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1648             if (fbuts[i].flags == DEFPUSH_BUTTON)
1649                 fbuts[i].len += 2;
1650         }
1651 
1652         i18n_flag = TRUE;
1653     }
1654 
1655     lines = LINES - 4;
1656     cols = COLS - 16;
1657 
1658     find_dlg =
1659         dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL,
1660                     "[Find File]", NULL);
1661 
1662     find_calc_button_locations (find_dlg, TRUE);
1663 
1664     y = 2;
1665     find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1666     add_widget_autopos (find_dlg, find_list, WPOS_KEEP_ALL, NULL);
1667     y += WIDGET (find_list)->lines;
1668 
1669     add_widget_autopos (find_dlg, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1670 
1671     found_num_label = label_new (y++, 4, "");
1672     add_widget_autopos (find_dlg, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1673 
1674     status_label = label_new (y++, 4, _("Searching"));
1675     add_widget_autopos (find_dlg, status_label, WPOS_KEEP_BOTTOM, NULL);
1676 
1677     add_widget_autopos (find_dlg, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1678 
1679     for (i = 0; i < fbuts_num; i++)
1680     {
1681         if (i == 3)
1682             fbuts[3].button = fbuts[2].button;
1683         else
1684         {
1685             fbuts[i].button =
1686                 WIDGET (button_new
1687                         (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text,
1688                          fbuts[i].callback));
1689             add_widget_autopos (find_dlg, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1690         }
1691 
1692         if (i == quit_button)
1693             y++;
1694     }
1695 
1696     widget_select (WIDGET (find_list));
1697 }
1698 
1699 /* --------------------------------------------------------------------------------------------- */
1700 
1701 static int
1702 run_process (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1703 {
1704     int ret;
1705 
1706     search_content_handle = mc_search_new (content_pattern, NULL);
1707     if (search_content_handle)
1708     {
1709         search_content_handle->search_type =
1710             options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1711         search_content_handle->is_case_sensitive = options.content_case_sens;
1712         search_content_handle->whole_words = options.content_whole_words;
1713 #ifdef HAVE_CHARSET
1714         search_content_handle->is_all_charsets = options.content_all_charsets;
1715 #endif
1716     }
1717     search_file_handle = mc_search_new (find_pattern, NULL);
1718     search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1719     search_file_handle->is_case_sensitive = options.file_case_sens;
1720 #ifdef HAVE_CHARSET
1721     search_file_handle->is_all_charsets = options.file_all_charsets;
1722 #endif
1723     search_file_handle->is_entire_line = options.file_pattern;
1724 
1725     resuming = FALSE;
1726 
1727     widget_idle (WIDGET (find_dlg), TRUE);
1728     ret = dlg_run (find_dlg);
1729 
1730     mc_search_free (search_file_handle);
1731     search_file_handle = NULL;
1732     mc_search_free (search_content_handle);
1733     search_content_handle = NULL;
1734 
1735     return ret;
1736 }
1737 
1738 /* --------------------------------------------------------------------------------------------- */
1739 
1740 static void
1741 kill_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1742 {
1743     widget_idle (WIDGET (find_dlg), FALSE);
1744     dlg_destroy (find_dlg);
1745 }
1746 
1747 /* --------------------------------------------------------------------------------------------- */
1748 
1749 static int
1750 do_find (const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
     /* [previous][next][first][last][top][bottom][index][help]  */
1751          char **dirname, char **filename)
1752 {
1753     int return_value = 0;
1754     char *dir_tmp = NULL, *file_tmp = NULL;
1755 
1756     setup_gui ();
1757 
1758     init_find_vars ();
1759     parse_ignore_dirs (ignore_dirs);
1760     push_directory (vfs_path_from_str (start_dir));
1761 
1762     return_value = run_process ();
1763 
1764     /* Clear variables */
1765     init_find_vars ();
1766 
1767     get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1768 
1769     if (dir_tmp)
1770         *dirname = g_strdup (dir_tmp);
1771     if (file_tmp)
1772         *filename = g_strdup (file_tmp);
1773 
1774     if (return_value == B_PANELIZE && *filename)
1775     {
1776         int i;
1777         struct stat st;
1778         GList *entry;
1779         dir_list *list = &current_panel->dir;
1780         char *name = NULL;
1781 
1782         panel_clean_dir (current_panel);
1783         dir_list_init (list);
1784 
1785         for (i = 0, entry = listbox_get_first_link (find_list); entry != NULL;
1786              i++, entry = g_list_next (entry))
1787         {
1788             const char *lc_filename = NULL;
1789             WLEntry *le = LENTRY (entry->data);
1790             find_match_location_t *location = le->data;
1791             char *p;
1792             gboolean link_to_dir, stale_link;
1793 
1794             if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1795                 continue;
1796 
1797             if (!content_is_empty)
1798                 lc_filename = strchr (le->text + 4, ':') + 1;
1799             else
1800                 lc_filename = le->text + 4;
1801 
1802             name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1803             /* skip initial start dir */
1804             if (start_dir_len < 0)
1805                 p = name;
1806             else
1807             {
1808                 p = name + (size_t) start_dir_len;
1809                 if (IS_PATH_SEP (*p))
1810                     p++;
1811             }
1812 
1813             if (!handle_path (p, &st, &link_to_dir, &stale_link))
1814             {
1815                 g_free (name);
1816                 continue;
1817             }
1818             /* Need to grow the *list? */
1819             if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
1820             {
1821                 g_free (name);
1822                 break;
1823             }
1824 
1825             /* don't add files more than once to the panel */
1826             if (!content_is_empty && list->len != 0
1827                 && strcmp (list->list[list->len - 1].fname, p) == 0)
1828             {
1829                 g_free (name);
1830                 continue;
1831             }
1832 
1833             list->list[list->len].fnamelen = strlen (p);
1834             list->list[list->len].fname = g_strndup (p, list->list[list->len].fnamelen);
1835             list->list[list->len].f.marked = 0;
1836             list->list[list->len].f.link_to_dir = link_to_dir ? 1 : 0;
1837             list->list[list->len].f.stale_link = stale_link ? 1 : 0;
1838             list->list[list->len].f.dir_size_computed = 0;
1839             list->list[list->len].st = st;
1840             list->list[list->len].sort_key = NULL;
1841             list->list[list->len].second_sort_key = NULL;
1842             list->len++;
1843             g_free (name);
1844             if ((list->len & 15) == 0)
1845                 rotate_dash (TRUE);
1846         }
1847 
1848         current_panel->is_panelized = TRUE;
1849         panelize_absolutize_if_needed (current_panel);
1850         panelize_save_panel (current_panel);
1851     }
1852 
1853     kill_gui ();
1854     do_search (NULL);           /* force do_search to release resources */
1855     MC_PTR_FREE (old_dir);
1856     rotate_dash (FALSE);
1857 
1858     return return_value;
1859 }
1860 
1861 /* --------------------------------------------------------------------------------------------- */
1862 /*** public functions ****************************************************************************/
1863 /* --------------------------------------------------------------------------------------------- */
1864 
1865 void
1866 find_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1867 {
1868     char *start_dir = NULL, *ignore_dirs = NULL;
1869     ssize_t start_dir_len;
1870 
1871     find_pattern = NULL;
1872     content_pattern = NULL;
1873 
1874     while (find_parameters (&start_dir, &start_dir_len,
1875                             &ignore_dirs, &find_pattern, &content_pattern))
1876     {
1877         char *filename = NULL, *dirname = NULL;
1878         int v = B_CANCEL;
1879 
1880         content_is_empty = content_pattern == NULL;
1881 
1882         if (find_pattern[0] != '\0')
1883         {
1884             last_refresh.tv_sec = 0;
1885             last_refresh.tv_usec = 0;
1886 
1887             is_start = FALSE;
1888 
1889             if (!content_is_empty && !str_is_valid_string (content_pattern))
1890                 MC_PTR_FREE (content_pattern);
1891 
1892             v = do_find (start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1893         }
1894 
1895         g_free (start_dir);
1896         g_free (ignore_dirs);
1897         MC_PTR_FREE (find_pattern);
1898 
1899         if (v == B_ENTER)
1900         {
1901             if (dirname != NULL)
1902             {
1903                 vfs_path_t *dirname_vpath;
1904 
1905                 dirname_vpath = vfs_path_from_str (dirname);
1906                 do_cd (dirname_vpath, cd_exact);
1907                 vfs_path_free (dirname_vpath);
1908                 if (filename != NULL)
1909                     try_to_select (current_panel,
1910                                    filename + (content_pattern != NULL
1911                                                ? strchr (filename + 4, ':') - filename + 1 : 4));
1912             }
1913             else if (filename != NULL)
1914             {
1915                 vfs_path_t *filename_vpath;
1916 
1917                 filename_vpath = vfs_path_from_str (filename);
1918                 do_cd (filename_vpath, cd_exact);
1919                 vfs_path_free (filename_vpath);
1920             }
1921         }
1922 
1923         MC_PTR_FREE (content_pattern);
1924         g_free (dirname);
1925         g_free (filename);
1926 
1927         if (v == B_ENTER || v == B_CANCEL)
1928             break;
1929 
1930         if (v == B_PANELIZE)
1931         {
1932             panel_re_sort (current_panel);
1933             try_to_select (current_panel, NULL);
1934             break;
1935         }
1936     }
1937 }
1938 
1939 /* --------------------------------------------------------------------------------------------- */

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