/*
   Some misc dialog boxes for the program.

   Copyright (C) 1994-2022
   Free Software Foundation, Inc.

   Written by:
   Miguel de Icaza, 1994, 1995
   Jakub Jelinek, 1995
   Andrew Borodin <aborodin@vmail.ru>, 2009-2022

   This file is part of the Midnight Commander.

   The Midnight Commander is free software: you can redistribute it
   and/or modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation, either version 3 of the License,
   or (at your option) any later version.

   The Midnight Commander is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/** \file boxes.c
 *  \brief Source: Some misc dialog boxes for the program
 */

#include <config.h>

#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "lib/global.h"

#include "lib/tty/tty.h"
#include "lib/tty/color.h"      /* tty_use_colors() */
#include "lib/tty/key.h"        /* XCTRL and ALT macros  */
#include "lib/skin.h"           /* INPUT_COLOR */
#include "lib/mcconfig.h"       /* Load/save user formats */
#include "lib/strutil.h"

#include "lib/vfs/vfs.h"
#ifdef ENABLE_VFS_FTP
#include "src/vfs/ftpfs/ftpfs.h"
#endif /* ENABLE_VFS_FTP */

#include "lib/util.h"           /* Q_() */
#include "lib/widget.h"

#include "src/setup.h"
#include "src/history.h"        /* MC_HISTORY_ESC_TIMEOUT */
#include "src/execute.h"        /* pause_after_run */
#ifdef ENABLE_BACKGROUND
#include "src/background.h"     /* task_list */
#endif

#ifdef HAVE_CHARSET
#include "lib/charsets.h"
#include "src/selcodepage.h"
#endif

#include "command.h"            /* For cmdline */
#include "dir.h"
#include "tree.h"
#include "layout.h"             /* for get_nth_panel_name proto */
#include "filemanager.h"

#include "boxes.h"

/*** global variables ****************************************************************************/

/*** file scope macro definitions ****************************************************************/

#ifdef ENABLE_BACKGROUND
#define B_STOP   (B_USER+1)
#define B_RESUME (B_USER+2)
#define B_KILL   (B_USER+3)
#endif /* ENABLE_BACKGROUND */

/*** file scope type declarations ****************************************************************/

/*** file scope variables ************************************************************************/

static unsigned long configure_old_esc_mode_id, configure_time_out_id;

/* Index in list_formats[] for "brief" */
static const int panel_list_brief_idx = 1;
/* Index in list_formats[] for "user defined" */
static const int panel_list_user_idx = 3;

static char **status_format;
static unsigned long panel_list_formats_id, panel_user_format_id, panel_brief_cols_id;
static unsigned long user_mini_status_id, mini_user_format_id;

#ifdef HAVE_CHARSET
static int new_display_codepage;
#endif /* HAVE_CHARSET */

#if defined(ENABLE_VFS) && defined(ENABLE_VFS_FTP)
static unsigned long ftpfs_always_use_proxy_id, ftpfs_proxy_host_id;
#endif /* ENABLE_VFS && ENABLE_VFS_FTP */

static GPtrArray *skin_names;
static gchar *current_skin_name;

#ifdef ENABLE_BACKGROUND
static WListbox *bg_list = NULL;
#endif /* ENABLE_BACKGROUND */

static unsigned long shadows_id;

/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
configure_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_NOTIFY:
        /* message from "Single press" checkbutton */
        if (sender != NULL && sender->id == configure_old_esc_mode_id)
        {
            const gboolean not_single = !CHECK (sender)->state;
            Widget *ww;

            /* input line */
            ww = widget_find_by_id (w, configure_time_out_id);
            widget_disable (ww, not_single);

            return MSG_HANDLED;
        }
        return MSG_NOT_HANDLED;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static void
skin_apply (const gchar * skin_override)
{
    GError *mcerror = NULL;

    mc_skin_deinit ();
    mc_skin_init (skin_override, &mcerror);
    mc_fhl_free (&mc_filehighlight);
    mc_filehighlight = mc_fhl_new (TRUE);
    dlg_set_default_colors ();
    input_set_default_colors ();
    if (mc_global.mc_run_mode == MC_RUN_FULL)
        command_set_default_colors ();
    panel_deinit ();
    panel_init ();
    repaint_screen ();

    mc_error_message (&mcerror, NULL);
}

/* --------------------------------------------------------------------------------------------- */

