/**
 * @file gnome-cmd-user-actions.cc
 * @copyright (C) 2001-2006 Marcus Bjurman\n
 * @copyright (C) 2007-2012 Piotr Eljasiak\n
 * @copyright (C) 2013-2023 Uwe Scholz\n
 *
 * @copyright This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * @copyright This program 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.
 *
 * @copyright You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <gtk/gtk.h>
#include <set>
#include <algorithm>

#include "gnome-cmd-includes.h"
#include "gnome-cmd-con-home.h"
#include "gnome-cmd-con-list.h"
#include "gnome-cmd-data.h"
#include "gnome-cmd-dir.h"
#include "gnome-cmd-file-list.h"
#include "gnome-cmd-file-selector.h"
#include "gnome-cmd-main-win.h"
#include "gnome-cmd-user-actions.h"
#include "gnome-cmd-dir-indicator.h"
#include "gnome-cmd-style.h"
#include "plugin_manager.h"
#include "cap.h"
#include "utils.h"
#include "dialogs/gnome-cmd-advrename-dialog.h"
#include "dialogs/gnome-cmd-chmod-dialog.h"
#include "dialogs/gnome-cmd-chown-dialog.h"
#include "dialogs/gnome-cmd-con-dialog.h"
#include "dialogs/gnome-cmd-remote-dialog.h"
#include "dialogs/gnome-cmd-key-shortcuts-dialog.h"
#include "dialogs/gnome-cmd-make-copy-dialog.h"
#include "dialogs/gnome-cmd-manage-bookmarks-dialog.h"
#include "dialogs/gnome-cmd-mkdir-dialog.h"
#include "dialogs/gnome-cmd-search-dialog.h"
#include "dialogs/gnome-cmd-options-dialog.h"
#include "dialogs/gnome-cmd-prepare-copy-dialog.h"
#include "dialogs/gnome-cmd-prepare-move-dialog.h"

using namespace std;

/***********************************
 * Functions for using GSettings
 ***********************************/

struct _GcmdUserActionSettings
{
    GObject parent;
    GSettings *filter;
    GSettings *general;
    GSettings *programs;
};

G_DEFINE_TYPE (GcmdUserActionSettings, gcmd_user_action_settings, G_TYPE_OBJECT)

static void gcmd_user_action_settings_finalize (GObject *object)
{
    G_OBJECT_CLASS (gcmd_user_action_settings_parent_class)->finalize (object);
}

static void gcmd_user_action_settings_dispose (GObject *object)
{
    GcmdUserActionSettings *gs = GCMD_USER_ACTIONS (object);

    g_clear_object (&gs->general);
    g_clear_object (&gs->programs);

    G_OBJECT_CLASS (gcmd_user_action_settings_parent_class)->dispose (object);
}

static void gcmd_user_action_settings_class_init (GcmdUserActionSettingsClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = gcmd_user_action_settings_finalize;
    object_class->dispose = gcmd_user_action_settings_dispose;
}

GcmdUserActionSettings *gcmd_user_action_settings_new ()
{
    return (GcmdUserActionSettings *) g_object_new (USER_ACTION_SETTINGS, nullptr);
}

static void gcmd_user_action_settings_init (GcmdUserActionSettings *gs)
{
    GSettingsSchemaSource   *global_schema_source;
    GSettingsSchema         *global_schema;

    global_schema_source = GnomeCmdData::GetGlobalSchemaSource();

    global_schema = g_settings_schema_source_lookup (global_schema_source, GCMD_PREF_FILTER, FALSE);
    gs->filter = g_settings_new_full (global_schema, nullptr, nullptr);

    global_schema = g_settings_schema_source_lookup (global_schema_source, GCMD_PREF_GENERAL, FALSE);
    gs->general = g_settings_new_full (global_schema, nullptr, nullptr);

    global_schema = g_settings_schema_source_lookup (global_schema_source, GCMD_PREF_PROGRAMS, FALSE);
    gs->programs = g_settings_new_full (global_schema, nullptr, nullptr);
}

/***********************************
 * UserActions
 ***********************************/

GnomeCmdFileSelector *get_fs (const FileSelectorID fsID)
{
    return main_win->fs(fsID);
}

GnomeCmdFileList *get_fl (const FileSelectorID fsID)
{
    GnomeCmdFileSelector *fs = get_fs (fsID);

    return fs ? fs->file_list() : nullptr;
}

// The file returned from this function is not to be unrefed
static GnomeCmdFile *get_selected_file (const FileSelectorID fsID)
{
    GnomeCmdFile *f = get_fl (fsID)->get_first_selected_file();

    if (!f)
        gnome_cmd_show_message (*main_win, _("No file selected"));

    return f;
}


inline gboolean append_real_path (string &s, const gchar *name)
{
    s += ' ';
    s += name;

    return TRUE;
}


static gboolean append_real_path (string &s, GnomeCmdFile *f)
{
    if (!f)
        return FALSE;

    gchar *name = g_shell_quote (f->get_real_path());

    append_real_path (s, name);

    g_free (name);

    return TRUE;
}


int parse_command(string *cmd, gchar * command);

GnomeCmdUserActions gcmd_user_actions;


inline bool operator < (const GdkEventKey &e1, const GdkEventKey &e2)
{
    if (e1.keyval < e2.keyval)
        return true;

    if (e1.keyval > e2.keyval)
        return false;

    return (e1.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK)) < (e2.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK));
}


DICT<GnomeCmdUserActionFunc> GnomeCmdUserActions::action_func;
DICT<GnomeCmdUserActionFunc> GnomeCmdUserActions::action_name;


struct UserActionData
{
    GnomeCmdUserActionFunc func;
    const gchar *name;
    const gchar *description;
};


