2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
11 Andrej Borsenkow, 1996
13 Andrew Borodin <aborodin@vmail.ru>, 2009-2022
15 This file is part of the Midnight Commander.
17 The Midnight Commander is free software: you can redistribute it
18 and/or modify it under the terms of the GNU General Public License as
19 published by the Free Software Foundation, either version 3 of the License,
20 or (at your option) any later version.
22 The Midnight Commander is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program. If not, see <http://www.gnu.org/licenses/>.
32 * \brief Source: WListbox widget
39 #include "lib/global.h"
41 #include "lib/tty/tty.h"
43 #include "lib/strutil.h"
44 #include "lib/util.h" /* Q_() */
45 #include "lib/widget.h"
47 /*** global variables ****************************************************************************/
49 const global_keymap_t
*listbox_map
= NULL
;
51 /*** file scope macro definitions ****************************************************************/
53 /* Gives the position of the last item. */
54 #define LISTBOX_LAST(l) (listbox_is_empty (l) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
56 /*** file scope type declarations ****************************************************************/
58 /*** forward declarations (file scope functions) *************************************************/
60 /*** file scope variables ************************************************************************/
62 /* --------------------------------------------------------------------------------------------- */
63 /*** file scope functions ************************************************************************/
64 /* --------------------------------------------------------------------------------------------- */
67 listbox_entry_cmp (const void *a
, const void *b
, void *user_data
)
69 const WLEntry
*ea
= (const WLEntry
*) a
;
70 const WLEntry
*eb
= (const WLEntry
*) b
;
74 return strcmp (ea
->text
, eb
->text
);
77 /* --------------------------------------------------------------------------------------------- */
80 listbox_entry_free (void *data
)
90 /* --------------------------------------------------------------------------------------------- */
93 listbox_drawscroll (const WListbox
*l
)
95 const WRect
*w
= &CONST_WIDGET (l
)->rect
;
96 int max_line
= w
->lines
- 1;
101 /* Are we at the top? */
102 widget_gotoyx (l
, 0, w
->cols
);
104 tty_print_one_vline (TRUE
);
106 tty_print_char ('^');
108 length
= g_queue_get_length (l
->list
);
110 /* Are we at the bottom? */
111 widget_gotoyx (w
, max_line
, w
->cols
);
112 if (l
->top
+ w
->lines
== length
|| w
->lines
>= length
)
113 tty_print_one_vline (TRUE
);
115 tty_print_char ('v');
117 /* Now draw the nice relative pointer */
118 if (!g_queue_is_empty (l
->list
))
119 line
= 1 + ((l
->current
* (w
->lines
- 2)) / length
);
121 for (i
= 1; i
< max_line
; i
++)
123 widget_gotoyx (l
, i
, w
->cols
);
125 tty_print_one_vline (TRUE
);
127 tty_print_char ('*');
131 /* --------------------------------------------------------------------------------------------- */
134 listbox_draw (WListbox
*l
, gboolean focused
)
136 Widget
*wl
= WIDGET (l
);
137 const WRect
*w
= &CONST_WIDGET (l
)->rect
;
147 colors
= widget_get_colors (wl
);
149 disabled
= widget_get_state (wl
, WST_DISABLED
);
150 normalc
= disabled
? DISABLED_COLOR
: colors
[DLG_COLOR_NORMAL
];
151 selc
= disabled
? DISABLED_COLOR
: colors
[focused
? DLG_COLOR_HOT_FOCUS
: DLG_COLOR_FOCUS
];
155 length
= g_queue_get_length (l
->list
);
156 le
= g_queue_peek_nth_link (l
->list
, (guint
) l
->top
);
159 /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
160 pos
= (le
== NULL
) ? 0 : l
->top
;
162 for (i
= 0; i
< w
->lines
; i
++)
164 const char *text
= "";
166 /* Display the entry */
167 if (pos
== l
->current
&& sel_line
== -1)
173 tty_setcolor (normalc
);
175 widget_gotoyx (l
, i
, 1);
177 if (l
->list
!= NULL
&& le
!= NULL
&& (i
== 0 || pos
< length
))
179 WLEntry
*e
= LENTRY (le
->data
);
182 le
= g_list_next (le
);
186 tty_print_string (str_fit_to_term (text
, w
->cols
- 2, J_LEFT_FIT
));
189 l
->cursor_y
= sel_line
;
191 if (l
->scrollbar
&& length
> w
->lines
)
193 tty_setcolor (normalc
);
194 listbox_drawscroll (l
);
198 /* --------------------------------------------------------------------------------------------- */
201 listbox_check_hotkey (WListbox
*l
, int key
)
203 if (!listbox_is_empty (l
))
208 for (i
= 0, le
= g_queue_peek_head_link (l
->list
); le
!= NULL
; i
++, le
= g_list_next (le
))
210 WLEntry
*e
= LENTRY (le
->data
);
212 if (e
->hotkey
== key
)
220 /* --------------------------------------------------------------------------------------------- */
222 /* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
224 listbox_y_pos (WListbox
*l
, int y
)
226 return MIN (l
->top
+ y
, LISTBOX_LAST (l
));
229 /* --------------------------------------------------------------------------------------------- */
232 listbox_fwd (WListbox
*l
, gboolean wrap
)
234 if (!listbox_is_empty (l
))
236 if ((guint
) l
->current
+ 1 < g_queue_get_length (l
->list
))
237 listbox_set_current (l
, l
->current
+ 1);
239 listbox_select_first (l
);
243 /* --------------------------------------------------------------------------------------------- */
246 listbox_fwd_n (WListbox
*l
, int n
)
248 listbox_set_current (l
, MIN (l
->current
+ n
, LISTBOX_LAST (l
)));
251 /* --------------------------------------------------------------------------------------------- */
254 listbox_back (WListbox
*l
, gboolean wrap
)
256 if (!listbox_is_empty (l
))
259 listbox_set_current (l
, l
->current
- 1);
261 listbox_select_last (l
);
265 /* --------------------------------------------------------------------------------------------- */
268 listbox_back_n (WListbox
*l
, int n
)
270 listbox_set_current (l
, MAX (l
->current
- n
, 0));
273 /* --------------------------------------------------------------------------------------------- */
276 listbox_execute_cmd (WListbox
*l
, long command
)
278 cb_ret_t ret
= MSG_HANDLED
;
279 const WRect
*w
= &CONST_WIDGET (l
)->rect
;
281 if (l
->list
== NULL
|| g_queue_is_empty (l
->list
))
282 return MSG_NOT_HANDLED
;
287 listbox_back (l
, TRUE
);
290 listbox_fwd (l
, TRUE
);
293 listbox_select_first (l
);
296 listbox_select_last (l
);
299 listbox_back_n (l
, w
->lines
- 1);
302 listbox_fwd_n (l
, w
->lines
- 1);
307 gboolean is_last
, is_more
;
310 length
= g_queue_get_length (l
->list
);
312 is_last
= (l
->current
+ 1 >= length
);
313 is_more
= (l
->top
+ w
->lines
>= length
);
315 listbox_remove_current (l
);
316 if ((l
->top
> 0) && (is_last
|| is_more
))
321 if (l
->deletable
&& mc_global
.widget
.confirm_history_cleanup
322 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
323 && (query_dialog (Q_ ("DialogTitle|History cleanup"),
324 _("Do you want clean this history?"),
325 D_ERROR
, 2, _("&Yes"), _("&No")) == 0))
326 listbox_remove_list (l
);
331 ret
= send_message (WIDGET (l
)->owner
, l
, MSG_NOTIFY
, command
, NULL
);
334 ret
= MSG_NOT_HANDLED
;
340 /* --------------------------------------------------------------------------------------------- */
342 /* Return MSG_HANDLED if we want a redraw */
344 listbox_key (WListbox
*l
, int key
)
349 return MSG_NOT_HANDLED
;
351 /* focus on listbox item N by '0'..'9' keys */
352 if (key
>= '0' && key
<= '9')
354 listbox_set_current (l
, key
- '0');
358 command
= widget_lookup_key (WIDGET (l
), key
);
359 if (command
== CK_IgnoreKey
)
360 return MSG_NOT_HANDLED
;
361 return listbox_execute_cmd (l
, command
);
364 /* --------------------------------------------------------------------------------------------- */
366 /* Listbox item adding function */
368 listbox_add_entry (WListbox
*l
, WLEntry
*e
, listbox_append_t pos
)
372 l
->list
= g_queue_new ();
373 pos
= LISTBOX_APPEND_AT_END
;
378 case LISTBOX_APPEND_AT_END
:
379 g_queue_push_tail (l
->list
, e
);
382 case LISTBOX_APPEND_BEFORE
:
383 g_queue_insert_before (l
->list
, g_queue_peek_nth_link (l
->list
, (guint
) l
->current
), e
);
386 case LISTBOX_APPEND_AFTER
:
387 g_queue_insert_after (l
->list
, g_queue_peek_nth_link (l
->list
, (guint
) l
->current
), e
);
390 case LISTBOX_APPEND_SORTED
:
391 g_queue_insert_sorted (l
->list
, e
, (GCompareDataFunc
) listbox_entry_cmp
, NULL
);
399 /* --------------------------------------------------------------------------------------------- */
401 /* Call this whenever the user changes the selected item. */
403 listbox_on_change (WListbox
*l
)
405 listbox_draw (l
, TRUE
);
406 send_message (WIDGET (l
)->owner
, l
, MSG_NOTIFY
, 0, NULL
);
409 /* --------------------------------------------------------------------------------------------- */
412 listbox_do_action (WListbox
*l
)
416 if (listbox_is_empty (l
))
419 if (l
->callback
!= NULL
)
420 action
= l
->callback (l
);
422 action
= LISTBOX_DONE
;
424 if (action
== LISTBOX_DONE
)
426 WDialog
*h
= DIALOG (WIDGET (l
)->owner
);
428 h
->ret_value
= B_ENTER
;
433 /* --------------------------------------------------------------------------------------------- */
436 listbox_run_hotkey (WListbox
*l
, int pos
)
438 listbox_set_current (l
, pos
);
439 listbox_on_change (l
);
440 listbox_do_action (l
);
443 /* --------------------------------------------------------------------------------------------- */
446 listbox_destroy (WListbox
*l
)
448 listbox_remove_list (l
);
451 /* --------------------------------------------------------------------------------------------- */
454 listbox_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
456 WListbox
*l
= LISTBOX (w
);
464 pos
= listbox_check_hotkey (l
, parm
);
466 return MSG_NOT_HANDLED
;
468 listbox_run_hotkey (l
, pos
);
477 ret_code
= listbox_key (l
, parm
);
478 if (ret_code
!= MSG_NOT_HANDLED
)
479 listbox_on_change (l
);
484 return listbox_execute_cmd (l
, parm
);
487 widget_gotoyx (l
, l
->cursor_y
, 0);
491 listbox_draw (l
, widget_get_state (w
, WST_FOCUSED
));
499 return widget_default_callback (w
, sender
, msg
, parm
, data
);
503 /* --------------------------------------------------------------------------------------------- */
506 listbox_mouse_callback (Widget
*w
, mouse_msg_t msg
, mouse_event_t
*event
)
508 WListbox
*l
= LISTBOX (w
);
511 old_current
= l
->current
;
517 listbox_set_current (l
, listbox_y_pos (l
, event
->y
));
520 case MSG_MOUSE_SCROLL_UP
:
521 listbox_back (l
, FALSE
);
524 case MSG_MOUSE_SCROLL_DOWN
:
525 listbox_fwd (l
, FALSE
);
529 event
->result
.repeat
= TRUE
; /* It'd be functional even without this. */
530 listbox_set_current (l
, listbox_y_pos (l
, event
->y
));
533 case MSG_MOUSE_CLICK
:
534 /* We don't call listbox_set_current() here: MSG_MOUSE_DOWN/DRAG did this already. */
535 if (event
->count
== GPM_DOUBLE
) /* Double click */
536 listbox_do_action (l
);
543 /* If the selection has changed, we redraw the widget and notify the dialog. */
544 if (l
->current
!= old_current
)
545 listbox_on_change (l
);
548 /* --------------------------------------------------------------------------------------------- */
549 /*** public functions ****************************************************************************/
550 /* --------------------------------------------------------------------------------------------- */
553 listbox_new (int y
, int x
, int height
, int width
, gboolean deletable
, lcback_fn callback
)
555 WRect r
= { y
, x
, 1, width
};
559 l
= g_new (WListbox
, 1);
561 r
.lines
= height
> 0 ? height
: 1;
562 widget_init (w
, &r
, listbox_callback
, listbox_mouse_callback
);
563 w
->options
|= WOP_SELECTABLE
| WOP_WANT_HOTKEY
;
564 w
->keymap
= listbox_map
;
567 l
->top
= l
->current
= 0;
568 l
->deletable
= deletable
;
569 l
->callback
= callback
;
570 l
->allow_duplicates
= TRUE
;
571 l
->scrollbar
= !mc_global
.tty
.slow_terminal
;
576 /* --------------------------------------------------------------------------------------------- */
579 * Finds item by its label.
582 listbox_search_text (WListbox
*l
, const char *text
)
584 if (!listbox_is_empty (l
))
589 for (i
= 0, le
= g_queue_peek_head_link (l
->list
); le
!= NULL
; i
++, le
= g_list_next (le
))
591 WLEntry
*e
= LENTRY (le
->data
);
593 if (strcmp (e
->text
, text
) == 0)
601 /* --------------------------------------------------------------------------------------------- */
604 * Finds item by its 'data' slot.
607 listbox_search_data (WListbox
*l
, const void *data
)
609 if (!listbox_is_empty (l
))
614 for (i
= 0, le
= g_queue_peek_head_link (l
->list
); le
!= NULL
; i
++, le
= g_list_next (le
))
616 WLEntry
*e
= LENTRY (le
->data
);
626 /* --------------------------------------------------------------------------------------------- */
628 /* Select the first entry and scrolls the list to the top */
630 listbox_select_first (WListbox
*l
)
632 l
->current
= l
->top
= 0;
635 /* --------------------------------------------------------------------------------------------- */
637 /* Selects the last entry and scrolls the list to the bottom */
639 listbox_select_last (WListbox
*l
)
641 int lines
= WIDGET (l
)->rect
.lines
;
644 length
= listbox_get_length (l
);
646 l
->current
= DOZ (length
, 1);
647 l
->top
= DOZ (length
, lines
);
650 /* --------------------------------------------------------------------------------------------- */
653 listbox_set_current (WListbox
*l
, int dest
)
657 gboolean top_seen
= FALSE
;
659 if (listbox_is_empty (l
) || dest
< 0)
663 for (pos
= 0, le
= g_queue_peek_head_link (l
->list
); le
!= NULL
; pos
++, le
= g_list_next (le
))
675 int lines
= WIDGET (l
)->rect
.lines
;
677 if (l
->current
- l
->top
>= lines
)
678 l
->top
= l
->current
- lines
+ 1;
684 /* If we are unable to find it, set decent values */
685 l
->current
= l
->top
= 0;
688 /* --------------------------------------------------------------------------------------------- */
691 listbox_get_length (const WListbox
*l
)
693 return listbox_is_empty (l
) ? 0 : (int) g_queue_get_length (l
->list
);
696 /* --------------------------------------------------------------------------------------------- */
698 /* Returns the current string text as well as the associated extra data */
700 listbox_get_current (WListbox
*l
, char **string
, void **extra
)
706 e
= listbox_get_nth_entry (l
, l
->current
);
711 *string
= ok
? e
->text
: NULL
;
714 *extra
= ok
? e
->data
: NULL
;
717 /* --------------------------------------------------------------------------------------------- */
720 listbox_get_nth_entry (const WListbox
*l
, int pos
)
722 if (!listbox_is_empty (l
) && pos
>= 0)
726 item
= g_queue_peek_nth_link (l
->list
, (guint
) pos
);
728 return LENTRY (item
->data
);
734 /* --------------------------------------------------------------------------------------------- */
737 listbox_get_first_link (const WListbox
*l
)
739 return (l
== NULL
|| l
->list
== NULL
) ? NULL
: g_queue_peek_head_link (l
->list
);
742 /* --------------------------------------------------------------------------------------------- */
745 listbox_remove_current (WListbox
*l
)
747 if (!listbox_is_empty (l
))
752 current
= g_queue_peek_nth_link (l
->list
, (guint
) l
->current
);
753 listbox_entry_free (current
->data
);
754 g_queue_delete_link (l
->list
, current
);
756 length
= g_queue_get_length (l
->list
);
759 l
->top
= l
->current
= 0;
760 else if (l
->current
>= length
)
761 l
->current
= length
- 1;
765 /* --------------------------------------------------------------------------------------------- */
768 listbox_is_empty (const WListbox
*l
)
770 return (l
== NULL
|| l
->list
== NULL
|| g_queue_is_empty (l
->list
));
773 /* --------------------------------------------------------------------------------------------- */
776 * Set new listbox items list.
778 * @param l WListbox object
779 * @param list list of WLEntry objects
782 listbox_set_list (WListbox
*l
, GQueue
*list
)
784 listbox_remove_list (l
);
790 /* --------------------------------------------------------------------------------------------- */
793 listbox_remove_list (WListbox
*l
)
799 g_queue_free_full (l
->list
, (GDestroyNotify
) listbox_entry_free
);
803 l
->current
= l
->top
= 0;
807 /* --------------------------------------------------------------------------------------------- */
810 * Add new intem to the listbox.
812 * @param l WListbox object
813 * @param pos position of the item
814 * @param hotkey position of the item
815 * @param text item text. @l takes the copy of @text.
816 * @param data item data
817 * @param free_data if TRUE free the @data when @l is destroyed,
819 * @returns pointer to copy of @text.
822 listbox_add_item (WListbox
*l
, listbox_append_t pos
, int hotkey
, const char *text
, void *data
,
825 return listbox_add_item_take (l
, pos
, hotkey
, g_strdup (text
), data
, free_data
);
828 /* --------------------------------------------------------------------------------------------- */
831 * Add new intem to the listbox.
833 * @param l WListbox object
834 * @param pos position of the item
835 * @param hotkey position of the item
836 * @param text item text. Ownership of the text is transferred to the @l.
837 * @param data item data
838 * @param free_data if TRUE free the @data when @l is destroyed,
840 * After this call, @text belongs to the @l and may no longer be modified by the caller.
842 * @returns pointer to @text.
845 listbox_add_item_take (WListbox
*l
, listbox_append_t pos
, int hotkey
, char *text
, void *data
,
853 if (!l
->allow_duplicates
&& (listbox_search_text (l
, text
) >= 0))
856 entry
= g_new (WLEntry
, 1);
859 entry
->free_data
= free_data
;
860 entry
->hotkey
= hotkey
;
862 listbox_add_entry (l
, entry
, pos
);
867 /* --------------------------------------------------------------------------------------------- */