static const gchar *
skin_name_to_label (const gchar * name)
{
    if (strcmp (name, "default") == 0)
        return _("< Default >");
    return name;
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
skin_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_RESIZE:
        {
            WDialog *d = DIALOG (w);
            const WRect *wd = &WIDGET (d->data)->rect;
            WRect r = w->rect;

            r.y = wd->y + (wd->lines - r.lines) / 2;
            r.x = wd->x + wd->cols / 2;

            return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
        }

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static int
sel_skin_button (WButton * button, int action)
{
    int result;
    WListbox *skin_list;
    WDialog *skin_dlg;
    const gchar *skin_name;
    unsigned int i;
    unsigned int pos = 1;

    (void) action;

    skin_dlg =
        dlg_create (TRUE, 0, 0, 13, 24, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, skin_dlg_callback,
                    NULL, "[Appearance]", _("Skins"));
    /* use Appearance dialog for positioning */
    skin_dlg->data = WIDGET (button)->owner;

    /* set dialog location before all */
    send_message (skin_dlg, NULL, MSG_RESIZE, 0, NULL);

    skin_list = listbox_new (1, 1, 11, 22, FALSE, NULL);
    skin_name = "default";
    listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
                      (void *) skin_name, FALSE);

    if (strcmp (skin_name, current_skin_name) == 0)
        listbox_select_entry (skin_list, 0);

    for (i = 0; i < skin_names->len; i++)
    {
        skin_name = g_ptr_array_index (skin_names, i);
        if (strcmp (skin_name, "default") != 0)
        {
            listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
                              (void *) skin_name, FALSE);
            if (strcmp (skin_name, current_skin_name) == 0)
                listbox_select_entry (skin_list, pos);
            pos++;
        }
    }

    /* make list stick to all sides of dialog, effectively make it be resized with dialog */
    group_add_widget_autopos (GROUP (skin_dlg), skin_list, WPOS_KEEP_ALL, NULL);

    result = dlg_run (skin_dlg);
    if (result == B_ENTER)
    {
        gchar *skin_label;

        listbox_get_current (skin_list, &skin_label, (void **) &skin_name);
        g_free (current_skin_name);
        current_skin_name = g_strdup (skin_name);
        skin_apply (skin_name);

        button_set_text (button, str_fit_to_term (skin_label, 20, J_LEFT_FIT));
    }
    widget_destroy (WIDGET (skin_dlg));

    return 0;
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
appearance_box_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_INIT:
#ifdef ENABLE_SHADOWS
        if (!tty_use_colors ())
#endif
        {
            Widget *shadow;

            shadow = widget_find_by_id (w, shadows_id);
            CHECK (shadow)->state = FALSE;
            widget_disable (shadow, TRUE);
        }
        return MSG_HANDLED;

    case MSG_NOTIFY:
        if (sender != NULL && sender->id == shadows_id)
        {
            mc_global.tty.shadows = CHECK (sender)->state;
            repaint_screen ();
            return MSG_HANDLED;
        }
        return MSG_NOT_HANDLED;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
panel_listing_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_NOTIFY:
        if (sender != NULL && sender->id == panel_list_formats_id)
        {
            WCheck *ch;
            WInput *in1, *in2, *in3;

            in1 = INPUT (widget_find_by_id (w, panel_user_format_id));
            in2 = INPUT (widget_find_by_id (w, panel_brief_cols_id));
            ch = CHECK (widget_find_by_id (w, user_mini_status_id));
            in3 = INPUT (widget_find_by_id (w, mini_user_format_id));

            if (!ch->state)
                input_assign_text (in3, status_format[RADIO (sender)->sel]);
            input_update (in1, FALSE);
            input_update (in2, FALSE);
            input_update (in3, FALSE);
            widget_disable (WIDGET (in1), RADIO (sender)->sel != panel_list_user_idx);
            widget_disable (WIDGET (in2), RADIO (sender)->sel != panel_list_brief_idx);
            return MSG_HANDLED;
        }

        if (sender != NULL && sender->id == user_mini_status_id)
        {
            WInput *in;

            in = INPUT (widget_find_by_id (w, mini_user_format_id));

            if (CHECK (sender)->state)
            {
                widget_disable (WIDGET (in), FALSE);
                input_assign_text (in, status_format[3]);
            }
            else
            {
                WRadio *r;

                r = RADIO (widget_find_by_id (w, panel_list_formats_id));
                widget_disable (WIDGET (in), TRUE);
                input_assign_text (in, status_format[r->sel]);
            }
            /* input_update (in, FALSE); */
            return MSG_HANDLED;
        }

        return MSG_NOT_HANDLED;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

#ifdef HAVE_CHARSET
static int
sel_charset_button (WButton * button, int action)
{
    int new_dcp;

    (void) action;

    new_dcp = select_charset (-1, -1, new_display_codepage, TRUE);

    if (new_dcp != SELECT_CHARSET_CANCEL)
    {
        const char *cpname;

        new_display_codepage = new_dcp;
        cpname = (new_display_codepage == SELECT_CHARSET_OTHER_8BIT) ?
            _("Other 8 bit") :
            ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;
        if (cpname != NULL)
            mc_global.utf8_display = str_isutf8 (cpname);
        else
            cpname = _("7-bit ASCII");  /* FIXME */

        button_set_text (button, cpname);
        widget_draw (WIDGET (WIDGET (button)->owner));
    }

    return 0;
}
#endif /* HAVE_CHARSET */

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    WDialog *h = DIALOG (w);

    switch (msg)
    {
    case MSG_RESIZE:
        {
            WRect r = w->rect;
            Widget *bar;

            r.lines = LINES - 9;
            r.cols = COLS - 20;
            dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);

            bar = WIDGET (find_buttonbar (h));
            bar->rect.x = 0;
            bar->rect.y = LINES - 1;
            return MSG_HANDLED;
        }

    case MSG_ACTION:
        return send_message (find_tree (h), NULL, MSG_ACTION, parm, NULL);

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