static UserActionData user_actions_data[] = {
                                             {bookmarks_add_current, "bookmarks.add_current", N_("Bookmark current directory")},
                                             {bookmarks_edit, "bookmarks.edit", N_("Manage bookmarks")},
                                             {bookmarks_goto, "bookmarks.goto", N_("Go to bookmarked location")},
                                             {bookmarks_view, "bookmarks.view", N_("Show bookmarks of current device")},
                                             {command_execute, "command.execute", N_("Execute command")},
                                             {command_open_terminal, "command.open_terminal", N_("Open terminal")},
                                             {command_open_terminal_as_root, "command.open_terminal_as_root", N_("Open terminal as root")},
                                             {command_root_mode, "command.root_mode", N_("Start GNOME Commander as root")},
                                             {connections_close_current, "connections.close", N_("Close connection")},
                                             {connections_new, "connections.new", N_("New connection")},
                                             {connections_open, "connections.open", N_("Open connection")},
                                             {connections_change_left, "connections.change_left", N_("Change left connection")},
                                             {connections_change_right, "connections.change_right", N_("Change right connection")},
                                             {edit_cap_copy, "edit.copy", N_("Copy")},
                                             {edit_copy_fnames, "edit.copy_filenames", N_("Copy file names")},
                                             {edit_cap_cut, "edit.cut", N_("Cut")},
                                             {file_delete, "edit.delete", N_("Delete")},
                                             {edit_filter, "edit.filter", N_("Show user defined files")},
                                             {edit_cap_paste, "edit.paste", N_("Paste")},
                                             {file_quick_search, "file.quick_search", N_("Quick search")},
                                             {file_search, "file.search", N_("Search")},
                                             {file_advrename, "file.advrename", N_("Advanced rename tool")},
                                             {file_chmod, "file.chmod", N_("Change permissions")},
                                             {file_chown, "file.chown", N_("Change owner/group")},
                                             {file_copy, "file.copy", N_("Copy files")},
                                             {file_copy_as, "file.copy_as", N_("Copy files with rename")},
                                             {file_create_symlink, "file.create_symlink", N_("Create symbolic link")},
                                             {file_delete, "file.delete", N_("Delete files")},
                                             {file_diff, "file.diff", N_("Compare files (diff)")},
                                             {file_edit, "file.edit", N_("Edit file")},
                                             {file_edit_new_doc, "file.edit_new_doc", N_("Edit a new file")},
                                             {file_exit, "file.exit", N_("Quit")},
                                             {file_external_view, "file.external_view", N_("View with external viewer")},
                                             {file_internal_view, "file.internal_view", N_("View with internal viewer")},
                                             {file_mkdir, "file.mkdir", N_("Create directory")},
                                             {file_move, "file.move", N_("Move files")},
                                             {file_properties, "file.properties", N_("Properties")},
                                             {file_rename, "file.rename", N_("Rename files")},
                                             // {file_run, "file.run"},
                                             {file_sendto, "file.sendto", N_("Send files")},
                                             {file_sync_dirs, "file.synchronize_directories", N_("Synchronize directories")},
                                             // {file_umount, "file.umount"},
                                             {file_view, "file.view", N_("View file")},
                                             {help_about, "help.about", N_("About GNOME Commander")},
                                             {help_help, "help.help", N_("Help contents")},
                                             {help_keyboard, "help.keyboard", N_("Help on keyboard shortcuts")},
                                             {help_problem, "help.problem", N_("Report a problem")},
                                             {help_web, "help.web", N_("GNOME Commander on the web")},
                                             {mark_compare_directories, "mark.compare_directories", N_("Compare directories")},
                                             {mark_invert_selection, "mark.invert", N_("Invert selection")},
                                             {mark_select_all, "mark.select_all", N_("Select all")},
                                             {mark_select_all_files, "mark.select_all_files", N_("Select all files")},
                                             {mark_unselect_all_files, "mark.unselect_all_files", N_("Unselect all files")},
                                             {mark_toggle, "mark.toggle", N_("Toggle selection")},
                                             {mark_toggle_and_step, "mark.toggle_and_step", N_("Toggle selection and move cursor downward")},
                                             {mark_unselect_all, "mark.unselect_all", N_("Unselect all")},
                                             {no_action, "no.action", N_("Do nothing")},
                                             {options_edit, "options.edit", N_("Options")},
                                             {options_edit_shortcuts, "options.shortcuts", N_("Keyboard shortcuts")},
                                             {plugins_configure, "plugins.configure", N_("Configure plugins")},
                                             {view_back, "view.back", N_("Back one directory")},
                                             {view_close_tab, "view.close_tab", N_("Close the current tab")},
                                             {view_close_all_tabs, "view.close_all_tabs", N_("Close all tabs")},
                                             {view_close_duplicate_tabs, "view.close_duplicate_tabs", N_("Close duplicate tabs")},
                                             {view_directory, "view.directory", N_("Change directory")},
                                             {view_dir_history, "view.dir_history", N_("Show directory history")},
                                             {view_equal_panes, "view.equal_panes", N_("Equal panel size")},
                                             {view_maximize_pane, "view.maximize_pane", N_("Maximize panel size")},
                                             {view_first, "view.first", N_("Back to the first directory")},
                                             {view_forward, "view.forward", N_("Forward one directory")},
                                             {view_home, "view.home", N_("Home directory")},
                                             {view_in_active_pane, "view.in_active_pane", N_("Open directory in the active window")},
                                             {view_in_inactive_pane, "view.in_inactive_pane", N_("Open directory in the inactive window")},
                                             {view_in_left_pane, "view.in_left_pane", N_("Open directory in the left window")},
                                             {view_in_right_pane, "view.in_right_pane", N_("Open directory in the right window")},
                                             {view_in_new_tab, "view.in_new_tab", N_("Open directory in the new tab")},
                                             {view_in_inactive_tab, "view.in_inactive_tab", N_("Open directory in the new tab (inactive window)")},
                                             {view_last, "view.last", N_("Forward to the last directory")},
                                             {view_next_tab, "view.next_tab", N_("Next tab")},
                                             {view_new_tab, "view.new_tab", N_("Open directory in a new tab")},
                                             {view_prev_tab, "view.prev_tab", N_("Previous tab")},
                                             {view_refresh, "view.refresh", N_("Refresh")},
                                             {view_root, "view.root", N_("Root directory")},
                                             {view_toggle_tab_lock, "view.toggle_lock_tab", N_("Lock/unlock tab")},
#if 0
                                             {view_terminal, "view.terminal", N_("Show terminal")},
#endif
                                             {view_up, "view.up", N_("Up one directory")},
                                             {view_main_menu, "view.main_menu", N_("Display main menu")},
                                             {view_step_up, "view.step_up", N_("Move cursor one step up")},
                                             {view_step_down, "view.step_down", N_("Move cursor one step down")},
                                            };


void GnomeCmdUserActions::init()
{
    for (guint i=0; i<G_N_ELEMENTS(user_actions_data); ++i)
    {
        action_func.add(user_actions_data[i].func, user_actions_data[i].name);
        action_name.add(user_actions_data[i].func, _(user_actions_data[i].description));
    }

    register_action(GDK_F3, "file.view");
    register_action(GDK_F4, "file.edit");
    register_action(GDK_F5, "file.copy");
    register_action(GDK_F6, "file.rename");
    register_action(GDK_F7, "file.mkdir");
    register_action(GDK_F8, "file.delete");
    // register_action(GDK_F9, "edit.search");     //  do not register F9 here, as edit.search action wouldn't be checked for registration later
    settings = gcmd_user_action_settings_new();
}


 void GnomeCmdUserActions::set_defaults()
 {
    if (!registered("view.main_menu"))
        register_action(GDK_F12, "view.main_menu");

   if (!registered("bookmarks.edit"))
        register_action(GDK_CONTROL_MASK, GDK_D, "bookmarks.edit");

    if (!registered("connections.new"))
        register_action(GDK_CONTROL_MASK, GDK_N, "connections.new");

    if (!registered("connections.open"))
        register_action(GDK_CONTROL_MASK, GDK_F, "connections.open");

    if (!registered("connections.close"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_F, "connections.close");

    if (!registered("connections.change_left"))
    {
        register_action(GDK_MOD1_MASK, GDK_1, "connections.change_left");
        register_action(GDK_SUPER_MASK, GDK_1, "connections.change_left");
    }

    if (!registered("connections.change_right"))
    {
        register_action(GDK_MOD1_MASK, GDK_2, "connections.change_right");
        register_action(GDK_SUPER_MASK, GDK_2, "connections.change_right");
    }

    if (!registered("edit.copy_filenames"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_C, "edit.copy_filenames");

    if (!registered("edit.filter"))
        register_action(GDK_CONTROL_MASK, GDK_F12, "edit.filter");

    if (!registered("edit.search"))
    {
        register_action(GDK_MOD1_MASK, GDK_F7, "edit.search");
        register_action(GDK_SUPER_MASK, GDK_F, "edit.search");
    }

    if (!registered("file.advrename"))
        register_action(GDK_CONTROL_MASK, GDK_M, "file.advrename");

    if (!registered("file.copy_as"))
        register_action(GDK_SHIFT_MASK, GDK_F5, "file.copy_as");

    if (!registered("file.create_symlink"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_F5, "file.create_symlink");

    if (!registered("file.edit_new_doc"))
        register_action(GDK_SHIFT_MASK, GDK_F4, "file.edit_new_doc");

    if (!registered("file.exit"))
        register_action(GDK_CONTROL_MASK, GDK_Q, "file.exit");

    if (!registered("file.external_view"))
        register_action(GDK_MOD1_MASK, GDK_F3, "file.external_view");

    if (!registered("file.internal_view"))
        register_action(GDK_SHIFT_MASK, GDK_F3, "file.internal_view");

    if (!registered("mark.compare_directories"))
        register_action(GDK_SHIFT_MASK, GDK_F2, "mark.compare_directories");

    if (!registered("mark.select_all"))
    {
        register_action(GDK_CONTROL_MASK, GDK_A, "mark.select_all");
        register_action(GDK_CONTROL_MASK, GDK_equal, "mark.select_all");
        register_action(GDK_CONTROL_MASK, GDK_KP_Add, "mark.select_all");
    }

    if (!registered("mark.unselect_all"))
    {
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_A, "mark.unselect_all");
        register_action(GDK_CONTROL_MASK, GDK_minus, "mark.unselect_all");
        register_action(GDK_CONTROL_MASK, GDK_KP_Subtract, "mark.unselect_all");
    }

    if (!registered("options.edit"))
        register_action(GDK_CONTROL_MASK, GDK_O, "options.edit");

    if (!registered("dir_history"))
    {
        register_action(GDK_MOD1_MASK, GDK_Down, "view.dir_history");
        register_action(GDK_MOD1_MASK, GDK_KP_Down, "view.dir_history");
    }

    if (!registered("view.up"))
    {
        register_action(GDK_CONTROL_MASK, GDK_Page_Up, "view.up");
        register_action(GDK_CONTROL_MASK, GDK_KP_Page_Up, "view.up");
    }

    if (!registered("view.equal_panes"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_plus, "view.equal_panes");

    if (!registered("view.in_active_pane"))
        register_action(GDK_CONTROL_MASK, GDK_period, "view.in_active_pane");

    if (!registered("view.in_inactive_pane"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_greater, "view.in_inactive_pane");

    if (!registered("view.in_left_pane"))
    {
        register_action(GDK_CONTROL_MASK, GDK_Left, "view.in_left_pane");
        register_action(GDK_CONTROL_MASK, GDK_KP_Left, "view.in_left_pane");
    }

    if (!registered("view.in_right_pane"))
    {
        register_action(GDK_CONTROL_MASK, GDK_Right, "view.in_right_pane");
        register_action(GDK_CONTROL_MASK, GDK_KP_Right, "view.in_right_pane");
    }

    if (!registered("view.in_new_tab"))
    {
        register_action(GDK_CONTROL_MASK, GDK_Up, "view.in_new_tab");
        register_action(GDK_CONTROL_MASK, GDK_KP_Up, "view.in_new_tab");
    }

    if (!registered("view.in_inactive_tab"))
    {
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_Up, "view.in_inactive_tab");
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_KP_Up, "view.in_inactive_tab");
    }

    if (!registered("view.directory"))
    {
        register_action(GDK_CONTROL_MASK, GDK_Page_Down, "view.directory");
        register_action(GDK_CONTROL_MASK, GDK_KP_Page_Down, "view.directory");
    }

    if (!registered("view.home"))
    {
        register_action(GDK_CONTROL_MASK, GDK_quoteleft, "view.home");
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_asciitilde, "view.home");
    }

    if (!registered("view.root"))
        register_action(GDK_CONTROL_MASK, GDK_backslash, "view.root");

    if (!registered("view.refresh"))
        register_action(GDK_CONTROL_MASK, GDK_R, "view.refresh");

    if (!registered("view.new_tab"))
    {
        unregister(GDK_CONTROL_MASK, GDK_T);                       // unregister CTRL+T as it was used previously for file.advrename
        register_action(GDK_CONTROL_MASK, GDK_T, "view.new_tab");
    }

    if (!registered("view.close_tab"))
        register_action(GDK_CONTROL_MASK, GDK_W, "view.close_tab");

    if (!registered("view.close_all_tabs"))
        register_action(GDK_CONTROL_MASK | GDK_SHIFT_MASK, GDK_W, "view.close_all_tabs");

    unregister(GDK_F9);                                 // unregister F9 if defined in [key-bindings]
    register_action(GDK_F9, "edit.search");             // and overwrite it with edit.search action
 }


void GnomeCmdUserActions::shutdown()
{
    // unregister Fn to avoid writing 'hardcoded' Fn actions to config file

    unregister(GDK_F3);
    unregister(GDK_F4);
    unregister(GDK_F5);
    unregister(GDK_F6);
    unregister(GDK_F7);
    unregister(GDK_F8);
    unregister(GDK_F9);
}


gboolean GnomeCmdUserActions::register_action(guint state, guint keyval, const gchar *the_action_name, const char *user_data)
{
    GnomeCmdUserActionFunc func = action_func[the_action_name];

    if (!func)
        return FALSE;

    GdkEventKey event;

    event.keyval = keyval;
    event.state = state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK);
    if (action.find(event)!=action.end())
        return FALSE;

    if (!ascii_isalpha (keyval))
        action[event] = UserAction(func, user_data);
    else
    {
        event.keyval = g_ascii_toupper (keyval);
        action[event] = UserAction(func, user_data);
        event.keyval = g_ascii_tolower (keyval);
        action[event] = UserAction(func, user_data);
    }

    return TRUE;
}


