Ticket #4165: MultiSearch_v5.3-2_final.patch

File MultiSearch_v5.3-2_final.patch, 75.9 KB (added by psprint, 3 years ago)
  • lib/keybind.c

    From ba562895083b2ee209b465cdfb1d8a46d59fa42a Mon Sep 17 00:00:00 2001
    From: Sebastian Gniazdowski <sgniazdowski@gmail.com>
    Date: Wed, 20 Jan 2021 13:59:46 -0600
    Subject: =?UTF-8?q?MultiSearch=20=E2=80=93=C2=A0an=20AND-chained=20multi-w?=
     =?UTF-8?q?ord=20searching=20in=20any=20listbox.?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     lib/keybind.c                  |   1 +
     lib/keybind.h                  |   1 +
     lib/widget.h                   |   2 +
     lib/widget/Makefile.am         |   2 +
     lib/widget/dialog-switch.c     |   3 +-
     lib/widget/dialog.c            |  49 +--
     lib/widget/dialog.h            |   1 +
     lib/widget/filtering_listbox.c | 525 +++++++++++++++++++++++++++++++++
     lib/widget/filtering_listbox.h |  46 +++
     lib/widget/forwarding_input.c  | 160 ++++++++++
     lib/widget/forwarding_input.h  |  36 +++
     lib/widget/group.c             |  11 +-
     lib/widget/history.c           |  97 +++---
     lib/widget/history.h           |   4 +-
     lib/widget/input.c             |  60 +++-
     lib/widget/input.h             |   2 +
     lib/widget/input_complete.c    |  73 +++--
     lib/widget/listbox-window.c    |  24 +-
     lib/widget/listbox-window.h    |   4 +-
     lib/widget/listbox.c           |  75 ++++-
     lib/widget/listbox.h           |   8 +
     lib/widget/widget-common.c     |   2 +
     lib/widget/widget-common.h     |  15 +-
     src/editor/choosesyntax.c      |   2 +-
     src/editor/editcmd_dialogs.c   |  14 +-
     src/editor/editwidget.c        |   8 +-
     src/file_history.c             |   3 +-
     src/keybind-defaults.c         |   5 +-
     src/selcodepage.c              |   2 +-
     src/usermenu.c                 |   2 +-
     30 files changed, 1092 insertions(+), 145 deletions(-)
     create mode 100644 lib/widget/filtering_listbox.c
     create mode 100644 lib/widget/filtering_listbox.h
     create mode 100644 lib/widget/forwarding_input.c
     create mode 100644 lib/widget/forwarding_input.h
    
    diff --git a/lib/keybind.c b/lib/keybind.c
    index abd44d3e2..df3cbf110 100644
    a b static name_keymap_t command_names[] = { 
    9393    ADD_KEYMAP_NAME (SearchContinue), 
    9494    ADD_KEYMAP_NAME (Replace), 
    9595    ADD_KEYMAP_NAME (ReplaceContinue), 
     96    ADD_KEYMAP_NAME (MultiSearch), 
    9697    ADD_KEYMAP_NAME (Help), 
    9798    ADD_KEYMAP_NAME (Shell), 
    9899    ADD_KEYMAP_NAME (Edit), 
  • lib/keybind.h

    diff --git a/lib/keybind.h b/lib/keybind.h
    index af019df09..817158412 100644
    a b enum 
    8282    CK_SearchContinue, 
    8383    CK_Replace, 
    8484    CK_ReplaceContinue, 
     85    CK_MultiSearch, 
    8586    CK_SearchStop, 
    8687    CK_Help, 
    8788    CK_Edit, 
  • lib/widget.h

    diff --git a/lib/widget.h b/lib/widget.h
    index e3bb5cac2..cfe556891 100644
    a b typedef struct WGroup WGroup; 
    3030#include "lib/widget/groupbox.h" 
    3131#include "lib/widget/label.h" 
    3232#include "lib/widget/listbox.h" 
     33#include "lib/widget/filtering_listbox.h" 
    3334#include "lib/widget/menu.h" 
    3435#include "lib/widget/radio.h" 
    3536#include "lib/widget/input.h" 
     37#include "lib/widget/forwarding_input.h" 
    3638#include "lib/widget/listbox-window.h" 
    3739#include "lib/widget/quick.h" 
    3840#include "lib/widget/wtools.h" 
  • lib/widget/Makefile.am

    diff --git a/lib/widget/Makefile.am b/lib/widget/Makefile.am
    index 90f023bbc..9a4616c38 100644
    a b libmcwidget_la_SOURCES = \ 
    1616        history.c history.h \ 
    1717        input.c input.h \ 
    1818        input_complete.c \ 
     19        forwarding_input.c forwarding_input.h \ 
    1920        listbox-window.c listbox-window.h \ 
    2021        listbox.c listbox.h \ 
     22        filtering_listbox.c filtering_listbox.h \ 
    2123        label.c label.h \ 
    2224        menu.c menu.h \ 
    2325        mouse.c mouse.h \ 
  • lib/widget/dialog-switch.c

    diff --git a/lib/widget/dialog-switch.c b/lib/widget/dialog-switch.c
    index 93868b19d..a9a51ce0d 100644
    a b dialog_switch_list (void) 
    241241        else 
    242242            title = g_strdup (""); 
    243243 
    244         listbox_add_item (listbox->list, LISTBOX_APPEND_BEFORE, get_hotkey (i++), title, h, FALSE); 
     244        listbox_add_item (LISTBOX (listbox->list), LISTBOX_APPEND_BEFORE, get_hotkey (i++), title, 
     245                          h, FALSE); 
    245246 
    246247        g_free (title); 
    247248    } 
  • lib/widget/dialog.c

    diff --git a/lib/widget/dialog.c b/lib/widget/dialog.c
    index b8a08f029..c8d940be9 100644
    a b dlg_default_get_colors (const Widget * w) 
    8787    return CONST_DIALOG (w)->colors; 
    8888} 
    8989 
    90 /* --------------------------------------------------------------------------------------------- */ 
    91 /** 
    92   * Read histories from the ${XDG_CACHE_HOME}/mc/history file 
    93   */ 
    94 static void 
    95 dlg_read_history (WDialog * h) 
    96 { 
    97     char *profile; 
    98     ev_history_load_save_t event_data; 
    99  
    100     if (num_history_items_recorded == 0)        /* this is how to disable */ 
    101         return; 
    102  
    103     profile = mc_config_get_full_path (MC_HISTORY_FILE); 
    104     event_data.cfg = mc_config_init (profile, TRUE); 
    105     event_data.receiver = NULL; 
    106  
    107     /* create all histories in dialog */ 
    108     mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data); 
    109  
    110     mc_config_deinit (event_data.cfg); 
    111     g_free (profile); 
    112 } 
    113  
    11490/* --------------------------------------------------------------------------------------------- */ 
    11591 
    11692static void 
    do_refresh (void) 
    473449 
    474450/* --------------------------------------------------------------------------------------------- */ 
    475451 
     452/** 
     453  * Read histories from the ${XDG_CACHE_HOME}/mc/history file 
     454  */ 
     455void 
     456dlg_read_history (WDialog * h) 
     457{ 
     458    char *profile; 
     459    ev_history_load_save_t event_data; 
     460 
     461    if (num_history_items_recorded == 0)        /* this is how to disable */ 
     462        return; 
     463 
     464    profile = mc_config_get_full_path (MC_HISTORY_FILE); 
     465    event_data.cfg = mc_config_init (profile, TRUE); 
     466    event_data.receiver = NULL; 
     467 
     468    /* create all histories in dialog */ 
     469    mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data); 
     470 
     471    mc_config_deinit (event_data.cfg); 
     472    g_free (profile); 
     473} 
     474 
     475/* --------------------------------------------------------------------------------------------- */ 
     476 
    476477void 
    477478dlg_stop (WDialog * h) 
    478479{ 
  • lib/widget/dialog.h

    diff --git a/lib/widget/dialog.h b/lib/widget/dialog.h
    index 1d08b8e1a..d67735e71 100644
    a b int dlg_run (WDialog * d); 
    109109void dlg_destroy (WDialog * h); 
    110110 
    111111void dlg_run_done (WDialog * h); 
     112void dlg_read_history (WDialog * h); 
    112113void dlg_save_history (WDialog * h); 
    113114void dlg_process_event (WDialog * h, int key, Gpm_Event * event); 
    114115 
  • new file lib/widget/filtering_listbox.c

    diff --git a/lib/widget/filtering_listbox.c b/lib/widget/filtering_listbox.c
    new file mode 100644
    index 000000000..61f48c368
    - +  
     1/* 
     2   A class extending WListbox with dynamic filtering (i.e.: removal) of entries. 
     3 
     4   Copyright (C) <2021> 
     5   Free Software Foundation, Inc. 
     6 
     7   Written by: 
     8   Sebastian Gniazdowski <sgniazdowski@gmail.com>, 2021. 
     9 
     10   This file is part of the Midnight Commander. 
     11 
     12   The Midnight Commander is free software: you can redistribute it 
     13   and/or modify it under the terms of the GNU General Public License as 
     14   published by the Free Software Foundation, either version 3 of the License, 
     15   or (at your option) any later version. 
     16 
     17   The Midnight Commander is distributed in the hope that it will be useful, 
     18   but WITHOUT ANY WARRANTY; without even the implied warranty of 
     19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     20   GNU General Public License for more details. 
     21 
     22   You should have received a copy of the GNU General Public License 
     23   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
     24 */ 
     25 
     26/** \file filtering_listbox.c 
     27 *  \brief A WListbox inheriting class that adds dynamic filtering of entries. 
     28 *  \author Sebastian Gniazdowski 
     29 *  \date 2021 
     30 * 
     31 * In order to enable/disable (i.e.: toggle) MultiSearch on an listbox you can send a message: 
     32 *      send_message (listbox, NULL, MSG_ACTION, CK_MultiSearch, NULL); 
     33 * 
     34 * It'll extend owning dialog with a new MultiSearch input and pair it up with the listbox, so that 
     35 * some of keys (basically all that aren't in input's keymap) are forwarded to listbox. 
     36 */ 
     37 
     38#include <config.h> 
     39 
     40#include "lib/global.h" 
     41#include "lib/widget.h" 
     42#include "lib/tty/tty.h" 
     43 
     44/*** global variables ****************************************************************************/ 
     45 
     46/*** file scope macro definitions ****************************************************************/ 
     47 
     48/*** file scope type declarations ****************************************************************/ 
     49 
     50/*** file scope variables ************************************************************************/ 
     51 
     52/*** file scope functions ************************************************************************/ 
     53/* --------------------------------------------------------------------------------------------- */ 
     54 
     55static WLEntry * 
     56filt_listbox_shallow_copy_entry (WLEntry * src, gboolean take_ownership) 
     57{ 
     58    WLEntry *copy; 
     59    copy = g_new (WLEntry, 1); 
     60    *copy = *src; 
     61 
     62    /* Who has the ownership of the data? */ 
     63    src->free_text = src->free_text && !take_ownership; 
     64    src->free_data = src->free_data && !take_ownership; 
     65    copy->free_text = copy->free_text && take_ownership; 
     66    copy->free_data = copy->free_data && take_ownership; 
     67 
     68    return copy; 
     69} 
     70 
     71/* --------------------------------------------------------------------------------------------- */ 
     72 
     73static void 
     74filt_listbox_make_one_line_room (WFilteringListbox * sl, gboolean should_add_free_room) 
     75{ 
     76    WListbox *l = LISTBOX (sl); 
     77    Widget *w = WIDGET (l), *owner = WIDGET (WIDGET (w)->owner); 
     78    WRect r_dialog, r_listbox; 
     79    int new_dialog_height, new_dialog_ypos, new_listbox_height, take_give_from_to_owner = 1; 
     80 
     81    /* 
     82     * IF the enlarged dialog won't fit the screen, don't resize it but the listbox instead. 
     83     * Do it also when requested if the listbox is large (for small listboxes always try to 
     84     * enlarge the dialog). 
     85     */ 
     86    if ((sl->resize_strategy == FILT_LIST_DIALOG_AUTO_RESIZE && LINES <= owner->lines + 2) || 
     87        (sl->resize_strategy == FILT_LIST_KEEP_DIALOG_SIZE && owner->lines > 7)) 
     88        take_give_from_to_owner = 0; 
     89 
     90    /* Increase the height of the dialog by 1, so that the new input fits. */ 
     91    if (should_add_free_room) 
     92    { 
     93        new_dialog_height = owner->lines + take_give_from_to_owner; 
     94        new_listbox_height = w->lines + (-1 + take_give_from_to_owner); 
     95        new_dialog_ypos = owner->y - take_give_from_to_owner; 
     96    } 
     97    else 
     98    { 
     99        new_dialog_height = owner->lines - take_give_from_to_owner; 
     100        new_listbox_height = w->lines - (-1 + take_give_from_to_owner); 
     101        new_dialog_ypos = owner->y + take_give_from_to_owner; 
     102    } 
     103    rect_init (&r_dialog, new_dialog_ypos, owner->x, new_dialog_height, owner->cols); 
     104    rect_init (&r_listbox, w->y, w->x, new_listbox_height, w->cols); 
     105 
     106    /* 
     107     * Doing widget_set_size_rect(w, &r_listbox) causes problems as it invokes 
     108     * drawing of the widget owner. 
     109     */ 
     110    send_message (w, NULL, MSG_RESIZE, 0, &r_listbox); 
     111    send_message (owner, NULL, MSG_RESIZE, 0, &r_dialog); 
     112} 
     113 
     114/* --------------------------------------------------------------------------------------------- */ 
     115 
     116static void 
     117filt_listbox_show_multi_search_widget (WFilteringListbox * sl) 
     118{ 
     119    WListbox *l = LISTBOX (sl); 
     120    Widget *w = WIDGET (l), *owner = WIDGET (WIDGET (l)->owner); 
     121    WForwardingInput *multi_search_in; 
     122    int distance_y = owner->y + owner->lines - (w->y + w->lines) + 1; 
     123    int distance_x = w->cols > 40 ? 5 : 1, small = w->cols <= 15 ? 1 : 0; 
     124 
     125    filt_listbox_make_one_line_room (sl, 1); 
     126    multi_search_in = forwarding_input_new (owner->lines - distance_y, distance_x, 
     127                                            input_colors, w->cols - 2 - distance_x + small, "", 
     128                                            "multi_search", INPUT_COMPLETE_NONE, w); 
     129    group_add_widget_autopos (GROUP (owner), multi_search_in, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 
     130                              NULL); 
     131    /* Initialize input widget. */ 
     132    send_message (WIDGET (multi_search_in), w, MSG_INIT, 0, NULL); 
     133    dlg_read_history (DIALOG (w->owner)); 
     134 
     135    /* Draw dialog and listbox, and then input. */ 
     136    widget_draw (WIDGET (w->owner)); 
     137    widget_draw (w); 
     138    widget_draw (WIDGET (multi_search_in)); 
     139} 
     140 
     141/* --------------------------------------------------------------------------------------------- */ 
     142 
     143static void 
     144filt_listbox_hide_multi_search_widget (WFilteringListbox * sl) 
     145{ 
     146    WListbox *l = LISTBOX (sl); 
     147    Widget *w = WIDGET (sl); 
     148    Widget *in; 
     149    in = widget_find_by_type (WIDGET (WIDGET (l)->owner), forw_input_callback); 
     150    if (in != NULL) 
     151    { 
     152        group_remove_widget (in); 
     153        filt_listbox_make_one_line_room (sl, 0); 
     154        group_select_next_widget (WIDGET (l)->owner); 
     155 
     156        /* 
     157         * Repainting is needed because some part of the resized dialog can be left on the 
     158         * background. 
     159         */ 
     160        if (sl->resize_strategy != FILT_LIST_KEEP_DIALOG_SIZE || w->lines <= 7) 
     161            repaint_screen (); 
     162 
     163        widget_draw (WIDGET (w->owner)); 
     164        widget_draw (w); 
     165        widget_destroy (in); 
     166    } 
     167} 
     168 
     169/* --------------------------------------------------------------------------------------------- */ 
     170 
     171/* Return TRUE if given listbox is in WST_FILTER state. */ 
     172static gboolean 
     173filt_listbox_is_filter_state (WFilteringListbox * sl) 
     174{ 
     175    return widget_get_state (WIDGET (sl), WST_FILTER); 
     176} 
     177 
     178/* --------------------------------------------------------------------------------------------- */ 
     179 
     180static void 
     181filt_listbox_filter_list (WFilteringListbox * sl, const char *text) 
     182{ 
     183    WListbox *l = LISTBOX (sl); 
     184    int i, size; 
     185    GList *le; 
     186    char **query_terms; 
     187    gboolean has_c_files = FALSE, has_header_files = FALSE; 
     188    /* 
     189     * Remove the list and allocate a new one. The elements are only shallowly freed because the 
     190     * internal data is still used (and kept in list_keep field). 
     191     */ 
     192    if (l->list != NULL) 
     193        g_queue_free_full (l->list, g_free); 
     194    l->list = g_queue_new (); 
     195 
     196    /* Split the query into space delimeted strings. */ 
     197    query_terms = g_strsplit (text, " ", 10); 
     198 
     199 
     200    size = g_queue_get_length (sl->list_keep); 
     201    le = g_queue_peek_head_link (sl->list_keep); 
     202    for (i = 0; i < size; i++, le = g_list_next (le)) 
     203    { 
     204        WLEntry *e = LENTRY (le->data); 
     205        size_t e_len; 
     206 
     207        /* Compute lenght once, then use it. */ 
     208        e_len = e->text ? strlen (e->text) : 0; 
     209 
     210        if (g_strrstr (e->text, ".c") == e->text + e_len - 2 || 
     211            g_strrstr (e->text, ".c~") == e->text + e_len - 3) 
     212            has_c_files = TRUE; 
     213        if (g_strrstr (e->text, ".cpp") == e->text + e_len - 4 || 
     214            g_strrstr (e->text, ".cpp~") == e->text + e_len - 5) 
     215            has_c_files = TRUE; 
     216        if (g_strrstr (e->text, ".h") == e->text + e_len - 2 || 
     217            g_strrstr (e->text, ".h~") == e->text + e_len - 3) 
     218            has_header_files = TRUE; 
     219        if (has_c_files && has_header_files) 
     220            break; 
     221    } 
     222 
     223    /* 
     224     * Get the size of the listbox and iterate over it testing each element against «all» words in  
     225     * query_terms. 
     226     */ 
     227    le = g_queue_peek_head_link (sl->list_keep); 
     228    for (i = 0; i < size; i++, le = g_list_next (le)) 
     229    { 
     230        WLEntry *e = LENTRY (le->data); 
     231        size_t e_len; 
     232        gboolean match = TRUE; 
     233 
     234        e_len = e->text ? strlen (e->text) : 0; 
     235 
     236        /* Test the query against the list entry. */ 
     237        for (gchar ** p = query_terms; *p != NULL; p++) 
     238        { 
     239 
     240            gboolean is_p_special; 
     241 
     242            /* Is it a special, shorthand query ("c" or "h") ? */ 
     243            is_p_special = g_strcmp0 (*p, "h") == 0 || g_strcmp0 (*p, "c") == 0; 
     244 
     245            /* Use special meaning of c/h queries only, when files seem to include such files. */ 
     246            if (is_p_special && (has_c_files || has_header_files)) 
     247            { 
     248                if (has_header_files && **p == 'h' && (g_strrstr (e->text, ".h") == 
     249                                                       e->text + e_len - 2 
     250                                                       || g_strrstr (e->text, 
     251                                                                     ".h~") == e->text + e_len - 3)) 
     252                { 
     253                    /* The file name (...apparently) matches -> do nothing. */ 
     254                } 
     255                else if (has_c_files && **p == 'c' && (g_strrstr (e->text, ".c") == 
     256                                                       e->text + e_len - 2 
     257                                                       || g_strrstr (e->text, 
     258                                                                     ".c~") == e->text + e_len - 3 
     259                                                       || g_strrstr (e->text, 
     260                                                                     ".cpp") == e->text + e_len - 4 
     261                                                       || g_strrstr (e->text, 
     262                                                                     ".cpp~") == 
     263                                                       e->text + e_len - 5)) 
     264                { 
     265                    /* The file name (...apparently) matches -> do nothing. */ 
     266                } 
     267                else 
     268                { 
     269                    /* No matching of a special query -> filter element out. */ 
     270                    match = FALSE; 
     271                    break; 
     272                } 
     273            } 
     274            else if (**p != '\0' && !strcasestr (e->text, *p)) 
     275            { 
     276                /* 
     277                 * No match -> filter out this entry. */ 
     278                match = FALSE; 
     279                break; 
     280            } 
     281        } 
     282 
     283        /* If all the terms matched, then add the element to the list. */ 
     284        if (match) 
     285            g_queue_push_tail (l->list, filt_listbox_shallow_copy_entry (e, FALSE)); 
     286    } 
     287    if (listbox_is_empty (l)) 
     288    { 
     289        listbox_add_item (l, LISTBOX_APPEND_AT_END, 0, "<no search results>", NULL, FALSE); 
     290        LENTRY (g_queue_peek_head_link (l->list)->data)->index = -2; 
     291    } 
     292    size = g_queue_get_length (l->list); 
     293    if (l->pos >= size) 
     294        listbox_select_entry (l, size - 1); 
     295    else 
     296        listbox_select_entry (l, l->pos); 
     297 
     298    g_strfreev (query_terms); 
     299} 
     300 
     301/* --------------------------------------------------------------------------------------------- */ 
     302 
     303/* Restores original elements of the list (from sl->list_keep) and turns off the WST_FILTER state. */ 
     304static gboolean 
     305filt_listbox_set_to_normal_state (WFilteringListbox * sl) 
     306{ 
     307    WListbox *l = LISTBOX (sl); 
     308    /* The listbox is already in non-filter state? */ 
     309    if (!widget_get_state (WIDGET (l), WST_FILTER)) 
     310    { 
     311        /* Return doing no change, just signal the error. */ 
     312        return FALSE; 
     313    } 
     314 
     315    /* The keep-list must be allocated (even if it's empty). */ 
     316    g_assert (sl->list_keep != NULL); 
     317 
     318    /* Mark the new state. */ 
     319    widget_set_state (WIDGET (l), WST_FILTER, FALSE); 
     320 
     321    /* Release the filtered list and replace it with the original, complete list (it owns the 
     322     * internal data, hence the release is a shallow one). */ 
     323    g_queue_free_full (l->list, g_free); 
     324    l->list = sl->list_keep; 
     325    sl->list_keep = NULL; 
     326    return TRUE; 
     327} 
     328 
     329/* --------------------------------------------------------------------------------------------- */ 
     330 
     331/* 
     332 * Sets the listbox into «filter» state. In this state, there's a separate copy of all list 
     333 * entries, while the original (and displayed) field ->list is being a filtered version of the 
     334 * full copy. 
     335 */ 
     336static gboolean 
     337filt_listbox_set_to_filter_state (WFilteringListbox * sl) 
     338{ 
     339    WListbox *l = LISTBOX (sl); 
     340    GList *le; 
     341 
     342    /* The listbox is already in filter state? */ 
     343    if (widget_get_state (WIDGET (l), WST_FILTER)) 
     344    { 
     345        /* Return doing no change, just signal the error. */ 
     346        return FALSE; 
     347    } 
     348 
     349    /* Mark the new state. */ 
     350    widget_set_state (WIDGET (l), WST_FILTER, TRUE); 
     351 
     352    /* No list copy when entering filter mode. */ 
     353    g_assert (sl->list_keep == NULL); 
     354    sl->list_keep = g_queue_new (); 
     355 
     356    /* Skip empty lists. */ 
     357    if (listbox_is_empty (l)) 
     358        return TRUE; 
     359 
     360    /* 
     361     * Remember the original position in the list in the field. It'll be used to determine the 
     362     * virtual_pos field. 
     363     */ 
     364    listbox_init_indices (l); 
     365 
     366    /* Perform a shallow copy of the original list. */ 
     367    for (le = g_queue_peek_head_link (l->list); le != NULL; le = g_list_next (le)) 
     368    { 
     369        WLEntry *copy; 
     370        copy = filt_listbox_shallow_copy_entry (LENTRY (le->data), TRUE); 
     371        g_queue_push_tail (sl->list_keep, copy); 
     372    } 
     373 
     374    return TRUE; 
     375} 
     376 
     377/* --------------------------------------------------------------------------------------------- */ 
     378 
     379/* 
     380 * If the list is filtered it replaces from the back list (list_keep). It returns whether such 
     381 * change occurred – FALSE means that the list was already unfiltered. 
     382 */ 
     383gboolean 
     384filt_listbox_ensure_unfiltered_state (WFilteringListbox * sl) 
     385{ 
     386    gboolean ret = FALSE; 
     387    if (filt_listbox_is_filter_state (sl)) 
     388        ret = filt_listbox_set_to_normal_state (sl); 
     389    return ret; 
     390} 
     391 
     392/* --------------------------------------------------------------------------------------------- */ 
     393 
     394gboolean 
     395filt_listbox_conditionally_enable_multi_search_init (WFilteringListbox * sl) 
     396{ 
     397    gboolean start_with_multi_search_active; 
     398 
     399    /* Option of starting the listbox with MultiSearch pre-activated. */ 
     400    start_with_multi_search_active = 
     401        mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION, 
     402                            "multi_search_active_by_default", 1); 
     403 
     404    /* CK_MultiSearch toggles the state. */ 
     405    if (start_with_multi_search_active) 
     406        send_message (WIDGET (sl), NULL, MSG_ACTION, CK_MultiSearch, NULL); 
     407    else 
     408        /* Only init embedded position indices. */ 
     409        listbox_init_indices (LISTBOX (sl)); 
     410 
     411    /* Return if did enable MultiSearch. */ 
     412    return start_with_multi_search_active; 
     413} 
     414 
     415/* --------------------------------------------------------------------------------------------- */ 
     416/*** public functions ****************************************************************************/ 
     417/* --------------------------------------------------------------------------------------------- */ 
     418 
     419WFilteringListbox * 
     420filtering_listbox_new (int y, int x, int height, int width, 
     421                       gboolean deletable, lcback_fn callback, 
     422                       filt_listbox_resize_strategy_t resize) 
     423{ 
     424    WFilteringListbox *object; 
     425    Widget *w_ref; 
     426 
     427    /* Allocate memory for the object body. */ 
     428    object = g_new (WFilteringListbox, 1); 
     429 
     430    /* Forward the call to construct the inherited object. */ 
     431    listbox_init (&object->base, y, x, height, width, deletable, callback); 
     432 
     433    /* Alter fields of base class. */ 
     434    w_ref = WIDGET (object); 
     435    w_ref->callback = filt_listbox_callback;    /* Set custom callback handler */ 
     436 
     437    /* Set extending fields of this class. */ 
     438    object->list_keep = NULL;   /* No back buffer at startup */ 
     439    object->resize_strategy = resize;   /* Save resize strategy */ 
     440    object->initialized = FALSE; 
     441 
     442    return object; 
     443} 
     444 
     445/* --------------------------------------------------------------------------------------------- */ 
     446 
     447cb_ret_t 
     448filt_listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
     449{ 
     450    WFilteringListbox *sl = FILT_LISTBOX (w);   /* s* - from `screen`, a "screened" listbox */ 
     451    cb_ret_t ret = MSG_NOT_HANDLED; 
     452    long activity; 
     453 
     454    switch (msg) 
     455    { 
     456    case MSG_INIT: 
     457        if (!sl->initialized) 
     458        { 
     459            filt_listbox_conditionally_enable_multi_search_init (sl); 
     460            sl->initialized = TRUE; 
     461        } 
     462        /* WListbox doesn't have MSG_INIT, so don't forward. */ 
     463        ret = MSG_HANDLED; 
     464        break; 
     465    case MSG_ACTION: 
     466        if (parm == CK_MultiSearch) 
     467        { 
     468            gboolean retval; 
     469            /* Toggle the multi term searching of any listbox. */ 
     470            if (filt_listbox_is_filter_state (sl)) 
     471            { 
     472                /* Remove the input widget from the dialog. */ 
     473                filt_listbox_hide_multi_search_widget (sl); 
     474                /* Restore original (unfiltered) listbox contents. */ 
     475                retval = filt_listbox_set_to_normal_state (sl); 
     476            } 
     477            else 
     478            { 
     479                /* Add input widget for the filter query at the bottom of the dialog window. */ 
     480                filt_listbox_show_multi_search_widget (sl); 
     481                /* ... and then turn on the filter state. */ 
     482                retval = filt_listbox_set_to_filter_state (sl); 
     483            } 
     484            if (!retval) 
     485                message (D_ERROR | D_CENTER, MSG_ERROR, 
     486                         "An internal error #3 occurred (filtered listbox support)."); 
     487 
     488            ret = MSG_HANDLED; 
     489        } 
     490        break; 
     491 
     492    case MSG_KEY: 
     493        activity = widget_lookup_key (WIDGET (sl), parm); 
     494        if (activity == CK_MultiSearch) 
     495            ret = send_message (w, NULL, MSG_ACTION, CK_MultiSearch, NULL); 
     496        break; 
     497 
     498    case MSG_NOTIFY: 
     499        if (widget_get_state (w, WST_FILTER)) 
     500        { 
     501            filt_listbox_filter_list (sl, (char *) data); 
     502            ret = MSG_HANDLED; 
     503        } 
     504        widget_draw (w); 
     505        /* Protect against normal, non ForwardingInput messages, which might not have sender set. */ 
     506        if (sender) 
     507            widget_draw (sender); 
     508        break; 
     509 
     510    case MSG_DESTROY: 
     511        filt_listbox_ensure_unfiltered_state (sl); 
     512        /* ret is unhandled -> the message will be forwarded to base class. */ 
     513        break; 
     514    default: 
     515        break; 
     516    } 
     517 
     518    /* Forward action to base class in case it's not yet handled. */ 
     519    if (ret == MSG_NOT_HANDLED) 
     520        ret = listbox_callback (w, sender, msg, parm, data); 
     521 
     522    return ret; 
     523} 
     524 
     525/* --------------------------------------------------------------------------------------------- */ 
  • new file lib/widget/filtering_listbox.h

    diff --git a/lib/widget/filtering_listbox.h b/lib/widget/filtering_listbox.h
    new file mode 100644
    index 000000000..636e75819
    - +  
     1#ifndef MC__FILTERING_LISTBOX_H 
     2#define MC__FILTERING_LISTBOX_H 
     3 
     4/*** typedefs(not structures) and defined constants **********************************************/ 
     5 
     6/* Casting macros. */ 
     7#define FILT_LISTBOX(x) ((WFilteringListbox *)(x)) 
     8#define CONST_FILT_LISTBOX(x) ((const WFilteringListbox *)(x)) 
     9 
     10/*** enums ***************************************************************************************/ 
     11 
     12typedef enum filt_listbox_resize_strategy_e 
     13{ 
     14    FILT_LIST_EXTEND_DIALOG = 0, 
     15    FILT_LIST_KEEP_DIALOG_SIZE, 
     16    FILT_LIST_DIALOG_AUTO_RESIZE 
     17} filt_listbox_resize_strategy_t; 
     18 
     19/*** structures declarations (and typedefs of structures)*****************************************/ 
     20 
     21typedef struct WFilteringListbox_s 
     22{ 
     23    WListbox base; 
     24    gboolean initialized;       /* Whether MSG_INIT has been received. */ 
     25 
     26    /* Fields for new logic. */ 
     27    GQueue *list_keep;          /* Unfiltered list (used in  WST_FILTER state). */ 
     28    filt_listbox_resize_strategy_t resize_strategy; 
     29} WFilteringListbox; 
     30 
     31/*** global variables defined in .c file *********************************************************/ 
     32 
     33/*** declarations of public functions ************************************************************/ 
     34 
     35WFilteringListbox *filtering_listbox_new (int y, int x, int height, int width, 
     36                                          gboolean deletable, lcback_fn callback, 
     37                                          filt_listbox_resize_strategy_t resize); 
     38gboolean filt_listbox_ensure_unfiltered_state (WFilteringListbox * l); 
     39gboolean filt_listbox_conditionally_enable_multi_search_init (WFilteringListbox * l); 
     40void filt_listbox_select_entry (WFilteringListbox * sl, int dest); 
     41cb_ret_t filt_listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, 
     42                                void *data); 
     43 
     44/*** inline functions ****************************************************************************/ 
     45 
     46#endif /* MC__FILTERING_LISTBOX_H */ 
  • new file lib/widget/forwarding_input.c

    diff --git a/lib/widget/forwarding_input.c b/lib/widget/forwarding_input.c
    new file mode 100644
    index 000000000..f69440a10
    - +  
     1/* 
     2   A key forwarding extended input class. 
     3 
     4   Copyright (C) 2021 
     5   Free Software Foundation, Inc. 
     6 
     7   Written by: 
     8   Sebastian Gniazdowski <sgniazdowski@gmail.com>, 2021. 
     9 
     10   This file is part of the Midnight Commander. 
     11 
     12   The Midnight Commander is free software: you can redistribute it 
     13   and/or modify it under the terms of the GNU General Public License as 
     14   published by the Free Software Foundation, either version 3 of the License, 
     15   or (at your option) any later version. 
     16 
     17   The Midnight Commander is distributed in the hope that it will be useful, 
     18   but WITHOUT ANY WARRANTY; without even the implied warranty of 
     19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     20   GNU General Public License for more details. 
     21 
     22   You should have received a copy of the GNU General Public License 
     23   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
     24 */ 
     25 
     26/** \file forwarding-input.c 
     27 *  \brief This input class has a feature of forwarding unrecognized keys to a widget passed at 
     28 *         creation. 
     29 *  \author Sebastian Gniazdowski 
     30 *  \date 2021 
     31 * 
     32 *  It's being used by MultiSearch to allow moving across listbox while typing to the input. 
     33 */ 
     34 
     35#include <config.h> 
     36 
     37#include "lib/global.h" 
     38#include "lib/widget.h" 
     39 
     40/*** global variables ****************************************************************************/ 
     41 
     42/*** file scope macro definitions ****************************************************************/ 
     43 
     44/*** file scope type declarations ****************************************************************/ 
     45 
     46/*** file scope variables ************************************************************************/ 
     47 
     48/*** file scope functions ************************************************************************/ 
     49/* --------------------------------------------------------------------------------------------- */ 
     50 
     51/* --------------------------------------------------------------------------------------------- */ 
     52/*** public functions ****************************************************************************/ 
     53/* --------------------------------------------------------------------------------------------- */ 
     54 
     55WForwardingInput * 
     56forwarding_input_new (int y, int x, const int *colors, 
     57                      int len, const char *text, const char *histname, 
     58                      input_complete_t completion_flags, Widget * forward_to_widget) 
     59{ 
     60    WForwardingInput *object; 
     61    Widget *w_ref; 
     62 
     63    /* Allocate memory for the object body. */ 
     64    object = g_new (WForwardingInput, 1); 
     65 
     66    /* Call upper constructor to initialize the inherited object. */ 
     67    input_init (&object->base, y, x, colors, len, text, histname, completion_flags); 
     68 
     69    /* Alter fields of base class. */ 
     70    w_ref = WIDGET (object); 
     71    w_ref->callback = forw_input_callback;      /* Set custom callback handler */ 
     72 
     73    /* Set  extending fields of this class. */ 
     74    object->forward_to_widget = forward_to_widget; 
     75    return object; 
     76} 
     77 
     78/* --------------------------------------------------------------------------------------------- */ 
     79 
     80cb_ret_t 
     81forw_input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
     82{ 
     83    WForwardingInput *in = FORW_INPUT (w); 
     84    gboolean key_message = FALSE; 
     85    cb_ret_t ret; 
     86 
     87    switch (msg) 
     88    { 
     89    case MSG_KEY: 
     90        ret = forw_input_handle_char (in, parm); 
     91        key_message = TRUE; 
     92        break; 
     93    default: 
     94        break; 
     95    } 
     96 
     97    /* 
     98     * Simply pass on all messages to the base class (except for MSG_KEY, which might have 
     99     * been possibly already sent from forw_input_handle_char() function). 
     100     */ 
     101 
     102    if (!key_message) 
     103        ret = input_callback (WIDGET (in), sender, msg, parm, data); 
     104 
     105    return ret; 
     106} 
     107 
     108/* --------------------------------------------------------------------------------------------- */ 
     109 
     110cb_ret_t 
     111forw_input_handle_char (WForwardingInput * in, int key) 
     112{ 
     113    cb_ret_t ret = MSG_NOT_HANDLED; 
     114    gboolean sent_to_base = FALSE; 
     115    long activity; 
     116    char *str_cp; 
     117 
     118    /* Save to detect if a change happened. */ 
     119    str_cp = g_strdup (in->base.buffer); 
     120 
     121    /* Is this key recognized by the base object? */ 
     122    activity = widget_lookup_key (WIDGET (in), key); 
     123    if (activity != CK_IgnoreKey && activity != CK_Complete) 
     124    { 
     125        /* Yes -> send the key to the upper class. */ 
     126        ret = input_callback (WIDGET (in), WIDGET (in), MSG_KEY, key, NULL); 
     127        sent_to_base = TRUE; 
     128    } 
     129    /* Should we try to forward the key to any paired widget? */ 
     130    if (in->forward_to_widget != NULL && ret == MSG_NOT_HANDLED) 
     131    { 
     132        /* Is it maybe recognized by forward_to_widget paired object? */ 
     133        activity = widget_lookup_key (WIDGET (in->forward_to_widget), key); 
     134        if (activity != CK_IgnoreKey) 
     135        { 
     136            /* Yes - forward the key to the paired widget (most probably WListbox). */ 
     137            ret = send_message (WIDGET (in->forward_to_widget), NULL, MSG_KEY, key, NULL); 
     138        } 
     139    } 
     140 
     141    /* 
     142     * If not handled yet, then send the key to the base object for general recognition (if 
     143     * not already done that). 
     144     */ 
     145 
     146    if (!sent_to_base && ret == MSG_NOT_HANDLED) 
     147    { 
     148        ret = input_callback (WIDGET (in), WIDGET (in), MSG_KEY, key, NULL); 
     149        sent_to_base = TRUE;    /* currently unused */ 
     150    } 
     151 
     152    /* Send update signal to paired widget «iff» input's text has changed. */ 
     153    if (in->forward_to_widget != NULL && g_strcmp0 (str_cp, in->base.buffer) != 0) 
     154        send_message (WIDGET (in->forward_to_widget), NULL, MSG_NOTIFY, key, in->base.buffer); 
     155    g_free (str_cp); 
     156 
     157    return ret; 
     158} 
     159 
     160/* --------------------------------------------------------------------------------------------- */ 
  • new file lib/widget/forwarding_input.h

    diff --git a/lib/widget/forwarding_input.h b/lib/widget/forwarding_input.h
    new file mode 100644
    index 000000000..dc4e49423
    - +  
     1#ifndef MC__FORWARDING_INPUT_H 
     2#define MC__FORWARDING_INPUT_H 
     3 
     4/*** typedefs(not structures) and defined constants **********************************************/ 
     5 
     6/* Casting macros. */ 
     7#define FORW_INPUT(x) ((WForwardingInput *)(x)) 
     8#define CONST_FORW_INPUT(x) ((const WForwardingInput *)(x)) 
     9 
     10typedef struct 
     11{ 
     12    WInput base; 
     13 
     14    /* Fields for new logic. */ 
     15    Widget *forward_to_widget;  /* The paired widget to receive unhandled keys */ 
     16} WForwardingInput; 
     17 
     18/*** enums ***************************************************************************************/ 
     19 
     20/*** structures declarations (and typedefs of structures)*****************************************/ 
     21 
     22/*** global variables defined in .c file *********************************************************/ 
     23 
     24/*** declarations of public functions ************************************************************/ 
     25 
     26WForwardingInput *forwarding_input_new (int y, int x, const int *colors, 
     27                                        int len, const char *text, const char *histname, 
     28                                        input_complete_t completion_flags, 
     29                                        Widget * forward_to_widget); 
     30 
     31cb_ret_t forw_input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 
     32cb_ret_t forw_input_handle_char (WForwardingInput * in, int key); 
     33 
     34/*** inline functions ****************************************************************************/ 
     35 
     36#endif /* MC__FORWARDING_INPUT_H */ 
  • lib/widget/group.c

    diff --git a/lib/widget/group.c b/lib/widget/group.c
    index f8e318bd9..f16de833f 100644
    a b group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, con 
    749749 * @param w Widget object 
    750750 */ 
    751751void 
    752 group_remove_widget (void *w) 
     752group_remove_widget (void *wid) 
    753753{ 
     754    Widget *w = WIDGET (wid); 
    754755    WGroup *g; 
    755756    GList *d; 
    756757 
    757758    /* Don't accept NULL widget. This shouldn't happen */ 
    758759    assert (w != NULL); 
    759760 
    760     g = WIDGET (w)->owner; 
     761    /* Invoke widget's pre unlink callback. */ 
     762    if (w->pre_unlink_func != NULL) 
     763        w->pre_unlink_func (w); 
     764 
     765    g = w->owner; 
    761766 
    762767    d = g_list_find (g->widgets, w); 
    763768    if (d == g->current) 
    group_remove_widget (void *w) 
    774779        group_select_current_widget (g); 
    775780    } 
    776781 
    777     WIDGET (w)->owner = NULL; 
     782    w->owner = NULL; 
    778783} 
    779784 
    780785/* --------------------------------------------------------------------------------------------- */ 
  • lib/widget/history.c

    diff --git a/lib/widget/history.c b/lib/widget/history.c
    index 775d02b1b..2ee719ca7 100644
    a b typedef struct 
    6666/*** file scope functions ************************************************************************/ 
    6767 
    6868static cb_ret_t 
    69 history_dlg_reposition (WDialog * dlg_head) 
     69history_dlg_reposition (WDialog * dlg_head, WRect * resize) 
    7070{ 
    71     history_dlg_data *data; 
    72     int x = 0, y, he, wi; 
    7371    WRect r; 
    7472 
    75     /* guard checks */ 
    76     if ((dlg_head == NULL) || (dlg_head->data == NULL)) 
    77         return MSG_NOT_HANDLED; 
    78  
    79     data = (history_dlg_data *) dlg_head->data; 
    80  
    81     y = data->y; 
    82     he = data->count + 2; 
    83  
    84     if (he <= y || y > (LINES - 6)) 
     73    if (resize == NULL) 
    8574    { 
    86         he = MIN (he, y - 1); 
    87         y -= he; 
     75        history_dlg_data *data; 
     76        int x = 0, y, he, wi; 
     77 
     78        /* guard checks */ 
     79        if ((dlg_head == NULL) || (dlg_head->data == NULL)) 
     80            return MSG_NOT_HANDLED; 
     81 
     82        data = (history_dlg_data *) dlg_head->data; 
     83 
     84        y = data->y; 
     85        he = data->count + 2; 
     86 
     87        if (he <= y || y > (LINES - 6)) 
     88        { 
     89            he = MIN (he, y - 1); 
     90            y -= he; 
     91        } 
     92        else 
     93        { 
     94            y++; 
     95            he = MIN (he, LINES - y); 
     96        } 
     97 
     98        if (data->x > 2) 
     99            x = data->x - 2; 
     100 
     101        wi = data->max_width + 4; 
     102 
     103        if ((wi + x) > COLS) 
     104        { 
     105            wi = MIN (wi, COLS); 
     106            x = COLS - wi; 
     107        } 
     108        rect_init (&r, y, x, he, wi); 
    88109    } 
    89110    else 
    90111    { 
    91         y++; 
    92         he = MIN (he, LINES - y); 
     112        /* A resize from some other code (currently from the listbox filter). */ 
     113        r = *resize; 
    93114    } 
    94115 
    95     if (data->x > 2) 
    96         x = data->x - 2; 
    97  
    98     wi = data->max_width + 4; 
    99  
    100     if ((wi + x) > COLS) 
    101     { 
    102         wi = MIN (wi, COLS); 
    103         x = COLS - wi; 
    104     } 
    105  
    106     rect_init (&r, y, x, he, wi); 
    107116 
    108117    return dlg_default_callback (WIDGET (dlg_head), NULL, MSG_RESIZE, 0, &r); 
    109118} 
    history_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, v 
    116125    switch (msg) 
    117126    { 
    118127    case MSG_RESIZE: 
    119         return history_dlg_reposition (DIALOG (w)); 
     128        return history_dlg_reposition (DIALOG (w), data); 
    120129 
    121130    case MSG_NOTIFY: 
    122131        { 
    history_create_item (history_descriptor_t * hd, void *data) 
    158167    width = str_term_width1 (text); 
    159168    hd->max_width = MAX (width, hd->max_width); 
    160169 
    161     listbox_add_item (hd->listbox, LISTBOX_APPEND_AT_END, 0, text, NULL, TRUE); 
     170    listbox_add_item (LISTBOX (hd->listbox), LISTBOX_APPEND_AT_END, 0, text, NULL, TRUE); 
    162171} 
    163172 
    164173/* --------------------------------------------------------------------------------------------- */ 
    history_descriptor_init (history_descriptor_t * hd, int y, int x, GList * histor 
    190199    hd->action = CK_IgnoreKey; 
    191200    hd->text = NULL; 
    192201    hd->max_width = 0; 
    193     hd->listbox = listbox_new (1, 1, 2, 2, TRUE, NULL); 
     202    hd->listbox = filtering_listbox_new (1, 1, 2, 2, TRUE, NULL, FILT_LIST_KEEP_DIALOG_SIZE); 
    194203    /* in most cases history list contains string only and no any other data */ 
    195204    hd->create = history_create_item; 
    196205    hd->release = history_release_item; 
    history_show (history_descriptor_t * hd) 
    205214    GList *z, *hi; 
    206215    size_t count; 
    207216    WDialog *query_dlg; 
     217    WListbox *lw; 
    208218    history_dlg_data hist_data; 
    209219    int dlg_ret; 
    210220 
    history_show (history_descriptor_t * hd) 
    217227        hd->create (hd, z->data); 
    218228    /* after this, the order of history items is following: recent at begin, oldest at end */ 
    219229 
    220     count = listbox_get_length (hd->listbox); 
     230    /* Get the WListbox pointer for convenience. */ 
     231    lw = LISTBOX (hd->listbox); 
     232    count = listbox_get_length (lw); 
    221233 
    222234    hist_data.y = hd->y; 
    223235    hist_data.x = hd->x; 
    history_show (history_descriptor_t * hd) 
    244256    { 
    245257        /* history is above base widget -- revert order to place recent item at bottom */ 
    246258        /* revert history direction */ 
    247         g_queue_reverse (hd->listbox->list); 
     259        g_queue_reverse (lw->list); 
    248260        if (hd->current < 0 || (size_t) hd->current >= count) 
    249             listbox_select_last (hd->listbox); 
     261            listbox_select_last (lw); 
    250262        else 
    251             listbox_select_entry (hd->listbox, count - 1 - (size_t) hd->current); 
     263            listbox_select_entry (lw, count - 1 - (size_t) hd->current); 
    252264    } 
    253265    else 
    254266    { 
    255267        /* history is below base widget -- keep order to place recent item on top  */ 
    256268        if (hd->current > 0) 
    257             listbox_select_entry (hd->listbox, hd->current); 
     269            listbox_select_entry (lw, hd->current); 
    258270    } 
    259271 
    260272    dlg_ret = dlg_run (query_dlg); 
    history_show (history_descriptor_t * hd) 
    274286            hd->action = CK_Enter; 
    275287        } 
    276288 
    277         listbox_get_current (hd->listbox, &q, NULL); 
    278         hd->text = g_strdup (q); 
     289        listbox_get_current (lw, &q, NULL); 
     290        /* Can still be 0 if special entry "<no search results>" will be selected. */ 
     291        if (q != NULL) 
     292            hd->text = g_strdup (q); 
    279293    } 
    280294 
     295    /* If needed, restore normal listbox state, with no backlist (list_keep). */ 
     296    filt_listbox_ensure_unfiltered_state (hd->listbox); 
     297 
    281298    /* get modified history from dialog */ 
    282299    z = NULL; 
    283     for (hi = listbox_get_first_link (hd->listbox); hi != NULL; hi = g_list_next (hi)) 
     300    for (hi = listbox_get_first_link (lw); hi != NULL; hi = g_list_next (hi)) 
    284301        /* history is being reverted here again */ 
    285302        z = g_list_prepend (z, hd->release (hd, LENTRY (hi->data))); 
    286303 
  • lib/widget/history.h

    diff --git a/lib/widget/history.h b/lib/widget/history.h
    index 9c4b403d1..23f4d403d 100644
    a b  
    1111/* forward declarations */ 
    1212struct history_descriptor_t; 
    1313struct WLEntry; 
    14 struct WListbox; 
     14typedef struct WFilteringListbox_s WFilteringListbox; 
    1515 
    1616typedef void (*history_create_item_func) (struct history_descriptor_t * hd, void *data); 
    1717typedef void *(*history_release_item_func) (struct history_descriptor_t * hd, struct WLEntry * le); 
    typedef struct history_descriptor_t 
    3030    char *text;                 /**< return text of selected item */ 
    3131 
    3232    size_t max_width;           /**< maximum width of sring in history */ 
    33     struct WListbox *listbox;   /**< listbox widget to draw history */ 
     33    WFilteringListbox *listbox;   /**< listbox widget to draw history */ 
    3434 
    3535    history_create_item_func create;    /**< function to create item of @list */ 
    3636    history_release_item_func release;  /**< function to release item of @list */ 
  • lib/widget/input.c

    diff --git a/lib/widget/input.c b/lib/widget/input.c
    index 471715c25..0069a765f 100644
    a b input_save_history (const gchar * event_group_name, const gchar * event_name, 
    871871    (void) event_group_name; 
    872872    (void) event_name; 
    873873 
    874     if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL)) 
     874    if (!in->is_password && WIDGET (in)->owner != NULL 
     875        && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL)) 
    875876    { 
    876877        ev_history_load_save_t *ev = (ev_history_load_save_t *) data; 
    877878 
    input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    970971    } 
    971972} 
    972973 
     974/* --------------------------------------------------------------------------------------------- */ 
     975/* a callback used when removing the widget from its WGroup */ 
     976 
     977static void 
     978input_unregister_history_events_cb (Widget * wid_ptr) 
     979{ 
     980    WDialog *h = DIALOG (wid_ptr->owner); 
     981 
     982    /* unsubscribe from "history_load" event */ 
     983    mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, wid_ptr); 
     984    /* unsubscribe from "history_save" event */ 
     985    mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, wid_ptr); 
     986} 
     987 
    973988/* --------------------------------------------------------------------------------------------- */ 
    974989/*** public functions ****************************************************************************/ 
    975990/* --------------------------------------------------------------------------------------------- */ 
    976991 
     992WInput * 
     993input_new (int y, int x, const int *colors, int width, const char *def_text, 
     994           const char *histname, input_complete_t completion_flags) 
     995{ 
     996    WInput *in; 
     997 
     998    in = g_new (WInput, 1); 
     999    input_init (in, y, x, colors, width, def_text, histname, completion_flags); 
     1000    return in; 
     1001} 
     1002 
     1003/* --------------------------------------------------------------------------------------------- */ 
     1004 
    9771005/** Create new instance of WInput object. 
    9781006  * @param y                    Y coordinate 
    9791007  * @param x                    X coordinate 
    input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    9841012  * @param completion_flags     Flags for specify type of completions 
    9851013  * @return                     WInput object 
    9861014  */ 
    987 WInput * 
    988 input_new (int y, int x, const int *colors, int width, const char *def_text, 
    989            const char *histname, input_complete_t completion_flags) 
     1015void 
     1016input_init (WInput * in, int y, int x, const int *colors, int width, const char *def_text, 
     1017            const char *histname, input_complete_t completion_flags) 
    9901018{ 
    991     WInput *in; 
    992     Widget *w; 
     1019    Widget *w = WIDGET (in); 
    9931020 
    994     in = g_new (WInput, 1); 
    995     w = WIDGET (in); 
    9961021    widget_init (w, y, x, 1, width, input_callback, input_mouse_callback); 
    9971022    w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR; 
    9981023    w->keymap = input_map; 
    input_new (int y, int x, const int *colors, int width, const char *def_text, 
    10281053    if ((histname != NULL) && (*histname != '\0')) 
    10291054        in->history.name = g_strdup (histname); 
    10301055    /* history will be loaded later */ 
    1031  
    10321056    in->label = NULL; 
    1033  
    1034     return in; 
    10351057} 
    10361058 
    10371059/* --------------------------------------------------------------------------------------------- */ 
    input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *d 
    10501072        mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL); 
    10511073        /* subscribe to "history_save" event */ 
    10521074        mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL); 
     1075        /* unregister (via the func) above events in case of removal from dialog */ 
     1076        w->pre_unlink_func = input_unregister_history_events_cb; 
    10531077        if (in->label != NULL) 
    10541078            widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED)); 
    10551079        return MSG_HANDLED; 
    input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *d 
    10631087            return v; 
    10641088        } 
    10651089 
    1066         /* Keys we want others to handle */ 
     1090        /* Keys we want others to handle. */ 
    10671091        if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR 
    10681092            || parm == KEY_F (10) || parm == '\n') 
    10691093            return MSG_NOT_HANDLED; 
    input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *d 
    10971121        return MSG_HANDLED; 
    10981122 
    10991123    case MSG_DESTROY: 
    1100         /* unsubscribe from "history_load" event */ 
    1101         mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w); 
    1102         /* unsubscribe from "history_save" event */ 
    1103         mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w); 
     1124        /* Only, if there is an owner WGroup. */ 
     1125        if (h != NULL) 
     1126        { 
     1127            /* unsubscribe from "history_load" event */ 
     1128            mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w); 
     1129            /* unsubscribe from "history_save" event */ 
     1130            mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w); 
     1131        } 
    11041132        input_destroy (in); 
    11051133        return MSG_HANDLED; 
    11061134 
  • lib/widget/input.h

    diff --git a/lib/widget/input.h b/lib/widget/input.h
    index a753e6160..792bb7f28 100644
    a b extern input_colors_t input_colors; 
    8383 
    8484/*** declarations of public functions ************************************************************/ 
    8585 
     86void input_init (WInput * in, int y, int x, const int *colors, int width, const char *def_text, 
     87                 const char *histname, input_complete_t completion_flags); 
    8688WInput *input_new (int y, int x, const int *colors, 
    8789                   int len, const char *text, const char *histname, 
    8890                   input_complete_t completion_flags); 
  • lib/widget/input_complete.c

    diff --git a/lib/widget/input_complete.c b/lib/widget/input_complete.c
    index c23fe6063..d4f9c423a 100644
    a b complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    10171017{ 
    10181018    static int bl = 0; 
    10191019 
    1020     WGroup *g = GROUP (w); 
    10211020    WDialog *h = DIALOG (w); 
     1021    WFilteringListbox *slw; 
     1022    cb_ret_t ret = MSG_NOT_HANDLED; 
     1023 
     1024    /* Find the listbox in dialog's group. */ 
     1025    slw = FILT_LISTBOX (WIDGET (h)->find_by_type (WIDGET (h), filt_listbox_callback)); 
    10221026 
    10231027    switch (msg) 
    10241028    { 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    10271031        { 
    10281032        case KEY_LEFT: 
    10291033        case KEY_RIGHT: 
     1034            /* MultiSearch (list filtering) is allowing for left/right movement in query input. */ 
     1035            if (widget_get_state (WIDGET (slw), WST_FILTER)) 
     1036                break; 
    10301037            bl = 0; 
    10311038            h->ret_value = 0; 
    10321039            dlg_stop (h); 
    1033             return MSG_HANDLED; 
     1040            ret = MSG_HANDLED; 
     1041            break; 
    10341042 
    10351043        case KEY_BACKSPACE: 
     1044            /* MultiSearch is exclusive with completion widening. */ 
     1045            if (widget_get_state (WIDGET (slw), WST_FILTER)) 
     1046                break; 
    10361047            bl = 0; 
    10371048            /* exit from completion list if input line is empty */ 
    10381049            if (end == 0) 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    10561067 
    10571068                new_end = str_get_prev_char (&input->buffer[end]) - input->buffer; 
    10581069 
    1059                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); 
     1070                for (i = 0, e = listbox_get_first_link (LISTBOX (slw)); 
    10601071                     e != NULL; i++, e = g_list_next (e)) 
    10611072                { 
    10621073                    WLEntry *le = LENTRY (e->data); 
    10631074 
    10641075                    if (strncmp (input->buffer + start, le->text, new_end - start) == 0) 
    10651076                    { 
    1066                         listbox_select_entry (LISTBOX (g->current->data), i); 
     1077                        listbox_select_entry (LISTBOX (slw), i); 
    10671078                        end = new_end; 
    10681079                        input_handle_char (input, parm); 
    1069                         widget_draw (WIDGET (g->current->data)); 
     1080                        widget_draw (WIDGET (slw)); 
    10701081                        break; 
    10711082                    } 
    10721083                } 
    10731084            } 
    1074             return MSG_HANDLED; 
     1085            ret = MSG_HANDLED; 
     1086            break; 
    10751087 
    10761088        default: 
    10771089            if (parm < 32 || parm > 255) 
    10781090            { 
    10791091                bl = 0; 
    1080                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete) 
    1081                     return MSG_NOT_HANDLED; 
    1082  
    1083                 if (end == min_end) 
    1084                     return MSG_HANDLED; 
    1085  
    1086                 /* This means we want to refill the list box and start again */ 
    1087                 h->ret_value = B_USER; 
    1088                 dlg_stop (h); 
     1092                /* CK_Complete -> Is completion up to date? */ 
     1093                if ((widget_lookup_key (WIDGET (input), parm) == CK_Complete) && (end != min_end)) 
     1094                { 
     1095                    /* This means we want to refill the list box and start again. */ 
     1096                    h->ret_value = B_USER; 
     1097                    dlg_stop (h); 
     1098                } 
     1099                /* 
     1100                 * else - key will be ignored by this function, so leave ret unchanged - allow other 
     1101                 * widgets to process it. 
     1102                 */ 
    10891103            } 
    1090             else 
     1104 
     1105            /* 
     1106             * Do standard live entry lookup only when not filtering list via MultiSearch – it is 
     1107             * a feature that to a great extent replaces old engine. It can be still used as MultiSearch 
     1108             * can be dynamically enabled and disabled (by default with: Alt-space and Ctrl-space). 
     1109             */ 
     1110            else if (!widget_get_state (WIDGET (slw), WST_FILTER)) 
    10911111            { 
    10921112                static char buff[MB_LEN_MAX] = ""; 
    10931113                GList *e; 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    11101130                    break; 
    11111131                } 
    11121132 
    1113                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); 
     1133                for (i = 0, e = listbox_get_first_link (LISTBOX (slw)); 
    11141134                     e != NULL; i++, e = g_list_next (e)) 
    11151135                { 
    11161136                    WLEntry *le = LENTRY (e->data); 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    11211141                        if (need_redraw == 0) 
    11221142                        { 
    11231143                            need_redraw = 1; 
    1124                             listbox_select_entry (LISTBOX (g->current->data), i); 
     1144                            listbox_select_entry (LISTBOX (slw), i); 
    11251145                            last_text = le->text; 
    11261146                        } 
    11271147                        else 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    11721192                if (need_redraw == 2) 
    11731193                { 
    11741194                    insert_text (input, last_text, low); 
    1175                     widget_draw (WIDGET (g->current->data)); 
     1195                    widget_draw (WIDGET (slw)); 
    11761196                } 
    11771197                else if (need_redraw == 1) 
    11781198                { 
    complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void 
    11801200                    dlg_stop (h); 
    11811201                } 
    11821202                bl = 0; 
     1203                ret = MSG_HANDLED; 
    11831204            } 
    11841205        } 
    1185         return MSG_HANDLED; 
     1206        break; 
    11861207 
    11871208    default: 
    11881209        return dlg_default_callback (w, sender, msg, parm, data); 
    11891210    } 
     1211    return ret; 
    11901212} 
    11911213 
    11921214/* --------------------------------------------------------------------------------------------- */ 
    complete_engine (WInput * in, int what_to_do) 
    12231245            int start_x, start_y; 
    12241246            char **p, *q; 
    12251247            WDialog *complete_dlg; 
    1226             WListbox *complete_list; 
     1248            WFilteringListbox *complete_list; 
    12271249 
    12281250            for (p = in->completions + 1; *p != NULL; count++, p++) 
    12291251            { 
    complete_engine (WInput * in, int what_to_do) 
    12661288            complete_dlg = 
    12671289                dlg_create (TRUE, y, x, complete_height, complete_width, WPOS_KEEP_DEFAULT, TRUE, 
    12681290                            dialog_colors, complete_callback, NULL, "[Completion]", NULL); 
    1269             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL); 
     1291            complete_list = filtering_listbox_new (1, 1, h - 2, w - 2, FALSE, NULL, 
     1292                                                   FILT_LIST_KEEP_DIALOG_SIZE); 
    12701293            group_add_widget (GROUP (complete_dlg), complete_list); 
    12711294 
    12721295            for (p = in->completions + 1; *p != NULL; p++) 
    1273                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE); 
     1296                listbox_add_item (LISTBOX (complete_list), LISTBOX_APPEND_AT_END, 0, *p, NULL, 
     1297                                  FALSE); 
    12741298 
    12751299            i = dlg_run (complete_dlg); 
    12761300            q = NULL; 
    12771301            if (i == B_ENTER) 
    12781302            { 
    1279                 listbox_get_current (complete_list, &q, NULL); 
     1303                listbox_get_current (LISTBOX (complete_list), &q, NULL); 
    12801304                if (q != NULL) 
    12811305                    insert_text (in, q, strlen (q)); 
    12821306            } 
    12831307            if (q != NULL || end != min_end) 
    12841308                input_complete_free (in); 
     1309 
    12851310            dlg_destroy (complete_dlg); 
    12861311 
    12871312            /* B_USER if user wants to start over again */ 
  • lib/widget/listbox-window.c

    diff --git a/lib/widget/listbox-window.c b/lib/widget/listbox-window.c
    index 44548b485..447ba1328 100644
    a b create_listbox_window_centered (int center_y, int center_x, int lines, int cols, 
    108108        dlg_create (TRUE, ypos, xpos, lines + space, cols + space, pos_flags, FALSE, listbox_colors, 
    109109                    NULL, NULL, help, title); 
    110110 
    111     listbox->list = listbox_new (2, 2, lines, cols, FALSE, NULL); 
    112     group_add_widget (GROUP (listbox->dlg), listbox->list); 
     111    listbox->list = 
     112        filtering_listbox_new (2, 2, lines, cols, FALSE, NULL, FILT_LIST_DIALOG_AUTO_RESIZE); 
     113    group_add_widget (GROUP (listbox->dlg), WIDGET (listbox->list)); 
    113114 
    114115    return listbox; 
    115116} 
    create_listbox_window (int lines, int cols, const char *title, const char *help) 
    128129int 
    129130run_listbox (Listbox * l) 
    130131{ 
     132    WListbox *lw = LISTBOX (l->list); 
    131133    int val = -1; 
    132134 
    133135    if (dlg_run (l->dlg) != B_CANCEL) 
    134         val = l->list->pos; 
     136    { 
     137        /* Get the virtual index first, to support filtered listboxes. */ 
     138        val = lw->virtual_pos; 
     139 
     140        /* No virtual position -> get pos. */ 
     141        if (val == -1) 
     142            val = lw->pos; 
     143    } 
     144 
    135145    dlg_destroy (l->dlg); 
    136146    g_free (l); 
    137147    return val; 
    run_listbox (Listbox * l) 
    149159void * 
    150160run_listbox_with_data (Listbox * l, const void *select) 
    151161{ 
     162    WListbox *lw = LISTBOX (l->list); 
    152163    void *val = NULL; 
    153164 
    154165    if (select != NULL) 
    155         listbox_select_entry (l->list, listbox_search_data (l->list, select)); 
     166        listbox_select_entry (lw, listbox_search_data (lw, select)); 
    156167 
    157168    if (dlg_run (l->dlg) != B_CANCEL) 
    158169    { 
    159170        WLEntry *e; 
    160         e = listbox_get_nth_item (l->list, l->list->pos); 
    161         if (e != NULL) 
     171        e = listbox_get_nth_item (lw, lw->pos); 
     172        /* -2 means that "<no search results>" has been selected. */ 
     173        if (e != NULL && e->index != -2) 
    162174        { 
    163175            /* The assert guards against returning a soon-to-be deallocated 
    164176             * pointer (as in listbox_add_item(..., TRUE)). */ 
  • lib/widget/listbox-window.h

    diff --git a/lib/widget/listbox-window.h b/lib/widget/listbox-window.h
    index 5a6082956..dc20cee13 100644
    a b  
    88/*** typedefs(not structures) and defined constants **********************************************/ 
    99 
    1010#define LISTBOX_APPEND_TEXT(l,h,t,d,f) \ 
    11     listbox_add_item (l->list, LISTBOX_APPEND_AT_END, h, t, d, f) 
     11    listbox_add_item (LISTBOX(l->list), LISTBOX_APPEND_AT_END, h, t, d, f) 
    1212 
    1313/*** enums ***************************************************************************************/ 
    1414 
     
    1717typedef struct 
    1818{ 
    1919    WDialog *dlg; 
    20     WListbox *list; 
     20    WFilteringListbox *list; 
    2121} Listbox; 
    2222 
    2323/*** global variables defined in .c file *********************************************************/ 
  • lib/widget/listbox.c

    diff --git a/lib/widget/listbox.c b/lib/widget/listbox.c
    index e20c1a82d..a284cc2e4 100644
    a b listbox_entry_free (void *data) 
    7777{ 
    7878    WLEntry *e = data; 
    7979 
    80     g_free (e->text); 
     80    if (e->free_text) 
     81        g_free (e->text); 
    8182    if (e->free_data) 
    8283        g_free (e->data); 
    8384    g_free (e); 
    listbox_key (WListbox * l, int key) 
    358359 
    359360/* --------------------------------------------------------------------------------------------- */ 
    360361 
     362/* When called via g_queue_foreach it assigns the index field with an incremented int. */ 
     363static void 
     364listbox_foreach_apply_index (gpointer data, gpointer user_data) 
     365{ 
     366    WLEntry *e = data; 
     367    int *cur_idx = user_data; 
     368 
     369    /* Set the index and increment it. */ 
     370    e->index = *cur_idx; 
     371    *cur_idx = *cur_idx + 1; 
     372} 
     373 
     374/* --------------------------------------------------------------------------------------------- */ 
     375 
    361376/* Listbox item adding function */ 
    362377static inline void 
    363378listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos) 
    listbox_destroy (WListbox * l) 
    445460 
    446461/* --------------------------------------------------------------------------------------------- */ 
    447462 
    448 static cb_ret_t 
     463cb_ret_t 
    449464listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
    450465{ 
    451466    WListbox *l = LISTBOX (w); 
    listbox_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    544559/*** public functions ****************************************************************************/ 
    545560/* --------------------------------------------------------------------------------------------- */ 
    546561 
    547 WListbox * 
    548 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback) 
     562void 
     563listbox_init (WListbox * l, int y, int x, int height, int width, gboolean deletable, 
     564              lcback_fn callback) 
    549565{ 
    550     WListbox *l; 
    551     Widget *w; 
     566    Widget *w = WIDGET (l); 
    552567 
    553568    if (height <= 0) 
    554569        height = 1; 
    555570 
    556     l = g_new (WListbox, 1); 
    557     w = WIDGET (l); 
    558571    widget_init (w, y, x, height, width, listbox_callback, listbox_mouse_callback); 
    559     w->options |= WOP_SELECTABLE | WOP_WANT_HOTKEY; 
     572    w->options |= WOP_SELECTABLE; 
    560573    w->keymap = listbox_map; 
    561574 
    562575    l->list = NULL; 
    563     l->top = l->pos = 0; 
     576    l->top = l->pos = l->virtual_pos = 0; 
    564577    l->deletable = deletable; 
    565578    l->callback = callback; 
    566579    l->allow_duplicates = TRUE; 
    567580    l->scrollbar = !mc_global.tty.slow_terminal; 
     581} 
    568582 
     583/* --------------------------------------------------------------------------------------------- */ 
     584 
     585WListbox * 
     586listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback) 
     587{ 
     588    WListbox *l; 
     589 
     590    l = g_new (WListbox, 1); 
     591    listbox_init (l, y, x, height, width, deletable, callback); 
    569592    return l; 
    570593} 
    571594 
    listbox_select_last (WListbox * l) 
    648671void 
    649672listbox_select_entry (WListbox * l, int dest) 
    650673{ 
     674    WLEntry *e; 
    651675    GList *le; 
    652676    int pos; 
    653677    gboolean top_seen = FALSE; 
    654678 
    655679    if (listbox_is_empty (l) || dest < 0) 
     680    { 
     681        /* Reset position to a minimal one. */ 
     682        l->pos = 0; 
     683        l->virtual_pos = 0; 
    656684        return; 
     685    } 
    657686 
    658687    /* Special case */ 
    659688    for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le)) 
    listbox_select_entry (WListbox * l, int dest) 
    673702                if (l->pos - l->top >= lines) 
    674703                    l->top = l->pos - lines + 1; 
    675704            } 
     705            /* 
     706             * Set the virtual position, i.e.: a position in the initial, unfiltered list if the 
     707             * same element would be selected. 
     708             */ 
     709            e = LENTRY (le->data); 
     710            l->virtual_pos = e->index; 
    676711            return; 
    677712        } 
    678713    } 
    listbox_get_current (WListbox * l, char **string, void **extra) 
    701736    if (l != NULL) 
    702737        e = listbox_get_nth_item (l, l->pos); 
    703738 
    704     ok = (e != NULL); 
     739    ok = (e != NULL && e->index != -2); 
    705740 
    706741    if (string != NULL) 
    707742        *string = ok ? e->text : NULL; 
    listbox_get_nth_item (const WListbox * l, int pos) 
    720755        GList *item; 
    721756 
    722757        item = g_queue_peek_nth_link (l->list, (guint) pos); 
    723         if (item != NULL) 
     758        if (item != NULL && LENTRY (item->data)->index != -2) 
    724759            return LENTRY (item->data); 
    725760    } 
    726761 
    listbox_remove_list (WListbox * l) 
    802837 
    803838/* --------------------------------------------------------------------------------------------- */ 
    804839 
     840/* 
     841 * Initializes the listbox elements with their position index. This allows to alter (filter, in 
     842 * particular) the listbox elements order and still get the original index (when selecting an 
     843 * element). 
     844 */ 
     845void 
     846listbox_init_indices (WListbox * l) 
     847{ 
     848    int index = 0; 
     849    g_queue_foreach (l->list, listbox_foreach_apply_index, &index); 
     850} 
     851 
     852/* --------------------------------------------------------------------------------------------- */ 
     853 
    805854char * 
    806855listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data, 
    807856                  gboolean free_data) 
    listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *te 
    815864        return NULL; 
    816865 
    817866    entry = g_new (WLEntry, 1); 
     867    entry->index = -1;          /* Will be initialized when switching to the filter state */ 
    818868    entry->text = g_strdup (text); 
     869    entry->free_text = 1; 
    819870    entry->data = data; 
    820871    entry->free_data = free_data; 
    821872    entry->hotkey = hotkey; 
  • lib/widget/listbox.h

    diff --git a/lib/widget/listbox.h b/lib/widget/listbox.h
    index 8b2236eff..263d2c06e 100644
    a b typedef lcback_ret_t (*lcback_fn) (struct WListbox * l); 
    3535 
    3636typedef struct WLEntry 
    3737{ 
     38    int index;                  /* The location in the list (used when it's filtered) */ 
    3839    char *text;                 /* Text to display */ 
     40    gboolean free_text;         /* Whether to free the text on entry's removal */ 
    3941    int hotkey; 
    4042    void *data;                 /* Client information */ 
    4143    gboolean free_data;         /* Whether to free the data on entry's removal */ 
    typedef struct WListbox 
    4648    Widget widget; 
    4749    GQueue *list;               /* Pointer to the list of WLEntry */ 
    4850    int pos;                    /* The current element displayed */ 
     51    int virtual_pos;            /* The initial index of the current element, works also for filtered listbox */ 
    4952    int top;                    /* The first element displayed */ 
    5053    gboolean allow_duplicates;  /* Do we allow duplicates on the list? */ 
    5154    gboolean scrollbar;         /* Draw a scrollbar? */ 
    extern const global_keymap_t *listbox_map; 
    6063 
    6164/*** declarations of public functions ************************************************************/ 
    6265 
     66void listbox_init (WListbox * l, int y, int x, int height, int width, gboolean deletable, 
     67                   lcback_fn callback); 
    6368WListbox *listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback); 
     69cb_ret_t listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 
     70 
    6471int listbox_search_text (WListbox * l, const char *text); 
    6572int listbox_search_data (WListbox * l, const void *data); 
    6673void listbox_select_first (WListbox * l); 
    void listbox_remove_current (WListbox * l); 
    7481gboolean listbox_is_empty (const WListbox * l); 
    7582void listbox_set_list (WListbox * l, GQueue * list); 
    7683void listbox_remove_list (WListbox * l); 
     84void listbox_init_indices (WListbox * l); 
    7785char *listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, 
    7886                        void *data, gboolean free_data); 
    7987 
  • lib/widget/widget-common.c

    diff --git a/lib/widget/widget-common.c b/lib/widget/widget-common.c
    index e2fcd1222..8dd4a0d30 100644
    a b widget_init (Widget * w, int y, int x, int lines, int cols, 
    338338    w->find_by_type = widget_default_find_by_type; 
    339339    w->find_by_id = widget_default_find_by_id; 
    340340 
     341    w->pre_unlink_func = NULL; 
     342 
    341343    w->set_state = widget_default_set_state; 
    342344    w->get_colors = widget_default_get_colors; 
    343345} 
  • lib/widget/widget-common.h

    diff --git a/lib/widget/widget-common.h b/lib/widget/widget-common.h
    index a20bcd671..28abfba39 100644
    a b typedef enum 
    4242    MSG_POST_KEY,               /* The key has been handled */ 
    4343    MSG_ACTION,                 /* Send to widget to handle command */ 
    4444    MSG_NOTIFY,                 /* Typically sent to dialog to inform it of state-change 
    45                                  * of listboxes, check- and radiobuttons. */ 
     45                                 * of listboxes, check- and radiobuttons. Also from input 
     46                                 * to paired listbox when query of MultiSearch changes. */ 
    4647    MSG_CURSOR,                 /* Sent to widget to position the cursor */ 
    4748    MSG_IDLE,                   /* The idle state is active */ 
    4849    MSG_RESIZE,                 /* Screen size has changed */ 
    typedef enum 
    8889    WST_CONSTRUCT = (1 << 15),  /* Widget has been constructed but not run yet */ 
    8990    WST_ACTIVE = (1 << 16),     /* Dialog is visible and active */ 
    9091    WST_SUSPENDED = (1 << 17),  /* Dialog is suspended */ 
    91     WST_CLOSED = (1 << 18)      /* Dialog is closed */ 
     92    WST_CLOSED = (1 << 18),     /* Dialog is closed */ 
     93 
     94    WST_FILTER = (1 << 19)      /* Listbox is filtering its contents */ 
    9295} widget_state_t; 
    9396 
    9497/* Flags for widget repositioning on dialog resize */ 
    typedef cb_ret_t (*widget_cb_fn) (Widget * widget, Widget * sender, widget_msg_t 
    125128typedef void (*widget_mouse_cb_fn) (Widget * w, mouse_msg_t msg, mouse_event_t * event); 
    126129/* translate mouse event and process it */ 
    127130typedef int (*widget_mouse_handle_fn) (Widget * w, Gpm_Event * event); 
     131/* Pre unlinking (removing from a WGroup) callback. */ 
     132typedef void (*pre_widget_unlink_cb) (Widget * w); 
    128133 
    129134/* Every Widget must have this as its first element */ 
    130135struct Widget 
    struct Widget 
    165170    cb_ret_t (*set_state) (Widget * w, widget_state_t state, gboolean enable); 
    166171    /* *INDENT-ON* */ 
    167172 
     173    pre_widget_unlink_cb pre_unlink_func;       /* a function invoked right before removing from a WGroup by 
     174                                                 * group_remove_widget; it can unregister any events, which 
     175                                                 * is an operation that depends on the `event_group` field of 
     176                                                 * the group, so it cannot be done «after» widget has been 
     177                                                 * removed from it (i.e.: when w.owner has been set to 0) */ 
     178 
    168179    const int *(*get_colors) (const Widget * w); 
    169180}; 
    170181 
  • src/editor/choosesyntax.c

    diff --git a/src/editor/choosesyntax.c b/src/editor/choosesyntax.c
    index 09b6d75d5..71ec44e12 100644
    a b exec_edit_syntax_dialog (const GPtrArray * names, const char *current_syntax) 
    8484        name = g_ptr_array_index (names, i); 
    8585        LISTBOX_APPEND_TEXT (syntaxlist, 0, name, NULL, FALSE); 
    8686        if (current_syntax != NULL && strcmp (name, current_syntax) == 0) 
    87             listbox_select_entry (syntaxlist->list, i + N_DFLT_ENTRIES); 
     87            listbox_select_entry (LISTBOX (syntaxlist->list), i + N_DFLT_ENTRIES); 
    8888    } 
    8989 
    9090    return run_listbox (syntaxlist); 
  • src/editor/editcmd_dialogs.c

    diff --git a/src/editor/editcmd_dialogs.c b/src/editor/editcmd_dialogs.c
    index 8b3634f23..2bfb71900 100644
    a b editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** comp 
    351351    int start_x, start_y, offset, i; 
    352352    char *curr = NULL; 
    353353    WDialog *compl_dlg; 
    354     WListbox *compl_list; 
     354    WFilteringListbox *compl_list; 
     355    WListbox *lw; 
    355356    int compl_dlg_h;            /* completion dialog height */ 
    356357    int compl_dlg_w;            /* completion dialog width */ 
    357358 
    editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** comp 
    384385                    dialog_colors, NULL, NULL, "[Completion]", NULL); 
    385386 
    386387    /* create the listbox */ 
    387     compl_list = listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, FALSE, NULL); 
     388    compl_list = filtering_listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, FALSE, NULL, 
     389                                        FILT_LIST_DIALOG_AUTO_RESIZE); 
     390 
     391    /* Save WListbox pointer for convenience. */ 
     392    lw = LISTBOX (compl_list); 
    388393 
    389394    /* add the dialog */ 
    390395    group_add_widget (GROUP (compl_dlg), compl_list); 
    391396 
    392397    /* fill the listbox with the completions */ 
    393398    for (i = num_compl - 1; i >= 0; i--)        /* reverse order */ 
    394         listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, (char *) compl[i]->str, NULL, 
    395                           FALSE); 
     399        listbox_add_item (lw, LISTBOX_APPEND_AT_END, 0, (char *) compl[i]->str, NULL, FALSE); 
    396400 
    397401    /* pop up the dialog and apply the chosen completion */ 
    398402    if (dlg_run (compl_dlg) == B_ENTER) 
    399403    { 
    400         listbox_get_current (compl_list, &curr, NULL); 
     404        listbox_get_current (lw, &curr, NULL); 
    401405        curr = g_strdup (curr); 
    402406    } 
    403407 
  • src/editor/editwidget.c

    diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c
    index 18ac00e66..f1989114a 100644
    a b edit_window_list (const WDialog * h) 
    302302    Listbox *listbox; 
    303303    GList *w; 
    304304    WEdit *selected; 
     305    WListbox *lw; 
    305306    int i = 0; 
    306307 
    307308    lines = MIN ((size_t) (LINES * 2 / 3), dlg_num); 
    edit_window_list (const WDialog * h) 
    309310 
    310311    listbox = create_listbox_window (lines, cols, _("Open files"), "[Open files]"); 
    311312 
     313    /* Convenience variable. */ 
     314    lw = LISTBOX (listbox->list); 
     315 
    312316    for (w = g->widgets; w != NULL; w = g_list_next (w)) 
    313317        if (edit_widget_is_editor (CONST_WIDGET (w->data))) 
    314318        { 
    edit_window_list (const WDialog * h) 
    322326                    g_strdup_printf ("%c%s", e->modified ? '*' : ' ', 
    323327                                     vfs_path_as_str (e->filename_vpath)); 
    324328 
    325             listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++), 
    326                               str_term_trim (fname, WIDGET (listbox->list)->cols - 2), e, FALSE); 
     329            listbox_add_item (lw, LISTBOX_APPEND_AT_END, get_hotkey (i++), 
     330                              str_term_trim (fname, WIDGET (lw)->cols - 2), e, FALSE); 
    327331            g_free (fname); 
    328332        } 
    329333 
  • src/file_history.c

    diff --git a/src/file_history.c b/src/file_history.c
    index 4304655aa..e5751ab8a 100644
    a b file_history_create_item (history_descriptor_t * hd, void *data) 
    155155    width = str_term_width1 (fhd->file_name); 
    156156    hd->max_width = MAX (width, hd->max_width); 
    157157 
    158     listbox_add_item (hd->listbox, LISTBOX_APPEND_AT_END, 0, fhd->file_name, fhd->file_pos, TRUE); 
     158    listbox_add_item (LISTBOX (hd->listbox), LISTBOX_APPEND_AT_END, 0, fhd->file_name, 
     159                      fhd->file_pos, TRUE); 
    159160    /* fhd->file_pos is not copied, NULLize it to prevent double free */ 
    160161    fhd->file_pos = NULL; 
    161162} 
  • src/keybind-defaults.c

    diff --git a/src/keybind-defaults.c b/src/keybind-defaults.c
    index 7b87c2f5a..2972e5bbf 100644
    a b static const global_keymap_ini_t default_listbox_keymap[] = { 
    293293    {"Bottom", "end; alt-gt; c1"}, 
    294294    {"PageUp", "pgup; alt-v"}, 
    295295    {"PageDown", "pgdn; ctrl-v"}, 
    296     {"Delete", "delete; d"}, 
    297     {"Clear", "shift-delete; shift-d"}, 
     296    {"Delete", "delete"}, 
     297    {"Clear", "shift-delete"}, 
    298298    {"View", "f3"}, 
    299299    {"Edit", "f4"}, 
     300    {"MultiSearch", "alt-space; ctrl-space"}, 
    300301    {"Enter", "enter"}, 
    301302    {NULL, NULL} 
    302303}; 
  • src/selcodepage.c

    diff --git a/src/selcodepage.c b/src/selcodepage.c
    index eba9d25bb..9ce48325b 100644
    a b select_charset (int center_y, int center_x, int current_charset, gboolean seldis 
    107107        ? ((current_charset < 0) ? codepages->len : (size_t) current_charset) 
    108108        : ((size_t) current_charset + 1); 
    109109 
    110     listbox_select_entry (listbox->list, i); 
     110    listbox_select_entry (LISTBOX (listbox->list), i); 
    111111 
    112112    listbox_result = run_listbox (listbox); 
    113113 
  • src/usermenu.c

    diff --git a/src/usermenu.c b/src/usermenu.c
    index 1fecbeaac..ba10634ab 100644
    a b user_menu_cmd (const WEdit * edit_widget, const char *menu_file, int selected_en 
    11221122                                     extract_line (p, p + MAX_ENTRY_LEN), p, FALSE); 
    11231123            } 
    11241124            /* Select the default entry */ 
    1125             listbox_select_entry (listbox->list, selected); 
     1125            listbox_select_entry (LISTBOX (listbox->list), selected); 
    11261126 
    11271127            selected = run_listbox (listbox); 
    11281128        }