#if defined(ENABLE_VFS) && defined (ENABLE_VFS_FTP)
static cb_ret_t
confvfs_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_NOTIFY:
        /* message from "Always use ftp proxy" checkbutton */
        if (sender != NULL && sender->id == ftpfs_always_use_proxy_id)
        {
            const gboolean not_use = !CHECK (sender)->state;
            Widget *wi;

            /* input */
            wi = widget_find_by_id (w, ftpfs_proxy_host_id);
            widget_disable (wi, not_use);
            return MSG_HANDLED;
        }
        return MSG_NOT_HANDLED;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}
#endif /* ENABLE_VFS && ENABLE_VFS_FTP */

/* --------------------------------------------------------------------------------------------- */

#ifdef ENABLE_BACKGROUND
static void
jobs_fill_listbox (WListbox * list)
{
    static const char *state_str[2] = { "", "" };
    TaskList *tl;

    if (state_str[0][0] == '\0')
    {
        state_str[0] = _("Running");
        state_str[1] = _("Stopped");
    }

    for (tl = task_list; tl != NULL; tl = tl->next)
    {
        char *s;

        s = g_strconcat (state_str[tl->state], " ", tl->info, (char *) NULL);
        listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, s, (void *) tl, FALSE);
        g_free (s);
    }
}

/* --------------------------------------------------------------------------------------------- */

static int
task_cb (WButton * button, int action)
{
    TaskList *tl;
    int sig = 0;

    (void) button;

    if (bg_list->list == NULL)
        return 0;

    /* Get this instance information */
    listbox_get_current (bg_list, NULL, (void **) &tl);

#ifdef SIGTSTP
    if (action == B_STOP)
    {
        sig = SIGSTOP;
        tl->state = Task_Stopped;
    }
    else if (action == B_RESUME)
    {
        sig = SIGCONT;
        tl->state = Task_Running;
    }
    else
#endif
    if (action == B_KILL)
        sig = SIGKILL;

    if (sig == SIGKILL)
        unregister_task_running (tl->pid, tl->fd);

    kill (tl->pid, sig);
    listbox_remove_list (bg_list);
    jobs_fill_listbox (bg_list);

    /* This can be optimized to just redraw this widget :-) */
    widget_draw (WIDGET (WIDGET (button)->owner));

    return 0;
}
#endif /* ENABLE_BACKGROUND */

/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */

void
configure_box (void)
{
    const char *pause_options[] = {
        N_("&Never"),
        N_("On dum&b terminals"),
        N_("Alwa&ys")
    };

    int pause_options_num;

    pause_options_num = G_N_ELEMENTS (pause_options);

    {
        char time_out[BUF_TINY] = "";
        char *time_out_new;

        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_START_GROUPBOX (N_("File operations")),
                    QUICK_CHECKBOX (N_("&Verbose operation"), &verbose, NULL),
                    QUICK_CHECKBOX (N_("Compute tota&ls"), &file_op_compute_totals, NULL),
                    QUICK_CHECKBOX (N_("Classic pro&gressbar"), &classic_progressbar, NULL),
                    QUICK_CHECKBOX (N_("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL),
                    QUICK_CHECKBOX (N_("&Preallocate space"), &mc_global.vfs.preallocate_space,
                                    NULL),
                QUICK_STOP_GROUPBOX,
                QUICK_START_GROUPBOX (N_("Esc key mode")),
                    QUICK_CHECKBOX (N_("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id),
                    QUICK_LABELED_INPUT (N_("Timeout:"), input_label_left,
                                         (const char *) time_out, MC_HISTORY_ESC_TIMEOUT,
                                         &time_out_new, &configure_time_out_id, FALSE, FALSE,
                                         INPUT_COMPLETE_NONE),
                QUICK_STOP_GROUPBOX,
                QUICK_START_GROUPBOX (N_("Pause after run")),
                    QUICK_RADIO (pause_options_num, pause_options, &pause_after_run, NULL),
                QUICK_STOP_GROUPBOX,
            QUICK_NEXT_COLUMN,
                QUICK_START_GROUPBOX (N_("Other options")),
                    QUICK_CHECKBOX (N_("Use internal edi&t"), &use_internal_edit, NULL),
                    QUICK_CHECKBOX (N_("Use internal vie&w"), &use_internal_view, NULL),
                    QUICK_CHECKBOX (N_("A&sk new file name"),
                                    &editor_ask_filename_before_edit, NULL),
                    QUICK_CHECKBOX (N_("Auto m&enus"), &auto_menu, NULL),
                    QUICK_CHECKBOX (N_("&Drop down menus"), &drop_menus, NULL),
                    QUICK_CHECKBOX (N_("S&hell patterns"), &easy_patterns, NULL),
                    QUICK_CHECKBOX (N_("Co&mplete: show all"),
                                    &mc_global.widget.show_all_if_ambiguous, NULL),
                    QUICK_CHECKBOX (N_("Rotating d&ash"), &nice_rotating_dash, NULL),
                    QUICK_CHECKBOX (N_("Cd follows lin&ks"), &mc_global.vfs.cd_symlinks, NULL),
                    QUICK_CHECKBOX (N_("Sa&fe delete"), &safe_delete, NULL),
                    QUICK_CHECKBOX (N_("Safe overwrite"), &safe_overwrite, NULL),       /* w/o hotkey */
                    QUICK_CHECKBOX (N_("A&uto save setup"), &auto_save_setup, NULL),
                    QUICK_SEPARATOR (FALSE),
                    QUICK_SEPARATOR (FALSE),
                QUICK_STOP_GROUPBOX,
            QUICK_STOP_COLUMNS,
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 60 };

        quick_dialog_t qdlg = {
            r, N_("Configure options"), "[Configuration]",
            quick_widgets, configure_callback, NULL
        };

        g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout);

#ifndef USE_INTERNAL_EDIT
        quick_widgets[17].state = WST_DISABLED;
#endif

        if (!old_esc_mode)
            quick_widgets[10].state = quick_widgets[11].state = WST_DISABLED;

#ifndef HAVE_POSIX_FALLOCATE
        mc_global.vfs.preallocate_space = FALSE;
        quick_widgets[7].state = WST_DISABLED;
#endif

        if (quick_dialog (&qdlg) == B_ENTER)
            old_esc_mode_timeout = atoi (time_out_new);

        g_free (time_out_new);
    }
}

/* --------------------------------------------------------------------------------------------- */

void
appearance_box (void)
{
    gboolean shadows = mc_global.tty.shadows;

    current_skin_name = g_strdup (mc_skin__default.name);
    skin_names = mc_skin_list ();

    {
        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_LABEL (N_("Skin:"), NULL),
            QUICK_NEXT_COLUMN,
                QUICK_BUTTON (str_fit_to_term (skin_name_to_label (current_skin_name), 20, J_LEFT_FIT),
                              B_USER, sel_skin_button, NULL),
            QUICK_STOP_COLUMNS,
            QUICK_SEPARATOR (TRUE),
            QUICK_CHECKBOX (N_("&Shadows"), &mc_global.tty.shadows, &shadows_id),
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 54 };

        quick_dialog_t qdlg = {
            r, N_("Appearance"), "[Appearance]",
            quick_widgets, appearance_box_callback, NULL
        };

        if (quick_dialog (&qdlg) == B_ENTER)
            mc_config_set_string (mc_global.main_config, CONFIG_APP_SECTION, "skin",
                                  current_skin_name);
        else
        {
            skin_apply (NULL);
            mc_global.tty.shadows = shadows;
        }
    }

    g_free (current_skin_name);
    g_ptr_array_foreach (skin_names, (GFunc) g_free, NULL);
    g_ptr_array_free (skin_names, TRUE);
}

/* --------------------------------------------------------------------------------------------- */

void
panel_options_box (void)
{
    gboolean simple_swap;

    simple_swap = mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION,
                                      "simple_swap", FALSE);
    {
        const char *qsearch_options[] = {
            N_("Case &insensitive"),
            N_("Cas&e sensitive"),
            N_("Use panel sort mo&de")
        };

        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_START_GROUPBOX (N_("Main options")),
                    QUICK_CHECKBOX (N_("Show mi&ni-status"), &panels_options.show_mini_info, NULL),
                    QUICK_CHECKBOX (N_("Use SI si&ze units"), &panels_options.kilobyte_si, NULL),
                    QUICK_CHECKBOX (N_("Mi&x all files"), &panels_options.mix_all_files, NULL),
                    QUICK_CHECKBOX (N_("Show &backup files"), &panels_options.show_backups, NULL),
                    QUICK_CHECKBOX (N_("Show &hidden files"), &panels_options.show_dot_files, NULL),
                    QUICK_CHECKBOX (N_("&Fast dir reload"), &panels_options.fast_reload, NULL),
                    QUICK_CHECKBOX (N_("Ma&rk moves down"), &panels_options.mark_moves_down, NULL),
                    QUICK_CHECKBOX (N_("Re&verse files only"), &panels_options.reverse_files_only,
                                    NULL),
                    QUICK_CHECKBOX (N_("Simple s&wap"), &simple_swap, NULL),
                    QUICK_CHECKBOX (N_("A&uto save panels setup"), &panels_options.auto_save_setup,
                                    NULL),
                    QUICK_SEPARATOR (FALSE),
                    QUICK_SEPARATOR (FALSE),
                    QUICK_SEPARATOR (FALSE),
                QUICK_STOP_GROUPBOX,
            QUICK_NEXT_COLUMN,
                QUICK_START_GROUPBOX (N_("Navigation")),
                    QUICK_CHECKBOX (N_("L&ynx-like motion"), &panels_options.navigate_with_arrows,
                                    NULL),
                    QUICK_CHECKBOX (N_("Pa&ge scrolling"), &panels_options.scroll_pages, NULL),
                    QUICK_CHECKBOX (N_("Center &scrolling"), &panels_options.scroll_center, NULL),
                    QUICK_CHECKBOX (N_("&Mouse page scrolling"), &panels_options.mouse_move_pages,
                                    NULL),
                QUICK_STOP_GROUPBOX,
                QUICK_START_GROUPBOX (N_("File highlight")),
                    QUICK_CHECKBOX (N_("File &types"), &panels_options.filetype_mode, NULL),
                    QUICK_CHECKBOX (N_("&Permissions"), &panels_options.permission_mode, NULL),
                QUICK_STOP_GROUPBOX,
                QUICK_START_GROUPBOX (N_("Quick search")),
                    QUICK_RADIO (QSEARCH_NUM, qsearch_options, (int *) &panels_options.qsearch_mode,
                                 NULL),
                QUICK_STOP_GROUPBOX,
            QUICK_STOP_COLUMNS,
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 60 };

        quick_dialog_t qdlg = {
            r, N_("Panel options"), "[Panel options]",
            quick_widgets, NULL, NULL
        };

        if (quick_dialog (&qdlg) != B_ENTER)
            return;
    }

    mc_config_set_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", simple_swap);

    if (!panels_options.fast_reload_msg_shown && panels_options.fast_reload)
    {
        message (D_NORMAL, _("Information"),
                 _("Using the fast reload option may not reflect the exact\n"
                   "directory contents. In this case you'll need to do a\n"
                   "manual reload of the directory. See the man page for\n" "the details."));
        panels_options.fast_reload_msg_shown = TRUE;
    }

    update_panels (UP_RELOAD, UP_KEEPSEL);
}