void GnomeCmdUserActions::unregister(const gchar *the_action_name)
{
}


void GnomeCmdUserActions::unregister(guint state, guint keyval)
{
    GdkEventKey event;

    event.keyval = keyval;
    event.state = state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK);
    map <GdkEventKey, UserAction>::iterator pos = action.find(event);

    if (pos!=action.end())
        action.erase(pos);
}


gboolean GnomeCmdUserActions::registered(const gchar *the_action_name)
{
    GnomeCmdUserActionFunc func = action_func[the_action_name];

    if (!func)
        return FALSE;

    for (ACTIONS_COLL::const_iterator i=action.begin(); i!=action.end(); ++i)
        if (i->second.func==func)
            return TRUE;

    return FALSE;
}


gboolean GnomeCmdUserActions::registered(guint state, guint keyval)
{
    GdkEventKey event;

    event.keyval = keyval;
    event.state = state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK);

    return action.find(event)!=action.end();
}


gboolean GnomeCmdUserActions::handle_key_event(GnomeCmdMainWin *mw, GnomeCmdFileList *fl, GdkEventKey *event)
{
    map <GdkEventKey, UserAction>::const_iterator pos = action.find(*event);

    if (pos==action.end())
        return FALSE;

    DEBUG('u', "Key event:  %s (%#x)\n", key2str(*event).c_str(), event->keyval);
    DEBUG('u', "Handling key event by %s()\n", action_func[pos->second.func].c_str());

    (*pos->second.func) (nullptr, (gpointer) (pos->second.user_data.empty() ? nullptr : pos->second.user_data.c_str()));

    return TRUE;
}


static int sort_by_description (const void *data1, const void *data2)
{
    const gchar *s1 = (static_cast<const UserActionData*> (data1))->description;
    const gchar *s2 = (static_cast<const UserActionData*> (data2))->description;

    if (!s1 && !s2)
        return 0;

    if (!s1)
        return 1;

    if (!s2)
        return -1;

    // compare s1 and s2 in UTF8 aware way, case insensitive
    gchar *is1 = g_utf8_casefold (_(s1), -1);
    gchar *is2 = g_utf8_casefold (_(s2), -1);

    gint retval = g_utf8_collate (is1, is2);

    g_free (is1);
    g_free (is2);

    return retval;
}


GtkTreeModel *gnome_cmd_user_actions_create_model ()
{
    auto data = static_cast<UserActionData*> (g_memdup (user_actions_data, sizeof(user_actions_data)));

    qsort (data, G_N_ELEMENTS(user_actions_data), sizeof(UserActionData), sort_by_description);

    GtkListStore *model = gtk_list_store_new (3, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_STRING);
    GtkTreeIter iter;

    for (guint i=0; i<G_N_ELEMENTS(user_actions_data); ++i)
    {
        gtk_list_store_append (model, &iter);

        gtk_list_store_set (model, &iter,
                                   0, data[i].func,
                                   1, data[i].name,
                                   2, _(data[i].description),
                                   -1);
    }

    g_free (data);

    return GTK_TREE_MODEL (model);
}


template <typename F>
static void get_file_list (string &s, GList *sfl, F f)
{
    vector<string> a;

    for (GList *i = sfl; i; i = i->next)
    {
        auto value = (*f) (GNOME_CMD_FILE (i->data));

        if (value)
            a.push_back (value);
    }
    join (s, a.begin(), a.end());
}


template <typename F, typename T>
inline void get_file_list (string &s, GList *sfl, F f, T t)
{
    vector<string> a;

    for (GList *i = sfl; i; i = i->next)
    {
        auto value = (*f) (GNOME_CMD_FILE (i->data), t);

        if (value)
            a.push_back (value);
    }

    join (s, a.begin(), a.end());
}


/***************************************/
void no_action (GtkMenuItem *menuitem, gpointer not_used)
{
}


/************** File Menu **************/
void file_copy (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *src_fs = get_fs (ACTIVE);
    GnomeCmdFileSelector *dest_fs = get_fs (INACTIVE);

    if (src_fs && dest_fs)
        gnome_cmd_prepare_copy_dialog_show (src_fs, dest_fs);
}


void file_copy_as (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFile *f = fs->file_list()->get_selected_file();

    if (GNOME_CMD_IS_FILE (f))
    {
        GtkWidget *dialog = gnome_cmd_make_copy_dialog_new (f, fs->get_directory());

        g_object_ref (dialog);
        gtk_widget_show (dialog);
    }
}


void file_move (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *src_fs = get_fs (ACTIVE);
    GnomeCmdFileSelector *dest_fs = get_fs (INACTIVE);

    if (src_fs && dest_fs)
        gnome_cmd_prepare_move_dialog_show (src_fs, dest_fs);
}


void file_delete (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_delete_dialog (get_fl (ACTIVE));
}


void file_view (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_view (get_fl (ACTIVE), -1);
}


void file_internal_view (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_view (get_fl (ACTIVE), TRUE);
}


void file_external_view (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_view (get_fl (ACTIVE), FALSE);
}

