Update translations from Transifex
[midnight-commander.git] / lib / widget / listbox.c
blob9234812bb488f33fbb614d35f48cf4d208dc5f66
1 /*
2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Authors:
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
10 Jakub Jelinek, 1995
11 Andrej Borsenkow, 1996
12 Norbert Warmuth, 1997
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/>.
31 /** \file listbox.c
32 * \brief Source: WListbox widget
35 #include <config.h>
37 #include <stdlib.h>
39 #include "lib/global.h"
41 #include "lib/tty/tty.h"
42 #include "lib/skin.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 /* --------------------------------------------------------------------------------------------- */
66 static int
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;
72 (void) user_data;
74 return strcmp (ea->text, eb->text);
77 /* --------------------------------------------------------------------------------------------- */
79 static void
80 listbox_entry_free (void *data)
82 WLEntry *e = data;
84 g_free (e->text);
85 if (e->free_data)
86 g_free (e->data);
87 g_free (e);
90 /* --------------------------------------------------------------------------------------------- */
92 static void
93 listbox_drawscroll (const WListbox *l)
95 const WRect *w = &CONST_WIDGET (l)->rect;
96 int max_line = w->lines - 1;
97 int line = 0;
98 int i;
99 int length;
101 /* Are we at the top? */
102 widget_gotoyx (l, 0, w->cols);
103 if (l->top == 0)
104 tty_print_one_vline (TRUE);
105 else
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);
114 else
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);
124 if (i != line)
125 tty_print_one_vline (TRUE);
126 else
127 tty_print_char ('*');
131 /* --------------------------------------------------------------------------------------------- */
133 static void
134 listbox_draw (WListbox *l, gboolean focused)
136 Widget *wl = WIDGET (l);
137 const WRect *w = &CONST_WIDGET (l)->rect;
138 const int *colors;
139 gboolean disabled;
140 int normalc, selc;
141 int length = 0;
142 GList *le = NULL;
143 int pos;
144 int i;
145 int sel_line = -1;
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];
153 if (l->list != NULL)
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)
169 sel_line = i;
170 tty_setcolor (selc);
172 else
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);
181 text = e->text;
182 le = g_list_next (le);
183 pos++;
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 /* --------------------------------------------------------------------------------------------- */
200 static int
201 listbox_check_hotkey (WListbox *l, int key)
203 if (!listbox_is_empty (l))
205 int i;
206 GList *le;
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)
213 return i;
217 return (-1);
220 /* --------------------------------------------------------------------------------------------- */
222 /* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
223 static int
224 listbox_y_pos (WListbox *l, int y)
226 return MIN (l->top + y, LISTBOX_LAST (l));
229 /* --------------------------------------------------------------------------------------------- */
231 static void
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);
238 else if (wrap)
239 listbox_select_first (l);
243 /* --------------------------------------------------------------------------------------------- */
245 static void
246 listbox_fwd_n (WListbox *l, int n)
248 listbox_set_current (l, MIN (l->current + n, LISTBOX_LAST (l)));
251 /* --------------------------------------------------------------------------------------------- */
253 static void
254 listbox_back (WListbox *l, gboolean wrap)
256 if (!listbox_is_empty (l))
258 if (l->current > 0)
259 listbox_set_current (l, l->current - 1);
260 else if (wrap)
261 listbox_select_last (l);
265 /* --------------------------------------------------------------------------------------------- */
267 static void
268 listbox_back_n (WListbox *l, int n)
270 listbox_set_current (l, MAX (l->current - n, 0));
273 /* --------------------------------------------------------------------------------------------- */
275 static cb_ret_t
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;
284 switch (command)
286 case CK_Up:
287 listbox_back (l, TRUE);
288 break;
289 case CK_Down:
290 listbox_fwd (l, TRUE);
291 break;
292 case CK_Top:
293 listbox_select_first (l);
294 break;
295 case CK_Bottom:
296 listbox_select_last (l);
297 break;
298 case CK_PageUp:
299 listbox_back_n (l, w->lines - 1);
300 break;
301 case CK_PageDown:
302 listbox_fwd_n (l, w->lines - 1);
303 break;
304 case CK_Delete:
305 if (l->deletable)
307 gboolean is_last, is_more;
308 int length;
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))
317 l->top--;
319 break;
320 case CK_Clear:
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);
327 break;
328 case CK_View:
329 case CK_Edit:
330 case CK_Enter:
331 ret = send_message (WIDGET (l)->owner, l, MSG_NOTIFY, command, NULL);
332 break;
333 default:
334 ret = MSG_NOT_HANDLED;
337 return ret;
340 /* --------------------------------------------------------------------------------------------- */
342 /* Return MSG_HANDLED if we want a redraw */
343 static cb_ret_t
344 listbox_key (WListbox *l, int key)
346 long command;
348 if (l->list == NULL)
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');
355 return MSG_HANDLED;
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 */
367 static inline void
368 listbox_add_entry (WListbox *l, WLEntry *e, listbox_append_t pos)
370 if (l->list == NULL)
372 l->list = g_queue_new ();
373 pos = LISTBOX_APPEND_AT_END;
376 switch (pos)
378 case LISTBOX_APPEND_AT_END:
379 g_queue_push_tail (l->list, e);
380 break;
382 case LISTBOX_APPEND_BEFORE:
383 g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
384 break;
386 case LISTBOX_APPEND_AFTER:
387 g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
388 break;
390 case LISTBOX_APPEND_SORTED:
391 g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
392 break;
394 default:
395 break;
399 /* --------------------------------------------------------------------------------------------- */
401 /* Call this whenever the user changes the selected item. */
402 static void
403 listbox_on_change (WListbox *l)
405 listbox_draw (l, TRUE);
406 send_message (WIDGET (l)->owner, l, MSG_NOTIFY, 0, NULL);
409 /* --------------------------------------------------------------------------------------------- */
411 static void
412 listbox_do_action (WListbox *l)
414 int action;
416 if (listbox_is_empty (l))
417 return;
419 if (l->callback != NULL)
420 action = l->callback (l);
421 else
422 action = LISTBOX_DONE;
424 if (action == LISTBOX_DONE)
426 WDialog *h = DIALOG (WIDGET (l)->owner);
428 h->ret_value = B_ENTER;
429 dlg_close (h);
433 /* --------------------------------------------------------------------------------------------- */
435 static void
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 /* --------------------------------------------------------------------------------------------- */
445 static inline void
446 listbox_destroy (WListbox *l)
448 listbox_remove_list (l);
451 /* --------------------------------------------------------------------------------------------- */
453 static cb_ret_t
454 listbox_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
456 WListbox *l = LISTBOX (w);
458 switch (msg)
460 case MSG_HOTKEY:
462 int pos;
464 pos = listbox_check_hotkey (l, parm);
465 if (pos < 0)
466 return MSG_NOT_HANDLED;
468 listbox_run_hotkey (l, pos);
470 return MSG_HANDLED;
473 case MSG_KEY:
475 cb_ret_t ret_code;
477 ret_code = listbox_key (l, parm);
478 if (ret_code != MSG_NOT_HANDLED)
479 listbox_on_change (l);
480 return ret_code;
483 case MSG_ACTION:
484 return listbox_execute_cmd (l, parm);
486 case MSG_CURSOR:
487 widget_gotoyx (l, l->cursor_y, 0);
488 return MSG_HANDLED;
490 case MSG_DRAW:
491 listbox_draw (l, widget_get_state (w, WST_FOCUSED));
492 return MSG_HANDLED;
494 case MSG_DESTROY:
495 listbox_destroy (l);
496 return MSG_HANDLED;
498 default:
499 return widget_default_callback (w, sender, msg, parm, data);
503 /* --------------------------------------------------------------------------------------------- */
505 static void
506 listbox_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
508 WListbox *l = LISTBOX (w);
509 int old_current;
511 old_current = l->current;
513 switch (msg)
515 case MSG_MOUSE_DOWN:
516 widget_select (w);
517 listbox_set_current (l, listbox_y_pos (l, event->y));
518 break;
520 case MSG_MOUSE_SCROLL_UP:
521 listbox_back (l, FALSE);
522 break;
524 case MSG_MOUSE_SCROLL_DOWN:
525 listbox_fwd (l, FALSE);
526 break;
528 case MSG_MOUSE_DRAG:
529 event->result.repeat = TRUE; /* It'd be functional even without this. */
530 listbox_set_current (l, listbox_y_pos (l, event->y));
531 break;
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);
537 break;
539 default:
540 break;
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 /* --------------------------------------------------------------------------------------------- */
552 WListbox *
553 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
555 WRect r = { y, x, 1, width };
556 WListbox *l;
557 Widget *w;
559 l = g_new (WListbox, 1);
560 w = WIDGET (l);
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;
566 l->list = NULL;
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;
573 return l;
576 /* --------------------------------------------------------------------------------------------- */
579 * Finds item by its label.
582 listbox_search_text (WListbox *l, const char *text)
584 if (!listbox_is_empty (l))
586 int i;
587 GList *le;
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)
594 return i;
598 return (-1);
601 /* --------------------------------------------------------------------------------------------- */
604 * Finds item by its 'data' slot.
607 listbox_search_data (WListbox *l, const void *data)
609 if (!listbox_is_empty (l))
611 int i;
612 GList *le;
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);
618 if (e->data == data)
619 return i;
623 return (-1);
626 /* --------------------------------------------------------------------------------------------- */
628 /* Select the first entry and scrolls the list to the top */
629 void
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 */
638 void
639 listbox_select_last (WListbox *l)
641 int lines = WIDGET (l)->rect.lines;
642 int length;
644 length = listbox_get_length (l);
646 l->current = DOZ (length, 1);
647 l->top = DOZ (length, lines);
650 /* --------------------------------------------------------------------------------------------- */
652 void
653 listbox_set_current (WListbox *l, int dest)
655 GList *le;
656 int pos;
657 gboolean top_seen = FALSE;
659 if (listbox_is_empty (l) || dest < 0)
660 return;
662 /* Special case */
663 for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
665 if (pos == l->top)
666 top_seen = TRUE;
668 if (pos == dest)
670 l->current = dest;
671 if (!top_seen)
672 l->top = l->current;
673 else
675 int lines = WIDGET (l)->rect.lines;
677 if (l->current - l->top >= lines)
678 l->top = l->current - lines + 1;
680 return;
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 */
699 void
700 listbox_get_current (WListbox *l, char **string, void **extra)
702 WLEntry *e = NULL;
703 gboolean ok;
705 if (l != NULL)
706 e = listbox_get_nth_entry (l, l->current);
708 ok = (e != NULL);
710 if (string != NULL)
711 *string = ok ? e->text : NULL;
713 if (extra != NULL)
714 *extra = ok ? e->data : NULL;
717 /* --------------------------------------------------------------------------------------------- */
719 WLEntry *
720 listbox_get_nth_entry (const WListbox *l, int pos)
722 if (!listbox_is_empty (l) && pos >= 0)
724 GList *item;
726 item = g_queue_peek_nth_link (l->list, (guint) pos);
727 if (item != NULL)
728 return LENTRY (item->data);
731 return NULL;
734 /* --------------------------------------------------------------------------------------------- */
736 GList *
737 listbox_get_first_link (const WListbox *l)
739 return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
742 /* --------------------------------------------------------------------------------------------- */
744 void
745 listbox_remove_current (WListbox *l)
747 if (!listbox_is_empty (l))
749 GList *current;
750 int length;
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);
758 if (length == 0)
759 l->top = l->current = 0;
760 else if (l->current >= length)
761 l->current = length - 1;
765 /* --------------------------------------------------------------------------------------------- */
767 gboolean
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
781 void
782 listbox_set_list (WListbox *l, GQueue *list)
784 listbox_remove_list (l);
786 if (l != NULL)
787 l->list = list;
790 /* --------------------------------------------------------------------------------------------- */
792 void
793 listbox_remove_list (WListbox *l)
795 if (l != NULL)
797 if (l->list != NULL)
799 g_queue_free_full (l->list, (GDestroyNotify) listbox_entry_free);
800 l->list = NULL;
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.
821 char *
822 listbox_add_item (WListbox *l, listbox_append_t pos, int hotkey, const char *text, void *data,
823 gboolean free_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.
844 char *
845 listbox_add_item_take (WListbox *l, listbox_append_t pos, int hotkey, char *text, void *data,
846 gboolean free_data)
848 WLEntry *entry;
850 if (l == NULL)
851 return NULL;
853 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
854 return NULL;
856 entry = g_new (WLEntry, 1);
857 entry->text = text;
858 entry->data = data;
859 entry->free_data = free_data;
860 entry->hotkey = hotkey;
862 listbox_add_entry (l, entry, pos);
864 return entry->text;
867 /* --------------------------------------------------------------------------------------------- */