/* --------------------------------------------------------------------------------------------- */

/* return list type */
int
panel_listing_box (WPanel * panel, int num, char **userp, char **minip, gboolean * use_msformat,
                   int *brief_cols)
{
    int result = -1;
    const char *p = NULL;

    if (panel == NULL)
    {
        p = get_nth_panel_name (num);
        panel = panel_empty_new (p);
    }

    {
        gboolean user_mini_status;
        char panel_brief_cols_in[BUF_TINY];
        char *panel_brief_cols_out = NULL;
        char *panel_user_format = NULL;
        char *mini_user_format = NULL;

        /* Controls whether the array strings have been translated */
        const char *list_formats[LIST_FORMATS] = {
            N_("&Full file list"),
            N_("&Brief file list:"),
            N_("&Long file list"),
            N_("&User defined:")
        };

        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_RADIO (LIST_FORMATS, list_formats, &result, &panel_list_formats_id),
            QUICK_NEXT_COLUMN,
                QUICK_SEPARATOR (FALSE),
                QUICK_LABELED_INPUT (_ ("columns"), input_label_right, panel_brief_cols_in,
                                     "panel-brief-cols-input", &panel_brief_cols_out,
                                     &panel_brief_cols_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
            QUICK_STOP_COLUMNS,
            QUICK_INPUT (panel->user_format, "user-fmt-input", &panel_user_format,
                         &panel_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
            QUICK_SEPARATOR (TRUE),
            QUICK_CHECKBOX (N_("User &mini status"), &user_mini_status, &user_mini_status_id),
            QUICK_INPUT (panel->user_status_format[panel->list_format], "mini_input",
                         &mini_user_format, &mini_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 48 };

        quick_dialog_t qdlg = {
            r, N_("Listing format"), "[Listing Format...]",
            quick_widgets, panel_listing_callback, NULL
        };

        user_mini_status = panel->user_mini_status;
        result = panel->list_format;
        status_format = panel->user_status_format;

        g_snprintf (panel_brief_cols_in, sizeof (panel_brief_cols_in), "%d", panel->brief_cols);

        if ((int) panel->list_format != panel_list_brief_idx)
            quick_widgets[4].state = WST_DISABLED;

        if ((int) panel->list_format != panel_list_user_idx)
            quick_widgets[6].state = WST_DISABLED;

        if (!user_mini_status)
            quick_widgets[9].state = WST_DISABLED;

        if (quick_dialog (&qdlg) == B_CANCEL)
            result = -1;
        else
        {
            int cols;
            char *error = NULL;

            *userp = panel_user_format;
            *minip = mini_user_format;
            *use_msformat = user_mini_status;

            cols = strtol (panel_brief_cols_out, &error, 10);
            if (*error == '\0')
                *brief_cols = cols;
            else
                *brief_cols = panel->brief_cols;

            g_free (panel_brief_cols_out);
        }
    }

    if (p != NULL)
    {
        int i;

        g_free (panel->user_format);
        for (i = 0; i < LIST_FORMATS; i++)
            g_free (panel->user_status_format[i]);
        g_free (panel);
    }

    return result;
}

/* --------------------------------------------------------------------------------------------- */

const panel_field_t *
sort_box (dir_sort_options_t * op, const panel_field_t * sort_field)
{
    char **sort_orders_names;
    gsize i;
    gsize sort_names_num = 0;
    int sort_idx = 0;
    const panel_field_t *result = NULL;

    sort_orders_names = panel_get_sortable_fields (&sort_names_num);

    for (i = 0; i < sort_names_num; i++)
        if (strcmp (sort_orders_names[i], _(sort_field->title_hotkey)) == 0)
        {
            sort_idx = i;
            break;
        }

    {
        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_RADIO (sort_names_num, (const char **) sort_orders_names, &sort_idx, NULL),
            QUICK_NEXT_COLUMN,
                QUICK_CHECKBOX (N_("Executable &first"), &op->exec_first, NULL),
                QUICK_CHECKBOX (N_("Cas&e sensitive"), &op->case_sensitive, NULL),
                QUICK_CHECKBOX (N_("&Reverse"), &op->reverse, NULL),
            QUICK_STOP_COLUMNS,
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 40 };

        quick_dialog_t qdlg = {
            r, N_("Sort order"), "[Sort Order...]",
            quick_widgets, NULL, NULL
        };

        if (quick_dialog (&qdlg) != B_CANCEL)
            result = panel_get_field_by_title_hotkey (sort_orders_names[sort_idx]);

        if (result == NULL)
            result = sort_field;
    }

    g_strfreev (sort_orders_names);

    return result;
}

/* --------------------------------------------------------------------------------------------- */

void
confirm_box (void)
{
    quick_widget_t quick_widgets[] = {
        /* *INDENT-OFF* */
        /* TRANSLATORS: no need to translate 'Confirmation', it's just a context prefix */
        QUICK_CHECKBOX (Q_("Confirmation|&Delete"), &confirm_delete, NULL),
        QUICK_CHECKBOX (Q_("Confirmation|O&verwrite"), &confirm_overwrite, NULL),
        QUICK_CHECKBOX (Q_("Confirmation|&Execute"), &confirm_execute, NULL),
        QUICK_CHECKBOX (Q_("Confirmation|E&xit"), &confirm_exit, NULL),
        QUICK_CHECKBOX (Q_("Confirmation|Di&rectory hotlist delete"),
                        &confirm_directory_hotlist_delete, NULL),
        QUICK_CHECKBOX (Q_("Confirmation|&History cleanup"),
                        &mc_global.widget.confirm_history_cleanup, NULL),
        QUICK_BUTTONS_OK_CANCEL,
        QUICK_END
        /* *INDENT-ON* */
    };

    WRect r = { -1, -1, 0, 46 };

    quick_dialog_t qdlg = {
        r, N_("Confirmation"), "[Confirmation]",
        quick_widgets, NULL, NULL
    };

    (void) quick_dialog (&qdlg);
}

/* --------------------------------------------------------------------------------------------- */

#ifndef HAVE_CHARSET
void
display_bits_box (void)
{
    gboolean new_meta;
    int current_mode;

    const char *display_bits_str[] = {
        N_("&UTF-8 output"),
        N_("&Full 8 bits output"),
        N_("&ISO 8859-1"),
        N_("7 &bits")
    };

    quick_widget_t quick_widgets[] = {
        /* *INDENT-OFF* */
        QUICK_RADIO (4, display_bits_str, &current_mode, NULL),
        QUICK_SEPARATOR (TRUE),
        QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
        QUICK_BUTTONS_OK_CANCEL,
        QUICK_END
        /* *INDENT-ON* */
    };

    WRect r = { -1, -1, 0, 46 };

    quick_dialog_t qdlg = {
        r, _("Display bits"), "[Display bits]",
        quick_widgets, NULL, NULL
    };

    if (mc_global.full_eight_bits)
        current_mode = 0;
    else if (mc_global.eight_bit_clean)
        current_mode = 1;
    else
        current_mode = 2;

    new_meta = !use_8th_bit_as_meta;

    if (quick_dialog (&qdlg) != B_CANCEL)
    {
        mc_global.eight_bit_clean = current_mode < 3;
        mc_global.full_eight_bits = current_mode < 2;
#ifndef HAVE_SLANG
        tty_display_8bit (mc_global.eight_bit_clean);
#else
        tty_display_8bit (mc_global.full_eight_bits);
#endif
        use_8th_bit_as_meta = !new_meta;
    }
}

/* --------------------------------------------------------------------------------------------- */
#else /* HAVE_CHARSET */

void
display_bits_box (void)
{
    const char *cpname;

    new_display_codepage = mc_global.display_codepage;

    cpname = (new_display_codepage < 0) ? _("Other 8 bit")
        : ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;

    {
        gboolean new_meta;

        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_START_COLUMNS,
                QUICK_LABEL (N_("Input / display codepage:"), NULL),
            QUICK_NEXT_COLUMN,
                QUICK_BUTTON (cpname, B_USER, sel_charset_button, NULL),
            QUICK_STOP_COLUMNS,
            QUICK_SEPARATOR (TRUE),
                QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 46 };

        quick_dialog_t qdlg = {
            r, N_("Display bits"), "[Display bits]",
            quick_widgets, NULL, NULL
        };

        new_meta = !use_8th_bit_as_meta;
        application_keypad_mode ();

        if (quick_dialog (&qdlg) == B_ENTER)
        {
            char *errmsg;

            mc_global.display_codepage = new_display_codepage;

            errmsg = init_translation_table (mc_global.source_codepage, mc_global.display_codepage);
            if (errmsg != NULL)
            {
                message (D_ERROR, MSG_ERROR, "%s", errmsg);
                g_free (errmsg);
            }

#ifdef HAVE_SLANG
            tty_display_8bit (mc_global.display_codepage != 0 && mc_global.display_codepage != 1);
#else
            tty_display_8bit (mc_global.display_codepage != 0);
#endif
            use_8th_bit_as_meta = !new_meta;

            repaint_screen ();
        }
    }
}
#endif /* HAVE_CHARSET */