void file_edit (GtkMenuItem *menuitem, gpointer not_used)
{
    GdkModifierType mask;

    gdk_window_get_pointer (nullptr, nullptr, nullptr, &mask);

    if (mask & GDK_SHIFT_MASK)
        gnome_cmd_file_selector_show_new_textfile_dialog (get_fs (ACTIVE));
    else
    {
	    gchar       *command;

	    command = g_strdup (gnome_cmd_data.options.editor);
	    g_return_if_fail (command != nullptr);

	    string cmd;

	    cmd.reserve(2000);
	    if (parse_command(&cmd, (const gchar*) command) == 0)
	    {
	        DEBUG ('g', "Edit file command is not valid.\n");
	        gnome_cmd_show_message (*main_win, _("No valid command given."));
	        return;
	    }
	    else
	    {
	        gint     argc;
	        gchar  **argv  = nullptr;
	        GError  *error = nullptr;
	        DEBUG ('g', "Edit file: %s\n", cmd.c_str());

	        g_shell_parse_argv (cmd.c_str(), &argc, &argv, nullptr);
	        if (!g_spawn_async (nullptr, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error))
            gnome_cmd_error_message (_("Unable to execute command."), error);

	        g_strfreev (argv);
	        g_free (command);
	    }
    }
}

void file_edit_new_doc (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_selector_show_new_textfile_dialog (get_fs (ACTIVE));
}


void file_search (GtkMenuItem *menuitem, gpointer not_used)
{
    if (gnome_cmd_data.options.use_internal_search)
    {
        if (!main_win->file_search_dlg)
            main_win->file_search_dlg = new GnomeCmdSearchDialog(gnome_cmd_data.search_defaults);

        main_win->file_search_dlg->show_and_set_focus();
    }
    else
    {
        string commandString;
        commandString.reserve(2000);

        if (parse_command(&commandString, (const gchar*) gnome_cmd_data.options.search) == 0)
        {
            DEBUG ('g', "Search command is empty.\n");
            gnome_cmd_show_message (*main_win, _("No search command given."), _("You can set a command for a search tool in the program options."));
            return;
        }
        else
        {
            gint     argc;
            gchar  **argv  = nullptr;
            GError  *error = nullptr;

            DEBUG ('g', "Invoking search: %s\n", commandString.c_str());
            // put command into argv
            g_shell_parse_argv (commandString.c_str(), &argc, &argv, nullptr);
            // execute command
            if (!g_spawn_async (nullptr, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error))
            {
                gnome_cmd_error_message (_("Unable to execute command."), error);
            }
            g_strfreev (argv);
        }
    }
}


void file_quick_search (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_quicksearch (get_fl (ACTIVE), 0);
}


void file_chmod (GtkMenuItem *menuitem, gpointer not_used)
{
    GList *files = get_fl (ACTIVE)->get_selected_files();

    if (files)
    {
        GtkWidget *dialog = gnome_cmd_chmod_dialog_new (files);

        g_object_ref (dialog);
        gtk_widget_show (dialog);
        g_list_free (files);
    }
}


void file_chown (GtkMenuItem *menuitem, gpointer not_used)
{
    GList *files = get_fl (ACTIVE)->get_selected_files();

    if (files)
    {
        GtkWidget *dialog = gnome_cmd_chown_dialog_new (files);

        g_object_ref (dialog);
        gtk_widget_show (dialog);
        g_list_free (files);
    }
}


void file_mkdir (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdDir *dir = fs->get_directory();

    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    gnome_cmd_dir_ref (dir);
    gnome_cmd_mkdir_dialog_new (dir, fs->file_list()->get_selected_file());
    gnome_cmd_dir_unref (dir);
}


void file_create_symlink (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *inactive_fs = get_fs (INACTIVE);
    GList *f = get_fl (ACTIVE)->get_selected_files();
    guint selected_files = g_list_length (f);

    if (selected_files > 1)
    {
        gchar *msg = g_strdup_printf (ngettext("Create symbolic links of %i file in %s?",
                                               "Create symbolic links of %i files in %s?",
                                               selected_files),
                                      selected_files, gnome_cmd_dir_get_display_path (inactive_fs->get_directory()));

        gint choice = run_simple_dialog (*main_win, TRUE, GTK_MESSAGE_QUESTION, msg, _("Create Symbolic Link"), 1, _("Cancel"), _("Create"), nullptr);

        g_free (msg);

        if (choice==1)
            gnome_cmd_file_selector_create_symlinks (inactive_fs, f);
    }
   else
   {
        GnomeCmdFile *focused_f = get_fl (ACTIVE)->get_focused_file();
        gnome_cmd_file_selector_create_symlink (inactive_fs, focused_f);
   }
}


void file_rename (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_rename_dialog (get_fl (ACTIVE));
}


void file_advrename (GtkMenuItem *menuitem, gpointer not_used)
{
    GList *files = get_fl (ACTIVE)->get_selected_files();

    if (files)
    {
        files = get_fl (ACTIVE)->sort_selection(files);

        if (!main_win->advrename_dlg)
        {
            main_win->advrename_dlg = new GnomeCmdAdvrenameDialog(gnome_cmd_data.advrename_defaults);
            // g_object_ref (main_win->advrename_dlg);      //  FIXME:  ???
            main_win->advrename_dlg->set(files);
            gtk_widget_show_all (*main_win->advrename_dlg);
        }
        else
        {
            main_win->advrename_dlg->set(files);
            gtk_widget_show (*main_win->advrename_dlg);
            // gdk_window_raise (gtk_widget_get_window (GTK_WIDGET (main_win->advrename_dlg)));     //  FIXME:  bring dlg to top ???
        }

        g_list_free (files);
    }
}


void file_sendto (GtkMenuItem *menuitem, gpointer not_used)
{
    string commandString;
    commandString.reserve(2000);

    if (parse_command(&commandString, (const gchar*) gnome_cmd_data.options.sendto) == 0)
    {
	    DEBUG ('g', "Sendto command is not valid.\n");
	    gnome_cmd_show_message (*main_win, _("No valid command given."));
	    return;
    }
    else
    {
        gint     argc;
        gchar  **argv  = nullptr;
        GError  *error = nullptr;

        eventually_warn_if_xdg_email_is_used();

        DEBUG ('g', "Invoking 'Send files': %s\n", commandString.c_str());
        g_shell_parse_argv (commandString.c_str(), &argc, &argv, nullptr);

        if (!g_spawn_async (nullptr, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error))
        {
            gnome_cmd_error_message (_("Unable to execute command."), error);
        }
        g_strfreev (argv);
    }
}


void eventually_warn_if_xdg_email_is_used()
{
    auto fileList = get_fl (ACTIVE);
    GList *selectedFileList = fileList->get_selected_files();
    auto currentSendToString = g_settings_get_string (gcmd_user_actions.settings->programs, GCMD_SETTINGS_SENDTO_CMD);

    if ((g_strcmp0(currentSendToString, "xdg-email --attach %s") == 0)
        && g_list_length(selectedFileList) > 1)
    {
        gnome_cmd_show_message (*main_win, _("Warning"), _("The default send-to command only supports one selected file at a time. You can change the command in the program options."));
    }
    g_list_free(selectedFileList);
    g_free(currentSendToString);
}


void file_properties (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_properties_dialog (get_fl (ACTIVE));
}


void file_diff (GtkMenuItem *menuitem, gpointer not_used)
{
    if (!get_fs (ACTIVE)->is_local())
    {
        gnome_cmd_show_message (*main_win, _("Operation not supported on remote file systems"));
        return;
    }

    GnomeCmdFileList *active_fl = get_fl (ACTIVE);

    GList *sel_files = active_fl->get_selected_files();

    string files_to_differ;

    switch (g_list_length (sel_files))
    {
        case 0:
            return;

        case 1:
            if (!get_fs (INACTIVE)->is_local())
                gnome_cmd_show_message (*main_win, _("Operation not supported on remote file systems"));
            else
                if (!append_real_path (files_to_differ, get_selected_file (ACTIVE)) || !append_real_path (files_to_differ, get_selected_file (INACTIVE)))
                    files_to_differ.clear();
            break;

        case 2:
        case 3:
            sel_files = active_fl->sort_selection(sel_files);

            for (GList *i = sel_files; i; i = i->next)
                append_real_path (files_to_differ, GNOME_CMD_FILE (i->data));
            break;

        default:
            gnome_cmd_show_message (*main_win, _("Too many selected files"));
            break;
    }

    g_list_free (sel_files);

    if (!files_to_differ.empty())
    {
#if defined (__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
        gchar *cmd = g_strdup_printf (gnome_cmd_data.options.differ, files_to_differ.c_str(), "");
#if defined (__GNUC__)
#pragma GCC diagnostic pop
#endif

        run_command (cmd);

        g_free (cmd);
    }
}


