(make_symlink): add missing space in error message.
[midnight-commander.git] / lib / widget / listbox.c
blob378a3af8a9f753d177c2100a20e07576c279c0ec
1 /*
2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2017
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, 2010, 2013, 2016
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/keybind.h" /* global_keymap_t */
46 #include "lib/widget.h"
48 /*** global variables ****************************************************************************/
50 const global_keymap_t *listbox_map = NULL;
52 /*** file scope macro definitions ****************************************************************/
54 /* Gives the position of the last item. */
55 #define LISTBOX_LAST(l) (listbox_is_empty (l) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
57 /*** file scope type declarations ****************************************************************/
59 /*** file scope variables ************************************************************************/
61 /*** file scope functions ************************************************************************/
63 static int
64 listbox_entry_cmp (const void *a, const void *b, void *user_data)
66 const WLEntry *ea = (const WLEntry *) a;
67 const WLEntry *eb = (const WLEntry *) b;
69 (void) user_data;
71 return strcmp (ea->text, eb->text);
74 /* --------------------------------------------------------------------------------------------- */
76 static void
77 listbox_entry_free (void *data)
79 WLEntry *e = data;
81 g_free (e->text);
82 if (e->free_data)
83 g_free (e->data);
84 g_free (e);
87 /* --------------------------------------------------------------------------------------------- */
89 static void
90 listbox_drawscroll (WListbox * l)
92 Widget *w = WIDGET (l);
93 int max_line = w->lines - 1;
94 int line = 0;
95 int i;
96 int length;
98 /* Are we at the top? */
99 widget_move (w, 0, w->cols);
100 if (l->top == 0)
101 tty_print_one_vline (TRUE);
102 else
103 tty_print_char ('^');
105 length = g_queue_get_length (l->list);
107 /* Are we at the bottom? */
108 widget_move (w, max_line, w->cols);
109 if (l->top + w->lines == length || w->lines >= length)
110 tty_print_one_vline (TRUE);
111 else
112 tty_print_char ('v');
114 /* Now draw the nice relative pointer */
115 if (!g_queue_is_empty (l->list))
116 line = 1 + ((l->pos * (w->lines - 2)) / length);
118 for (i = 1; i < max_line; i++)
120 widget_move (w, i, w->cols);
121 if (i != line)
122 tty_print_one_vline (TRUE);
123 else
124 tty_print_char ('*');
128 /* --------------------------------------------------------------------------------------------- */
130 static void
131 listbox_draw (WListbox * l, gboolean focused)
133 Widget *w = WIDGET (l);
134 const WDialog *h = w->owner;
135 gboolean disabled;
136 int normalc, selc;
137 int length = 0;
138 GList *le = NULL;
139 int pos;
140 int i;
141 int sel_line = -1;
143 disabled = widget_get_state (w, WST_DISABLED);
144 normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
145 /* *INDENT-OFF* */
146 selc = disabled
147 ? DISABLED_COLOR
148 : focused
149 ? h->color[DLG_COLOR_HOT_FOCUS]
150 : h->color[DLG_COLOR_FOCUS];
151 /* *INDENT-ON* */
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->pos && sel_line == -1)
169 sel_line = i;
170 tty_setcolor (selc);
172 else
173 tty_setcolor (normalc);
175 widget_move (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->pos + 1 < g_queue_get_length (l->list))
237 listbox_select_entry (l, l->pos + 1);
238 else if (wrap)
239 listbox_select_first (l);
243 /* --------------------------------------------------------------------------------------------- */
245 static void
246 listbox_fwd_n (WListbox * l, int n)
248 listbox_select_entry (l, MIN (l->pos + n, LISTBOX_LAST (l)));
251 /* --------------------------------------------------------------------------------------------- */
253 static void
254 listbox_back (WListbox * l, gboolean wrap)
256 if (!listbox_is_empty (l))
258 if (l->pos > 0)
259 listbox_select_entry (l, l->pos - 1);
260 else if (wrap)
261 listbox_select_last (l);
265 /* --------------------------------------------------------------------------------------------- */
267 static void
268 listbox_back_n (WListbox * l, int n)
270 listbox_select_entry (l, MAX (l->pos - n, 0));
273 /* --------------------------------------------------------------------------------------------- */
275 static cb_ret_t
276 listbox_execute_cmd (WListbox * l, long command)
278 cb_ret_t ret = MSG_HANDLED;
279 Widget *w = WIDGET (l);
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->pos + 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 default:
329 ret = MSG_NOT_HANDLED;
332 return ret;
335 /* --------------------------------------------------------------------------------------------- */
337 /* Return MSG_HANDLED if we want a redraw */
338 static cb_ret_t
339 listbox_key (WListbox * l, int key)
341 long command;
343 if (l->list == NULL)
344 return MSG_NOT_HANDLED;
346 /* focus on listbox item N by '0'..'9' keys */
347 if (key >= '0' && key <= '9')
349 listbox_select_entry (l, key - '0');
350 return MSG_HANDLED;
353 command = keybind_lookup_keymap_command (listbox_map, key);
354 if (command == CK_IgnoreKey)
355 return MSG_NOT_HANDLED;
356 return listbox_execute_cmd (l, command);
359 /* --------------------------------------------------------------------------------------------- */
361 /* Listbox item adding function */
362 static inline void
363 listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
365 if (l->list == NULL)
367 l->list = g_queue_new ();
368 pos = LISTBOX_APPEND_AT_END;
371 switch (pos)
373 case LISTBOX_APPEND_AT_END:
374 g_queue_push_tail (l->list, e);
375 break;
377 case LISTBOX_APPEND_BEFORE:
378 g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
379 break;
381 case LISTBOX_APPEND_AFTER:
382 g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
383 break;
385 case LISTBOX_APPEND_SORTED:
386 g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
387 break;
389 default:
390 break;
394 /* --------------------------------------------------------------------------------------------- */
396 /* Call this whenever the user changes the selected item. */
397 static void
398 listbox_on_change (WListbox * l)
400 listbox_draw (l, TRUE);
401 send_message (WIDGET (l)->owner, l, MSG_NOTIFY, l->pos, NULL);
404 /* --------------------------------------------------------------------------------------------- */
406 static void
407 listbox_do_action (WListbox * l)
409 int action;
411 if (listbox_is_empty (l))
412 return;
414 if (l->callback != NULL)
415 action = l->callback (l);
416 else
417 action = LISTBOX_DONE;
419 if (action == LISTBOX_DONE)
421 WDialog *h = WIDGET (l)->owner;
423 h->ret_value = B_ENTER;
424 dlg_stop (h);
428 /* --------------------------------------------------------------------------------------------- */
430 static void
431 listbox_run_hotkey (WListbox * l, int pos)
433 listbox_select_entry (l, pos);
434 listbox_on_change (l);
435 listbox_do_action (l);
438 /* --------------------------------------------------------------------------------------------- */
440 static inline void
441 listbox_destroy (WListbox * l)
443 listbox_remove_list (l);
446 /* --------------------------------------------------------------------------------------------- */
448 static cb_ret_t
449 listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
451 WListbox *l = LISTBOX (w);
452 cb_ret_t ret_code;
454 switch (msg)
456 case MSG_HOTKEY:
458 int pos;
460 pos = listbox_check_hotkey (l, parm);
461 if (pos < 0)
462 return MSG_NOT_HANDLED;
464 listbox_run_hotkey (l, pos);
466 return MSG_HANDLED;
469 case MSG_KEY:
470 ret_code = listbox_key (l, parm);
471 if (ret_code != MSG_NOT_HANDLED)
472 listbox_on_change (l);
473 return ret_code;
475 case MSG_ACTION:
476 return listbox_execute_cmd (l, parm);
478 case MSG_CURSOR:
479 widget_move (l, l->cursor_y, 0);
480 return MSG_HANDLED;
482 case MSG_DRAW:
483 listbox_draw (l, widget_get_state (w, WST_FOCUSED));
484 return MSG_HANDLED;
486 case MSG_DESTROY:
487 listbox_destroy (l);
488 return MSG_HANDLED;
490 case MSG_RESIZE:
491 return MSG_HANDLED;
493 default:
494 return widget_default_callback (w, sender, msg, parm, data);
498 /* --------------------------------------------------------------------------------------------- */
500 static void
501 listbox_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
503 WListbox *l = LISTBOX (w);
504 int old_pos;
506 old_pos = l->pos;
508 switch (msg)
510 case MSG_MOUSE_DOWN:
511 widget_select (w);
512 listbox_select_entry (l, listbox_y_pos (l, event->y));
513 break;
515 case MSG_MOUSE_SCROLL_UP:
516 listbox_back (l, FALSE);
517 break;
519 case MSG_MOUSE_SCROLL_DOWN:
520 listbox_fwd (l, FALSE);
521 break;
523 case MSG_MOUSE_DRAG:
524 event->result.repeat = TRUE; /* It'd be functional even without this. */
525 listbox_select_entry (l, listbox_y_pos (l, event->y));
526 break;
528 case MSG_MOUSE_CLICK:
529 /* We don't call listbox_select_entry() here: MSG_MOUSE_DOWN/DRAG did this already. */
530 if (event->count == GPM_DOUBLE) /* Double click */
531 listbox_do_action (l);
532 break;
534 default:
535 break;
538 /* If the selection has changed, we redraw the widget and notify the dialog. */
539 if (l->pos != old_pos)
540 listbox_on_change (l);
543 /* --------------------------------------------------------------------------------------------- */
544 /*** public functions ****************************************************************************/
545 /* --------------------------------------------------------------------------------------------- */
547 WListbox *
548 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
550 WListbox *l;
551 Widget *w;
553 if (height <= 0)
554 height = 1;
556 l = g_new (WListbox, 1);
557 w = WIDGET (l);
558 widget_init (w, y, x, height, width, listbox_callback, listbox_mouse_callback);
559 w->options |= WOP_SELECTABLE | WOP_WANT_HOTKEY;
561 l->list = NULL;
562 l->top = l->pos = 0;
563 l->deletable = deletable;
564 l->callback = callback;
565 l->allow_duplicates = TRUE;
566 l->scrollbar = !mc_global.tty.slow_terminal;
568 return l;
571 /* --------------------------------------------------------------------------------------------- */
574 * Finds item by its label.
577 listbox_search_text (WListbox * l, const char *text)
579 if (!listbox_is_empty (l))
581 int i;
582 GList *le;
584 for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
586 WLEntry *e = LENTRY (le->data);
588 if (strcmp (e->text, text) == 0)
589 return i;
593 return (-1);
596 /* --------------------------------------------------------------------------------------------- */
599 * Finds item by its 'data' slot.
602 listbox_search_data (WListbox * l, const void *data)
604 if (!listbox_is_empty (l))
606 int i;
607 GList *le;
609 for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
611 WLEntry *e = LENTRY (le->data);
613 if (e->data == data)
614 return i;
618 return (-1);
621 /* --------------------------------------------------------------------------------------------- */
623 /* Selects the first entry and scrolls the list to the top */
624 void
625 listbox_select_first (WListbox * l)
627 l->pos = l->top = 0;
630 /* --------------------------------------------------------------------------------------------- */
632 /* Selects the last entry and scrolls the list to the bottom */
633 void
634 listbox_select_last (WListbox * l)
636 int lines = WIDGET (l)->lines;
637 int length = 0;
639 if (!listbox_is_empty (l))
640 length = g_queue_get_length (l->list);
642 l->pos = length > 0 ? length - 1 : 0;
643 l->top = length > lines ? length - lines : 0;
646 /* --------------------------------------------------------------------------------------------- */
648 void
649 listbox_select_entry (WListbox * l, int dest)
651 GList *le;
652 int pos;
653 gboolean top_seen = FALSE;
655 if (listbox_is_empty (l) || dest < 0)
656 return;
658 /* Special case */
659 for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
661 if (pos == l->top)
662 top_seen = TRUE;
664 if (pos == dest)
666 l->pos = dest;
667 if (!top_seen)
668 l->top = l->pos;
669 else
671 int lines = WIDGET (l)->lines;
673 if (l->pos - l->top >= lines)
674 l->top = l->pos - lines + 1;
676 return;
680 /* If we are unable to find it, set decent values */
681 l->pos = l->top = 0;
684 /* --------------------------------------------------------------------------------------------- */
686 /* Returns the current string text as well as the associated extra data */
687 void
688 listbox_get_current (WListbox * l, char **string, void **extra)
690 WLEntry *e = NULL;
691 gboolean ok;
693 if (l != NULL)
694 e = listbox_get_nth_item (l, l->pos);
696 ok = (e != NULL);
698 if (string != NULL)
699 *string = ok ? e->text : NULL;
701 if (extra != NULL)
702 *extra = ok ? e->data : NULL;
705 /* --------------------------------------------------------------------------------------------- */
707 WLEntry *
708 listbox_get_nth_item (const WListbox * l, int pos)
710 if (!listbox_is_empty (l) && pos >= 0)
712 GList *item;
714 item = g_queue_peek_nth_link (l->list, (guint) pos);
715 if (item != NULL)
716 return LENTRY (item->data);
719 return NULL;
722 /* --------------------------------------------------------------------------------------------- */
724 GList *
725 listbox_get_first_link (const WListbox * l)
727 return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
730 /* --------------------------------------------------------------------------------------------- */
732 void
733 listbox_remove_current (WListbox * l)
735 if (!listbox_is_empty (l))
737 GList *current;
738 int length;
740 current = g_queue_peek_nth_link (l->list, (guint) l->pos);
741 listbox_entry_free (LENTRY (current->data));
742 g_queue_delete_link (l->list, current);
744 length = g_queue_get_length (l->list);
746 if (length == 0)
747 l->top = l->pos = 0;
748 else if (l->pos >= length)
749 l->pos = length - 1;
753 /* --------------------------------------------------------------------------------------------- */
755 gboolean
756 listbox_is_empty (const WListbox * l)
758 return (l == NULL || l->list == NULL || g_queue_is_empty (l->list));
761 /* --------------------------------------------------------------------------------------------- */
763 void
764 listbox_set_list (WListbox * l, GList * list)
766 listbox_remove_list (l);
768 if (l != NULL)
770 GList *ll;
772 l->list = g_queue_new ();
774 for (ll = list; ll != NULL; ll = g_list_next (ll))
775 g_queue_push_tail (l->list, ll->data);
777 g_list_free (list);
781 /* --------------------------------------------------------------------------------------------- */
783 void
784 listbox_remove_list (WListbox * l)
786 if (l != NULL)
788 if (l->list != NULL)
790 g_queue_free_full (l->list, (GDestroyNotify) listbox_entry_free);
791 l->list = NULL;
794 l->pos = l->top = 0;
798 /* --------------------------------------------------------------------------------------------- */
800 char *
801 listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data,
802 gboolean free_data)
804 WLEntry *entry;
806 if (l == NULL)
807 return NULL;
809 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
810 return NULL;
812 entry = g_new (WLEntry, 1);
813 entry->text = g_strdup (text);
814 entry->data = data;
815 entry->free_data = free_data;
816 entry->hotkey = hotkey;
818 listbox_append_item (l, entry, pos);
820 return entry->text;
823 /* --------------------------------------------------------------------------------------------- */