/* --------------------------------------------------------------------------------------------- */
/** Show tree in a box, not on a panel */

char *
tree_box (const char *current_dir)
{
    WTree *mytree;
    WDialog *dlg;
    WGroup *g;
    Widget *wd;
    char *val = NULL;
    WButtonBar *bar;

    (void) current_dir;

    /* Create the components */
    dlg = dlg_create (TRUE, 0, 0, LINES - 9, COLS - 20, WPOS_CENTER, FALSE, dialog_colors,
                      tree_callback, NULL, "[Directory Tree]", _("Directory tree"));
    g = GROUP (dlg);
    wd = WIDGET (dlg);

    mytree = tree_new (2, 2, wd->rect.lines - 6, wd->rect.cols - 5, FALSE);
    group_add_widget_autopos (g, mytree, WPOS_KEEP_ALL, NULL);
    group_add_widget_autopos (g, hline_new (wd->rect.lines - 4, 1, -1), WPOS_KEEP_BOTTOM, NULL);
    bar = buttonbar_new ();
    group_add_widget (g, bar);
    /* restore ButtonBar coordinates after add_widget() */
    WIDGET (bar)->rect.x = 0;
    WIDGET (bar)->rect.y = LINES - 1;

    if (dlg_run (dlg) == B_ENTER)
    {
        const vfs_path_t *selected_name;

        selected_name = tree_selected_name (mytree);
        val = g_strdup (vfs_path_as_str (selected_name));
    }

    widget_destroy (wd);
    return val;
}