void file_sync_dirs (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *active_fs = get_fs (ACTIVE);
    GnomeCmdFileSelector *inactive_fs = get_fs (INACTIVE);

    if (!active_fs->is_local() || !inactive_fs->is_local())
    {
        gnome_cmd_show_message (*main_win, _("Operation not supported on remote file systems"));
        return;
    }

    string s;

    append_real_path (s, GNOME_CMD_FILE (active_fs->get_directory()));
    append_real_path (s, GNOME_CMD_FILE (inactive_fs->get_directory()));

#if defined (__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
    gchar *cmd = g_strdup_printf (gnome_cmd_data.options.differ, s.c_str(), "");
#if defined (__GNUC__)
#pragma GCC diagnostic pop
#endif

    run_command (cmd);

    g_free (cmd);
}


void file_exit (GtkMenuItem *menuitem, gpointer not_used)
{
    gint x, y;

#if defined (__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
#endif
    switch (gnome_cmd_data.main_win_state)
    {
        case GDK_WINDOW_STATE_MAXIMIZED:
        case GDK_WINDOW_STATE_FULLSCREEN:
        case GDK_WINDOW_STATE_ICONIFIED:
            break;

        default:
            gdk_window_get_root_origin (gtk_widget_get_window (GTK_WIDGET (main_win)), &x, &y);
            gnome_cmd_data_set_main_win_pos (x, y);
            break;
    }
#if defined (__GNUC__)
#pragma GCC diagnostic pop
#endif

    gtk_widget_destroy (*main_win);
}


/************** Edit Menu **************/
void edit_cap_cut (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_cap_cut (get_fl (ACTIVE));
}


void edit_cap_copy (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_cap_copy (get_fl (ACTIVE));
}


void edit_cap_paste (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_selector_cap_paste (get_fs (ACTIVE));
}


void edit_filter (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->show_filter();
}


void edit_copy_fnames (GtkMenuItem *menuitem, gpointer fileList)
{
    GdkModifierType mask;
    GnomeCmdFileList *fl;

    gdk_window_get_pointer (nullptr, nullptr, nullptr, &mask);

    if (!GNOME_CMD_IS_FILE_LIST(fileList) || (GnomeCmdFileList*) fileList == nullptr)
    {
        fileList = get_fl (ACTIVE);
    }
    fl = (GnomeCmdFileList*) fileList;
    GList *sfl = fl->get_selected_files();
    sfl = fl->sort_selection(sfl);

    string fnames;

    fnames.reserve(2000);

    if (state_is_blank (mask))
        get_file_list (fnames, sfl, gnome_cmd_file_get_name);
    else
        if (state_is_shift (mask))
            get_file_list (fnames, sfl, gnome_cmd_file_get_real_path);
        else
            if (state_is_alt (mask))
                get_file_list (fnames, sfl, gnome_cmd_file_get_uri_str);

    gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), fnames.c_str(), -1);

    g_list_free (sfl);
}


/************** Command Menu **************/
void command_execute (GtkMenuItem *menuitem, gpointer command)
{
    g_return_if_fail (command != nullptr);

    DEBUG ('g', "invoking: %s\n", command);

    string dir_path;
    string quoted_dir_path;
    string cmd;

    GnomeCmdDir *dir = nullptr;

    GnomeCmdFileList *fl = get_fl (ACTIVE);
    GList *sfl = fl->get_selected_files();
    GList *i = sfl = fl->sort_selection(sfl);

    if (i)
    {
        dir = GNOME_CMD_FILE (i->data)->get_parent_dir();
        i = i->next;
    }

    for (; i && GNOME_CMD_FILE (i->data)->get_parent_dir()==dir; i=i->next);

    if (i)
        dir = nullptr;

    if (dir)
    {
        stringify (dir_path, GNOME_CMD_FILE (dir)->get_real_path());
        stringify (quoted_dir_path, gnome_cmd_file_get_quoted_real_path (GNOME_CMD_FILE (dir)));
    }

    cmd.reserve(2000);
    if (parse_command(&cmd, (const gchar*) command) == 0)
    {
	DEBUG ('g', "Command is not valid.\n");
	gnome_cmd_show_message (*main_win, _("No valid command given."));
	return;
    }
    else
    {
	gint argc;
	gchar **argv;
	GError *error = nullptr;

	DEBUG ('g', "Running: %s\n", cmd.c_str());

	g_shell_parse_argv (cmd.c_str(), &argc, &argv, nullptr);
	if (!g_spawn_async (gnome_cmd_dir_is_local (dir) ? dir_path.c_str() : nullptr, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error))
	    gnome_cmd_error_message (_("Unable to execute command."), error);

	g_strfreev (argv);
	g_list_free (sfl);
    }
}


void command_open_terminal__internal (GtkMenuItem *menuitem, gpointer not_used)           // this function is NOT exposed to user as UserAction
{
    GdkModifierType mask;

    gdk_window_get_pointer (nullptr, nullptr, nullptr, &mask);

    if (mask & GDK_SHIFT_MASK)
        command_open_terminal_as_root (menuitem, nullptr);
    else
        command_open_terminal (menuitem, nullptr);
}

/**
 * Executes the command stored in gnome_cmd_data.options.termopen in the
 * active directory.
 */
void command_open_terminal (GtkMenuItem *menuitem, gpointer not_used)
{
    gint argc;
    gchar **argv;
    gchar *command;
    gchar *dpath = GNOME_CMD_FILE (get_fs (ACTIVE)->get_directory())->get_real_path();
    GError *error = nullptr;

    command = g_strdup (gnome_cmd_data.options.termopen);
    g_return_if_fail (command[0] != '\0');

    DEBUG ('g', "running: %s\n", command);

    g_shell_parse_argv (command, &argc, &argv, nullptr);
    if (!g_spawn_async (dpath, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error))
        gnome_cmd_error_message (_("Unable to execute command."), error);

    g_strfreev (argv);
    g_free (command);
    g_free (dpath);
}

/**
 * Combines the command stored in gnome_cmd_data.options.termopen with a
 * command for a GUI for root login screen. The command is executed in
 * the active directory afterwards.
 */
void command_open_terminal_as_root (GtkMenuItem *menuitem, gpointer not_used)
{

    int argc = 1;
    char **argv = g_new0 (char *, argc+1);  //initialize argv
    gchar *command;

    command = g_strdup (gnome_cmd_data.options.termopen);
    // Parse the termopen string to get the number of arguments in argc
    g_shell_parse_argv (command, &argc, &argv, nullptr);
    g_free (argv);

    argv = g_new0 (char *, argc+1);
    argv[0] = command;

    if (gnome_cmd_prepend_su_to_vector (argc, argv))
    {
        gchar *dpath = GNOME_CMD_FILE (get_fs (ACTIVE)->get_directory())->get_real_path();
        GError *error = nullptr;

        if (!g_spawn_async (dpath, argv, nullptr, G_SPAWN_STDOUT_TO_DEV_NULL, nullptr, nullptr, nullptr, &error))
            gnome_cmd_error_message (_("Unable to open terminal in root mode."), error);

        g_free (dpath);
    }
    else
        gnome_cmd_show_message (nullptr, _("gksudo, xdg-su, gksu, gnomesu, kdesu or beesu is not found."));

    g_free (argv);
    g_free (command);
}


void command_root_mode (GtkMenuItem *menuitem, gpointer not_used)
{
    int argc = 1;
    char **argv = g_new0 (char *, argc+1);

    argv[0] = g_strdup (g_get_prgname ());

    if (gnome_cmd_prepend_su_to_vector (argc, argv))
    {
        GError *error = nullptr;

        if (!g_spawn_async (nullptr, argv, nullptr, G_SPAWN_STDOUT_TO_DEV_NULL, nullptr, nullptr, nullptr, &error))
            gnome_cmd_error_message (_("Unable to start GNOME Commander in root mode."), error);
    }
    else
        gnome_cmd_show_message (nullptr, _("gksudo, xdg-su, gksu, gnomesu, kdesu or beesu is not found"));

    g_strfreev (argv);
}


/************** Mark Menu **************/
void mark_toggle (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->toggle();
}


void mark_toggle_and_step (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->toggle_and_step();
}


void mark_select_all (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->select_all();
}


void mark_select_all_files (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->select_all_files();
}


void mark_unselect_all_files (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->unselect_all_files();
}


void mark_unselect_all (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->unselect_all();
}


void mark_select_with_pattern (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_selpat_dialog (get_fl (ACTIVE), TRUE);
}


void mark_unselect_with_pattern (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_file_list_show_selpat_dialog (get_fl (ACTIVE), FALSE);
}


void mark_invert_selection (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->invert_selection();
}


void mark_select_all_with_same_extension (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->select_all_with_same_extension();
}


void mark_unselect_all_with_same_extension (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->unselect_all_with_same_extension();
}


void mark_restore_selection (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fl (ACTIVE)->restore_selection();
}


struct ltstr
{
    bool operator() (const char *s1, const char *s2) const   {  return strcmp(s1,s2) < 0;  }
};


template <typename T>
struct select2nd
{
    typename T::second_type operator()(T const& value) const    {  return value.second;  }
};


template <typename T>
inline select2nd<typename T::value_type> make_select2nd(T const& m)
{
    return select2nd<typename T::value_type>();
}


inline void selection_delta(GnomeCmdFileList &fl, set<GnomeCmdFile *> &prev_selection, set<GnomeCmdFile *> &new_selection)
{
    vector<GnomeCmdFile *> a;

    a.reserve(max(prev_selection.size(),new_selection.size()));

    set_difference(prev_selection.begin(),prev_selection.end(),
                   new_selection.begin(),new_selection.end(),
                   back_inserter(a));

    for (vector<GnomeCmdFile *>::iterator i=a.begin(); i!=a.end(); ++i)
        fl.unselect_file(*i);

    a.clear();

    set_difference(new_selection.begin(),new_selection.end(),
                   prev_selection.begin(),prev_selection.end(),
                   back_inserter(a));

    for (vector<GnomeCmdFile *>::iterator i=a.begin(); i!=a.end(); ++i)
        fl.select_file(*i);
}


void mark_compare_directories (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileList *fl1 = get_fl (ACTIVE);
    GnomeCmdFileList *fl2 = get_fl (INACTIVE);

    map<const char *,GnomeCmdFile *,ltstr> files2;          //  map (fname -> GnomeCmdFile *) of visible files (non dirs!) in fl2

    for (GList *i2=fl2->get_visible_files(); i2; i2=i2->next)
    {
        auto f2 = static_cast<GnomeCmdFile*> (i2->data);

        if (!f2->is_dotdot && f2->GetGfileAttributeUInt32(G_FILE_ATTRIBUTE_STANDARD_TYPE) != G_FILE_TYPE_DIRECTORY)
            files2[f2->get_name()] = f2;
    }

    set<GnomeCmdFile *> new_selection1, new_selection2;

    for (GList *i1=fl1->get_visible_files(); i1; i1=i1->next)
    {
        auto f1 = static_cast<GnomeCmdFile*> (i1->data);

        if (f1->is_dotdot || f1->GetGfileAttributeUInt32(G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
            continue;

        map<const char *,GnomeCmdFile *,ltstr>::iterator i2 = files2.find(f1->get_name());

        if (i2==files2.end())                           //  if f1 is not found in visible fl2 files...
        {
            new_selection1.insert(f1);                  //  ... add f1 to files to be selected in fl1
            continue;
        }

        GnomeCmdFile *f2 = i2->second;

        if (f1->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_TIME_MODIFIED)
            > f2->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_TIME_MODIFIED)) //  if f1 is newer than f2...
        {
            new_selection1.insert(f1);                  //  ... add f1 to files to be selected in fl1...
            files2.erase(i2);                           //  ... and remove f2 from fl2 files
            continue;
        }

        if (f1->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_TIME_MODIFIED)
            == f2->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_TIME_MODIFIED)) //  if the same mtime for f1, f2
        {
            if (f1->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_STANDARD_SIZE)
                == f2->GetGfileAttributeUInt64(G_FILE_ATTRIBUTE_STANDARD_SIZE)) //  ... then check f1, f2 sizes
                files2.erase(i2);                       //  ... and remove f2 from fl2 files
            else
                new_selection1.insert(f1);              //  ... or add f1 to files to be selected in fl1
        }
    }

    transform (files2.begin(), files2.end(), inserter(new_selection2,new_selection2.begin()), make_select2nd(files2));    // copy left files2 --> new_selection2

    selection_delta (*fl1, fl1->get_marked_files(), new_selection1);
    selection_delta (*fl2, fl2->get_marked_files(), new_selection2);
}

/* ***************************** View Menu ****************************** */
/* Changing of GSettings here will trigger functions in gnome-cmd-data.cc */
/* ********************************************************************** */

void view_conbuttons (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_SHOW_DEVBUTTONS, active);
}


void view_devlist (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_SHOW_DEVLIST, active);
}


void view_toolbar (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_SHOW_TOOLBAR, active);
}


void view_buttonbar (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_SHOW_BUTTONBAR, active);
}


void view_cmdline (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_SHOW_CMDLINE, active);
}


void view_dir_history (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_dir_indicator_show_history (GNOME_CMD_DIR_INDICATOR (get_fs (ACTIVE)->dir_indicator));
}


void view_hidden_files (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->filter, GCMD_SETTINGS_FILTER_HIDE_HIDDEN, !active);
}


void view_backup_files (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->filter, GCMD_SETTINGS_FILTER_HIDE_BACKUPS, !active);
}


void view_horizontal_orientation (GtkToggleAction *toggleAction, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    auto active = gtk_toggle_action_get_active(toggleAction);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_HORIZONTAL_ORIENTATION, active);
}

void view_step_up (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    g_signal_emit_by_name (fl, "scroll-vertical", GTK_SCROLL_STEP_BACKWARD, 0.0, nullptr);
}

void view_step_down (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    g_signal_emit_by_name (fl, "scroll-vertical", GTK_SCROLL_STEP_FORWARD, 0.0, nullptr);
}

void view_up (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    if (fl->locked)
        fs->new_tab(gnome_cmd_dir_get_parent (fl->cwd));
    else
        fs->goto_directory("..");
}

void view_main_menu (GtkMenuItem *menuitem, gpointer not_used)
{
    if (!gtk_widget_get_realized ( GTK_WIDGET (main_win))) return;

    gboolean mainmenu_visibility;
    mainmenu_visibility = g_settings_get_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_MAINMENU_VISIBILITY);
    g_settings_set_boolean (gcmd_user_actions.settings->general, GCMD_SETTINGS_MAINMENU_VISIBILITY, !mainmenu_visibility);
}

void view_first (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->first();
}


void view_back (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->back();
}


void view_forward (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->forward();
}


void view_last (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->last();
}


void view_refresh (GtkMenuItem *menuitem, gpointer file_list)
{
    GnomeCmdFileList *fl = GNOME_CMD_IS_FILE_LIST(file_list)
        ? GNOME_CMD_FILE_LIST (file_list)
        : get_fl (ACTIVE);
    fl->reload();
}


void view_equal_panes (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->set_equal_panes();
}


void view_maximize_pane (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->maximize_pane();
}


void view_in_left_pane (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->set_fs_directory_to_opposite(LEFT);
}


void view_in_right_pane (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->set_fs_directory_to_opposite(RIGHT);
}


void view_in_active_pane (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->set_fs_directory_to_opposite(ACTIVE);
}


void view_in_inactive_pane (GtkMenuItem *menuitem, gpointer not_used)
{
    main_win->set_fs_directory_to_opposite(INACTIVE);
}