/* --------------------------------------------------------------------------------------------- */

#ifdef ENABLE_VFS
void
configure_vfs_box (void)
{
    char buffer2[BUF_TINY];
#ifdef ENABLE_VFS_FTP
    char buffer3[BUF_TINY];

    g_snprintf (buffer3, sizeof (buffer3), "%i", ftpfs_directory_timeout);
#endif

    g_snprintf (buffer2, sizeof (buffer2), "%i", vfs_timeout);

    {
        char *ret_timeout;
#ifdef ENABLE_VFS_FTP
        char *ret_passwd;
        char *ret_ftp_proxy;
        char *ret_directory_timeout;
#endif /* ENABLE_VFS_FTP */

        quick_widget_t quick_widgets[] = {
            /* *INDENT-OFF* */
            QUICK_LABELED_INPUT (N_("Timeout for freeing VFSs (sec):"), input_label_left,
                                 buffer2, "input-timo-vfs", &ret_timeout, NULL, FALSE, FALSE,
                                 INPUT_COMPLETE_NONE),
#ifdef ENABLE_VFS_FTP
            QUICK_SEPARATOR (TRUE),
            QUICK_LABELED_INPUT (N_("FTP anonymous password:"), input_label_left,
                                 ftpfs_anonymous_passwd, "input-passwd", &ret_passwd, NULL,
                                 FALSE, FALSE, INPUT_COMPLETE_NONE),
            QUICK_LABELED_INPUT (N_("FTP directory cache timeout (sec):"), input_label_left,
                                 buffer3, "input-timeout", &ret_directory_timeout, NULL,
                                 FALSE, FALSE, INPUT_COMPLETE_NONE),
            QUICK_CHECKBOX (N_("&Always use ftp proxy:"), &ftpfs_always_use_proxy,
                            &ftpfs_always_use_proxy_id),
            QUICK_INPUT (ftpfs_proxy_host, "input-ftp-proxy", &ret_ftp_proxy,
                         &ftpfs_proxy_host_id, FALSE, FALSE, INPUT_COMPLETE_HOSTNAMES),
            QUICK_CHECKBOX (N_("&Use ~/.netrc"), &ftpfs_use_netrc, NULL),
            QUICK_CHECKBOX (N_("Use &passive mode"), &ftpfs_use_passive_connections, NULL),
            QUICK_CHECKBOX (N_("Use passive mode over pro&xy"),
                            &ftpfs_use_passive_connections_over_proxy, NULL),
#endif /* ENABLE_VFS_FTP */
            QUICK_BUTTONS_OK_CANCEL,
            QUICK_END
            /* *INDENT-ON* */
        };

        WRect r = { -1, -1, 0, 56 };

        quick_dialog_t qdlg = {
            r, N_("Virtual File System Setting"), "[Virtual FS]",
            quick_widgets,
#ifdef ENABLE_VFS_FTP
            confvfs_callback,
#else
            NULL,
#endif
            NULL,
        };

#ifdef ENABLE_VFS_FTP
        if (!ftpfs_always_use_proxy)
            quick_widgets[5].state = WST_DISABLED;
#endif

        if (quick_dialog (&qdlg) != B_CANCEL)
        {
            /* cppcheck-suppress uninitvar */
            vfs_timeout = atoi (ret_timeout);
            g_free (ret_timeout);

            if (vfs_timeout < 0 || vfs_timeout > 10000)
                vfs_timeout = 10;
#ifdef ENABLE_VFS_FTP
            g_free (ftpfs_anonymous_passwd);
            /* cppcheck-suppress uninitvar */
            ftpfs_anonymous_passwd = ret_passwd;
            g_free (ftpfs_proxy_host);
            /* cppcheck-suppress uninitvar */
            ftpfs_proxy_host = ret_ftp_proxy;
            /* cppcheck-suppress uninitvar */
            ftpfs_directory_timeout = atoi (ret_directory_timeout);
            g_free (ret_directory_timeout);
#endif
        }
    }
}