void view_directory (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    GnomeCmdFile *f = fl->get_selected_file();
    if (f && f->GetGfileAttributeUInt32(G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
        fs->do_file_specific_action (fl, f);
}


void view_home (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    if (!fl->locked)
    {
        fl->set_connection(get_home_con ());
        fl->goto_directory("~");
    }
    else
        fs->new_tab(gnome_cmd_dir_new (get_home_con (), gnome_cmd_con_create_path (get_home_con (), g_get_home_dir ())));
}


void view_root (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFileList *fl = fs->file_list();

    if (fl->locked)
        fs->new_tab(gnome_cmd_dir_new (fl->con, gnome_cmd_con_create_path (fl->con, "/")));
    else
        fs->goto_directory("/");
}


void view_new_tab (GtkMenuItem *menuitem, gpointer file_list)
{
    GnomeCmdFileList *fl = file_list ? GNOME_CMD_FILE_LIST (file_list) : get_fl (ACTIVE);
    GnomeCmdFileSelector *fs = GNOME_CMD_FILE_SELECTOR (gtk_widget_get_ancestor (*fl, GNOME_CMD_TYPE_FILE_SELECTOR));
    fs->new_tab(fl->cwd);
}


void view_close_tab (GtkMenuItem *menuitem, gpointer file_list)
{
    if (file_list)
    {
        GnomeCmdFileList *fl = GNOME_CMD_FILE_LIST (file_list);
        GnomeCmdFileSelector *fs = GNOME_CMD_FILE_SELECTOR (gtk_widget_get_ancestor (*fl, GNOME_CMD_TYPE_FILE_SELECTOR));

        if (fs->notebook->size()>1)
            if (!fl->locked || gnome_cmd_prompt_message (*main_win, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, _("The tab is locked, close anyway?"))==GTK_RESPONSE_OK)
                fs->close_tab(gtk_notebook_page_num (*fs->notebook, gtk_widget_get_parent (*fl)));
    }
    else
    {
        GnomeCmdFileSelector *fs = get_fs (ACTIVE);

        if (fs->notebook->size()>1)
            if (!fs->file_list()->locked || gnome_cmd_prompt_message (*main_win, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, _("The tab is locked, close anyway?"))==GTK_RESPONSE_OK)
                fs->close_tab();
    }
}


void view_close_all_tabs (GtkMenuItem *menuitem, gpointer file_selector)
{
    GnomeCmdFileSelector *fs = file_selector ? GNOME_CMD_FILE_SELECTOR (file_selector) : get_fs (ACTIVE);
    GnomeCmdNotebook *notebook = fs->notebook;
    gint n = notebook->get_current_page();

    for (gint i=notebook->size(); i--;)
        if (i!=n && !fs->file_list(i)->locked)
            notebook->remove_page(i);
}


void view_close_duplicate_tabs (GtkMenuItem *menuitem, gpointer file_selector)
{
    GnomeCmdFileSelector *fs = file_selector ? GNOME_CMD_FILE_SELECTOR (file_selector) : get_fs (ACTIVE);
    GnomeCmdNotebook *notebook = fs->notebook;

    typedef set<gint> TABS_COLL;
    typedef map <GnomeCmdDir *, TABS_COLL> DIRS_COLL;

    DIRS_COLL dirs;

    for (gint i=notebook->size(); i--;)
    {
        GnomeCmdFileList *fl = GNOME_CMD_FILE_LIST (gtk_bin_get_child (GTK_BIN (notebook->page(i))));

        if (fl && !fl->locked)
            dirs[fl->cwd].insert(i);
    }

    TABS_COLL duplicates;

    DIRS_COLL::iterator pos = dirs.find(fs->get_directory());       //  find tabs with the current dir...

    if (pos!=dirs.end())
    {
        duplicates.swap(pos->second);                               //  ... and mark them as to be closed...
        duplicates.erase(notebook->get_current_page());             //  ... but WITHOUT the current one
        dirs.erase(pos);
    }

    for (DIRS_COLL::const_iterator i=dirs.begin(); i!=dirs.end(); ++i)
    {
        TABS_COLL::iterator beg = i->second.begin();
        copy(++beg, i->second.end(), inserter(duplicates, duplicates.begin()));   //  for every dir, leave the first tab opened
    }

    for (TABS_COLL::const_reverse_iterator i=duplicates.rbegin(); i!=duplicates.rend(); ++i)
        notebook->remove_page(*i);
}


void view_prev_tab (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->notebook->prev_page();
}


void view_next_tab (GtkMenuItem *menuitem, gpointer not_used)
{
    get_fs (ACTIVE)->notebook->next_page();
}


void view_in_new_tab (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdFileSelector *fs = get_fs (ACTIVE);
    GnomeCmdFile *file = fs->file_list()->get_selected_file();

    if (file && file->GetGfileAttributeUInt32(G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
        fs->new_tab(GNOME_CMD_DIR (file), FALSE);
    else
        fs->new_tab(fs->get_directory(), FALSE);
}


void view_in_inactive_tab (GtkMenuItem *menuitem, gpointer file_list)
{
    GnomeCmdFileList *fl = file_list ? GNOME_CMD_FILE_LIST (file_list) : get_fl (ACTIVE);
    GnomeCmdFile *file = fl->get_selected_file();

    if (file && file->GetGfileAttributeUInt32(G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
        get_fs (INACTIVE)->new_tab(GNOME_CMD_DIR (file), FALSE);
    else
        get_fs (INACTIVE)->new_tab(fl->cwd, FALSE);
}


void view_toggle_tab_lock (GtkMenuItem *menuitem, gpointer page)
{
    //  0       -> current tab
    //  1 .. n  -> tab #n for active fs
    // -1 .. -n -> tab #n for inactive fs

    int n = GPOINTER_TO_INT (page);
    GnomeCmdFileSelector *fs = get_fs (n>=0 ? ACTIVE : INACTIVE);
    GnomeCmdFileList *fl = n==0 ? get_fl (ACTIVE) : fs->file_list(ABS(n)-1);

    if (fs && fl)
    {
        fl->locked = !fl->locked;
        fs->update_tab_label(fl);
    }
}


/************** Options Menu **************/
void options_edit (GtkMenuItem *menuitem, gpointer not_used)
{
    if (gnome_cmd_options_dialog (*main_win, gnome_cmd_data.options))
    {
        main_win->update_view();

        gnome_cmd_data.save();
    }
}


void options_edit_shortcuts (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_key_shortcuts_dialog_new (gcmd_user_actions);
}

/************** Connections Menu **************/
void connections_open (GtkMenuItem *menuitem, gpointer not_used)
{
    GtkWidget *dialog = gnome_cmd_remote_dialog_new ();
    g_object_ref (dialog);
    gtk_widget_show (dialog);
}


void connections_new (GtkMenuItem *menuitem, gpointer not_used)
{
    show_quick_connect_dialog ();
}


void connections_change (GtkMenuItem *menuitem, gpointer con)           // this function is NOT exposed to user as UserAction
{
    get_fl (ACTIVE)->set_connection(static_cast<GnomeCmdCon*> (con));
}


void connections_change_left (GtkMenuItem *menuitem, gpointer con)
{
    main_win->change_connection(LEFT);
}


void connections_change_right (GtkMenuItem *menuitem, gpointer con)
{
    main_win->change_connection(RIGHT);
}


void connections_close (GtkMenuItem *menuitem, gpointer con)            // this function is NOT exposed to user as UserAction
{
    GnomeCmdFileSelector *active = get_fs (ACTIVE);
    GnomeCmdFileSelector *inactive = get_fs (INACTIVE);

    GnomeCmdCon *home = get_home_con ();

    if (con == active->get_connection())
        active->set_connection(home);
    if (con == inactive->get_connection())
        inactive->set_connection(home);

    gnome_cmd_con_close (static_cast<GnomeCmdCon*> (con));
}


void connections_close_current (GtkMenuItem *menuitem, gpointer not_used)
{
    GnomeCmdCon *con = get_fs (ACTIVE)->get_connection();

    if (!GNOME_CMD_IS_CON_HOME (con))
        connections_close (menuitem, con);
}


/************** Bookmarks Menu **************/

void bookmarks_add_current (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_bookmark_add_current (get_fs (ACTIVE)->get_directory());
}


void bookmarks_edit (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_bookmark_dialog_new (_("Bookmarks"), *main_win);
}


void bookmarks_goto (GtkMenuItem *menuitem, gpointer bookmark_name)
{
    if (!bookmark_name)
        return;

    vector<string> a;
    guint n = split(static_cast<gchar*>(bookmark_name), a, "|");
    string group = n<2 || a[0].empty() ? "local" : a[0];
    string name = a[n<2 ? 0 : 1];

    transform(group.begin(), group.end(), group.begin(), (int(*)(int))tolower);

    if (group=="local")
    {
        for (GList *bookmarks = gnome_cmd_con_get_bookmarks (get_home_con ())->bookmarks; bookmarks; bookmarks = bookmarks->next)
        {
            auto bookmark = static_cast<GnomeCmdBookmark*> (bookmarks->data);

            if (name == bookmark->name)
            {
                gnome_cmd_bookmark_goto (bookmark);
                return;
            }
        }

        g_warning ("[%s] Unknown bookmark name: '%s' - ignored", (char *) bookmark_name, name.c_str());
    }
#ifdef HAVE_SAMBA
    else
        if (group=="smb" || group=="samba")
        {
            for (GList *bookmarks = gnome_cmd_con_get_bookmarks (get_smb_con ())->bookmarks; bookmarks; bookmarks = bookmarks->next)
            {
                auto bookmark = static_cast<GnomeCmdBookmark*> (bookmarks->data);

                if (name == bookmark->name)
                {
                    gnome_cmd_bookmark_goto (bookmark);
                    return;
                }
            }

            g_warning ("[%s] Unknown bookmark name: '%s' - ignored", (char *) bookmark_name, name.c_str());
        }
#endif
        else
            g_warning ("[%s] Unsupported bookmark group: '%s' - ignored", (char *) bookmark_name, group.c_str());
}


void bookmarks_view (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_dir_indicator_show_bookmarks (GNOME_CMD_DIR_INDICATOR (get_fs (ACTIVE)->dir_indicator));
}


/************** Plugins Menu **************/

void plugins_configure (GtkMenuItem *menuitem, gpointer not_used)
{
    plugin_manager_show ();
}


/************** Help Menu **************/

void help_help (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_help_display ("gnome-commander.xml");
}


void help_keyboard (GtkMenuItem *menuitem, gpointer not_used)
{
    gnome_cmd_help_display ("gnome-commander.xml", "gnome-commander-keyboard");
}


void help_web (GtkMenuItem *menuitem, gpointer not_used)
{
    GError *error = nullptr;

    if (!gtk_show_uri (nullptr, PACKAGE_URL, GDK_CURRENT_TIME, &error))
        gnome_cmd_error_message (_("There was an error opening home page."), error);
}


void help_problem (GtkMenuItem *menuitem, gpointer not_used)
{
    GError *error = nullptr;

    if (!gtk_show_uri (nullptr, PACKAGE_BUGREPORT, GDK_CURRENT_TIME, &error))
        gnome_cmd_error_message (_("There was an error reporting problem."), error);
}


void help_about (GtkMenuItem *menuitem, gpointer not_used)
{
    static const gchar *authors[] = {
        "Marcus Bjurman <marbj499@student.liu.se>",
        "Piotr Eljasiak <epiotr@use.pl>",
        "Assaf Gordon <agordon88@gmail.com>",
        "Uwe Scholz <u.scholz83@gmx.de>",
        nullptr
    };

    static const gchar *documenters[] = {
        "Marcus Bjurman <marbj499@student.liu.se>",
        "Piotr Eljasiak <epiotr@use.pl>",
        "Laurent Coudeur <laurentc@eircom.net>",
        "Uwe Scholz <u.scholz83@gmx.de>",
        nullptr
    };

    static const gchar copyright[] = "Copyright \xc2\xa9 2001-2006 Marcus Bjurman\n"
                                     "Copyright \xc2\xa9 2007-2012 Piotr Eljasiak\n"
                                     "Copyright \xc2\xa9 2013-2023 Uwe Scholz";

    static const gchar comments[] = N_("A fast and powerful file manager for the GNOME desktop");


    static const gchar *license[] = {
        N_("GNOME 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 2 of the License, or "
           "(at your option) any later version."),
        N_("GNOME 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."),
        N_("You should have received a copy of the GNU General Public License "
           "along with GNOME Commander; if not, write to the Free Software Foundation, Inc., "
           "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.")
    };

    gchar *license_trans = g_strjoin ("\n\n", _(license[0]), _(license[1]), _(license[2]), nullptr);

    gtk_show_about_dialog (*main_win,
                           "name", "GNOME Commander",
                           "version", PACKAGE_VERSION,
                           "comments", _(comments),
                           "copyright", copyright,
                           "license", license_trans,
                           "wrap-license", TRUE,
                           "authors", authors,
                           "documenters", documenters,
                           "logo-icon-name", PACKAGE_NAME,
                           "translator-credits", _("translator-credits"),
                           "website", "https://gcmd.github.io",
                           "website-label", "GNOME Commander Website",
                           nullptr);

    g_free (license_trans);
}

/**
 * Parses a command, stored in a char array, and substitutes a certain
 * symbol with path or URI names. The result is stored in a string
 * variable, which is expanded if needed. Possible symbols are:
 * \li \%f: file name (or list for multiple selections)
 * \li \%F: quoted filename (or list for multiple selections)
 * \li \%p: full file system path (or list for multiple selections)
 * \li \%P: quoted full file system path (or list for multiple selections)
 * \li \%s: synonym for %P (for compatibility with previous versions of gcmd)
 * \li \%u: fully qualified URI for the file (or list for multiple selections)
 * \li \%d: full path to the directory containing file
 * \li \%D: quoted full path to the directory containg file
 * \li \%%: percent sign
 * Trailing blanks are removed.
 *
 * \param[in] command A char array to be parsed
 * \param[out] cmd A string with parsed symbols listed above
 * \returns Length of the cmd string
 */
int parse_command(string *cmd, const gchar *command)
{
    gboolean percent = FALSE;
    gboolean blcheck = FALSE;
    string filename;
    string quoted_filename;
    string file_path;
    string quoted_file_path;
    string dir_path;
    string quoted_dir_path;
    string uri;
    unsigned cmdcap;
    unsigned cmdlen;

    GnomeCmdFileList *fl = get_fl (ACTIVE);
    GList *sfl = fl->get_selected_files();

    if (sfl)
    {
        auto gnomeCmdFile = (GnomeCmdFile*) sfl->data;
        auto gFileParent = g_file_get_parent(gnomeCmdFile->gFile);
        dir_path = g_file_get_path(gFileParent);
        g_object_unref(gFileParent);
    }
    else
    {
        // ToDo: Fix this misleading usage of the home directory
        dir_path = g_get_home_dir();
    }


    cmdcap = cmd->capacity();
    cmdlen = cmd->length();

    for (auto s = command; *s; ++s) // loop over chars of command-string set by user
    {
        if (!percent) // check if % not found
        {
            percent = (*s == '%'); // true: if s=%, false: if s!=%
            if (!percent) // check if % not found
            {
                if (cmdcap < cmdlen + 1)
                {
                    cmd->reserve(cmdlen +1);
                    cmdcap = cmd->capacity();
                }
                if (blcheck) // copy letter by letter, but remove trailing blanks
                    *cmd += *s;
                else
                {
                    if (*s == ' ' || *s == '\t')
                    {
                    continue;
                    }
                    else
                    {
                    blcheck = TRUE;
                    *cmd += *s;
                    }
                }
                cmdlen = cmd->length();
            }

            continue;
        }

        switch (*s)
        {
            case 'f':           // %f  file name (or list for multiple selections)
                if (filename.empty())
                    get_file_list (filename, sfl, gnome_cmd_file_get_name);
		if (cmdcap < cmdlen + filename.length())
		{
		    cmd->reserve(cmdlen + filename.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += filename;
		cmdlen = cmd->length();
                break;

            case 'F':           // %F  quoted filename (or list for multiple selections)
                if (quoted_filename.empty())
                    get_file_list (quoted_filename, sfl, gnome_cmd_file_get_quoted_name);
		if (cmdcap < cmdlen + quoted_filename.length())
		{
		    cmd->reserve(cmdlen + quoted_filename.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += quoted_filename;
		cmdlen = cmd->length();
                break;

            case 'p':           // %p  full file system path (or list for multiple selections)
                if (file_path.empty())
                    get_file_list (file_path, sfl, gnome_cmd_file_get_real_path);
		if (cmdcap < cmdlen + file_path.length())
		{
		    cmd->reserve(cmdlen + file_path.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += file_path;
		cmdlen = cmd->length();
                break;

            case 'P':           // %P  quoted full file system path (or list for multiple selections)
            case 's':           // %s  synonym for %P (for compatibility with previous versions of gcmd)
                if (quoted_file_path.empty())
                    get_file_list (quoted_file_path, sfl, gnome_cmd_file_get_quoted_real_path);
		if (cmdcap < cmdlen + quoted_file_path.length())
		{
		    cmd->reserve(cmdlen + quoted_file_path.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += quoted_file_path;
		cmdlen = cmd->length();
                break;

            case 'u':           // %u  fully qualified URI for the file (or list for multiple selections)
                if (uri.empty())
                    get_file_list (uri, sfl, gnome_cmd_file_get_uri_str);
		if (cmdcap < cmdlen + uri.length())
		{
		    cmd->reserve(cmdlen + uri.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += uri;
		cmdlen = cmd->length();
                break;

            case 'd':           // %d  full path to the directory containing file
		if (cmdcap < cmdlen + dir_path.length())
		{
		    cmd->reserve(cmdlen + dir_path.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += dir_path;
		cmdlen = cmd->length();
                break;

            case 'D':           // %D  quoted full path to the directory containg file
		if (cmdcap < cmdlen + quoted_dir_path.length())
		{
		    cmd->reserve(cmdlen + quoted_dir_path.length());
		    cmdcap = cmd->capacity();
		}
                *cmd += quoted_dir_path;
		cmdlen = cmd->length();
                break;

            default:
		if (cmdcap < cmdlen + 1)
		{
		    cmd->reserve(cmdlen +1);
		    cmdcap = cmd->capacity();
		}
                *cmd += '%';
		cmdlen = cmd->length();
#if defined (__GNUC__) && __GNUC__ >= 7
		__attribute__ ((fallthrough));
#endif

            case '%':           // %%  percent sign
		if (cmdcap < cmdlen + 1)
		{
		    cmd->reserve(cmdlen +1);
		    cmdcap = cmd->capacity();
		}
                *cmd += *s;
		cmdlen = cmd->length();
                break;
        }

        percent = FALSE;
    }

    if (percent)
        *cmd += '%';

    return cmd->length();
}