#endif /* ENABLE_VFS */

/* --------------------------------------------------------------------------------------------- */

char *
cd_box (const WPanel * panel)
{
    const Widget *w = CONST_WIDGET (panel);
    char *my_str;

    quick_widget_t quick_widgets[] = {
        QUICK_LABELED_INPUT (N_("cd"), input_label_left, "", "input", &my_str, NULL, FALSE, TRUE,
                             INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
        QUICK_END
    };

    WRect r = { w->rect.y + w->rect.lines - 6, w->rect.x, 0, w->rect.cols };

    quick_dialog_t qdlg = {
        r, N_("Quick cd"), "[Quick cd]",
        quick_widgets, NULL, NULL
    };

    return (quick_dialog (&qdlg) != B_CANCEL) ? my_str : NULL;
}

/* --------------------------------------------------------------------------------------------- */

void
symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath,
             char **ret_existing, char **ret_new)
{
    quick_widget_t quick_widgets[] = {
        /* *INDENT-OFF* */
        QUICK_LABELED_INPUT (N_("Existing filename (filename symlink will point to):"),
                             input_label_above, vfs_path_as_str (existing_vpath), "input-2",
                             ret_existing, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
        QUICK_SEPARATOR (FALSE),
        QUICK_LABELED_INPUT (N_("Symbolic link filename:"), input_label_above,
                             vfs_path_as_str (new_vpath), "input-1",
                             ret_new, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
        QUICK_BUTTONS_OK_CANCEL,
        QUICK_END
        /* *INDENT-ON* */
    };

    WRect r = { -1, -1, 0, 64 };

    quick_dialog_t qdlg = {
        r, N_("Symbolic link"), "[File Menu]",
        quick_widgets, NULL, NULL
    };

    if (quick_dialog (&qdlg) == B_CANCEL)
    {
        *ret_new = NULL;
        *ret_existing = NULL;
    }
}

/* --------------------------------------------------------------------------------------------- */

#ifdef ENABLE_BACKGROUND
void
jobs_box (void)
{
    struct
    {
        const char *name;
        int flags;
        int value;
        int len;
        bcback_fn callback;
    }
    job_but[] =
    {
        /* *INDENT-OFF* */
        { N_("&Stop"), NORMAL_BUTTON, B_STOP, 0, task_cb },
        { N_("&Resume"), NORMAL_BUTTON, B_RESUME, 0, task_cb },
        { N_("&Kill"), NORMAL_BUTTON, B_KILL, 0, task_cb },
        { N_("&OK"), DEFPUSH_BUTTON, B_CANCEL, 0, NULL }
        /* *INDENT-ON* */
    };

    size_t i;
    const size_t n_but = G_N_ELEMENTS (job_but);

    WDialog *jobs_dlg;
    WGroup *g;
    int cols = 60;
    int lines = 15;
    int x = 0;

    for (i = 0; i < n_but; i++)
    {
#ifdef ENABLE_NLS
        job_but[i].name = _(job_but[i].name);
#endif /* ENABLE_NLS */

        job_but[i].len = str_term_width1 (job_but[i].name) + 3;
        if (job_but[i].flags == DEFPUSH_BUTTON)
            job_but[i].len += 2;
        x += job_but[i].len;
    }

    x += (int) n_but - 1;
    cols = MAX (cols, x + 6);

    jobs_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL,
                           "[Background jobs]", _("Background jobs"));
    g = GROUP (jobs_dlg);

    bg_list = listbox_new (2, 2, lines - 6, cols - 6, FALSE, NULL);
    jobs_fill_listbox (bg_list);
    group_add_widget (g, bg_list);

    group_add_widget (g, hline_new (lines - 4, -1, -1));

    x = (cols - x) / 2;
    for (i = 0; i < n_but; i++)
    {
        group_add_widget (g, button_new (lines - 3, x, job_but[i].value, job_but[i].flags,
                                         job_but[i].name, job_but[i].callback));
        x += job_but[i].len + 1;
    }

    (void) dlg_run (jobs_dlg);
    widget_destroy (WIDGET (jobs_dlg));
}
#endif /* ENABLE_BACKGROUND */

/* --------------------------------------------------------------------------------------------- */
