Remove outdated comment about refresh.
[midnight-commander.git] / lib / widget / listbox.c
blob0242e7246d28b249cc6922fe7a368647decc8341
1 /*
2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2015
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
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/tty/mouse.h"
43 #include "lib/skin.h"
44 #include "lib/strutil.h"
45 #include "lib/util.h" /* Q_() */
46 #include "lib/keybind.h" /* global_keymap_t */
47 #include "lib/widget.h"
49 /*** global variables ****************************************************************************/
51 const global_keymap_t *listbox_map = NULL;
53 /*** file scope macro definitions ****************************************************************/
55 /* Gives the position of the last item. */
56 #define LISTBOX_LAST(l) (g_queue_is_empty ((l)->list) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
58 /*** file scope type declarations ****************************************************************/
60 /*** file scope variables ************************************************************************/
62 /*** file scope functions ************************************************************************/
64 static int
65 listbox_entry_cmp (const void *a, const void *b, void *user_data)
67 const WLEntry *ea = (const WLEntry *) a;
68 const WLEntry *eb = (const WLEntry *) b;
70 (void) user_data;
72 return strcmp (ea->text, eb->text);
75 /* --------------------------------------------------------------------------------------------- */
77 static void
78 listbox_entry_free (void *data)
80 WLEntry *e = data;
82 g_free (e->text);
83 if (e->free_data)
84 g_free (e->data);
85 g_free (e);
88 /* --------------------------------------------------------------------------------------------- */
90 static void
91 listbox_drawscroll (WListbox * l)
93 Widget *w = WIDGET (l);
94 int max_line = w->lines - 1;
95 int line = 0;
96 int i;
97 int length;
99 /* Are we at the top? */
100 widget_move (w, 0, w->cols);
101 if (l->top == 0)
102 tty_print_one_vline (TRUE);
103 else
104 tty_print_char ('^');
106 length = g_queue_get_length (l->list);
108 /* Are we at the bottom? */
109 widget_move (w, max_line, w->cols);
110 if (l->top + w->lines == length || w->lines >= length)
111 tty_print_one_vline (TRUE);
112 else
113 tty_print_char ('v');
115 /* Now draw the nice relative pointer */
116 if (!g_queue_is_empty (l->list))
117 line = 1 + ((l->pos * (w->lines - 2)) / length);
119 for (i = 1; i < max_line; i++)
121 widget_move (w, i, w->cols);
122 if (i != line)
123 tty_print_one_vline (TRUE);
124 else
125 tty_print_char ('*');
129 /* --------------------------------------------------------------------------------------------- */
131 static void
132 listbox_draw (WListbox * l, gboolean focused)
134 Widget *w = WIDGET (l);
135 const WDialog *h = w->owner;
136 const gboolean disabled = (w->options & W_DISABLED) != 0;
137 const int normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
138 /* *INDENT-OFF* */
139 int selc = disabled
140 ? DISABLED_COLOR
141 : focused
142 ? h->color[DLG_COLOR_HOT_FOCUS]
143 : h->color[DLG_COLOR_FOCUS];
144 /* *INDENT-ON* */
146 int length = 0;
147 GList *le = NULL;
148 int pos;
149 int i;
150 int sel_line = -1;
152 if (l->list != NULL)
154 length = g_queue_get_length (l->list);
155 le = g_queue_peek_nth_link (l->list, (guint) l->top);
158 /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
159 pos = (le == NULL) ? 0 : l->top;
161 for (i = 0; i < w->lines; i++)
163 const char *text = "";
165 /* Display the entry */
166 if (pos == l->pos && sel_line == -1)
168 sel_line = i;
169 tty_setcolor (selc);
171 else
172 tty_setcolor (normalc);
174 widget_move (l, i, 1);
176 if (l->list != NULL && le != NULL && (i == 0 || pos < length))
178 WLEntry *e = LENTRY (le->data);
180 text = e->text;
181 le = g_list_next (le);
182 pos++;
185 tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
188 l->cursor_y = sel_line;
190 if (l->scrollbar && length > w->lines)
192 tty_setcolor (normalc);
193 listbox_drawscroll (l);
197 /* --------------------------------------------------------------------------------------------- */
199 static int
200 listbox_check_hotkey (WListbox * l, int key)
202 if (!listbox_is_empty (l))
204 int i;
205 GList *le;
207 for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
209 WLEntry *e = LENTRY (le->data);
211 if (e->hotkey == key)
212 return i;
216 return (-1);
219 /* --------------------------------------------------------------------------------------------- */
221 /* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
222 static int
223 listbox_y_pos (WListbox * l, int y)
225 return min (l->top + y, LISTBOX_LAST (l));
228 /* --------------------------------------------------------------------------------------------- */
230 static void
231 listbox_fwd (WListbox * l, gboolean wrap)
233 if ((guint) l->pos + 1 < g_queue_get_length (l->list))
234 listbox_select_entry (l, l->pos + 1);
235 else if (wrap)
236 listbox_select_first (l);
239 /* --------------------------------------------------------------------------------------------- */
241 static void
242 listbox_fwd_n (WListbox * l, int n)
244 listbox_select_entry (l, min (l->pos + n, LISTBOX_LAST (l)));
247 /* --------------------------------------------------------------------------------------------- */
249 static void
250 listbox_back (WListbox * l, gboolean wrap)
252 if (l->pos > 0)
253 listbox_select_entry (l, l->pos - 1);
254 else if (wrap)
255 listbox_select_last (l);
258 /* --------------------------------------------------------------------------------------------- */
260 static void
261 listbox_back_n (WListbox * l, int n)
263 listbox_select_entry (l, max (l->pos - n, 0));
266 /* --------------------------------------------------------------------------------------------- */
268 static cb_ret_t
269 listbox_execute_cmd (WListbox * l, unsigned long command)
271 cb_ret_t ret = MSG_HANDLED;
272 Widget *w = WIDGET (l);
274 if (l->list == NULL || g_queue_is_empty (l->list))
275 return MSG_NOT_HANDLED;
277 switch (command)
279 case CK_Up:
280 listbox_back (l, TRUE);
281 break;
282 case CK_Down:
283 listbox_fwd (l, TRUE);
284 break;
285 case CK_Top:
286 listbox_select_first (l);
287 break;
288 case CK_Bottom:
289 listbox_select_last (l);
290 break;
291 case CK_PageUp:
292 listbox_back_n (l, w->lines - 1);
293 break;
294 case CK_PageDown:
295 listbox_fwd_n (l, w->lines - 1);
296 break;
297 case CK_Delete:
298 if (l->deletable)
300 gboolean is_last, is_more;
301 int length;
303 length = g_queue_get_length (l->list);
305 is_last = (l->pos + 1 >= length);
306 is_more = (l->top + w->lines >= length);
308 listbox_remove_current (l);
309 if ((l->top > 0) && (is_last || is_more))
310 l->top--;
312 break;
313 case CK_Clear:
314 if (l->deletable && mc_global.widget.confirm_history_cleanup
315 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
316 && (query_dialog (Q_ ("DialogTitle|History cleanup"),
317 _("Do you want clean this history?"),
318 D_ERROR, 2, _("&Yes"), _("&No")) == 0))
319 listbox_remove_list (l);
320 break;
321 default:
322 ret = MSG_NOT_HANDLED;
325 return ret;
328 /* --------------------------------------------------------------------------------------------- */
330 /* Return MSG_HANDLED if we want a redraw */
331 static cb_ret_t
332 listbox_key (WListbox * l, int key)
334 unsigned long command;
336 if (l->list == NULL)
337 return MSG_NOT_HANDLED;
339 /* focus on listbox item N by '0'..'9' keys */
340 if (key >= '0' && key <= '9')
342 int oldpos = l->pos;
343 listbox_select_entry (l, key - '0');
345 /* need scroll to item? */
346 if (abs (oldpos - l->pos) > WIDGET (l)->lines)
347 l->top = l->pos;
349 return MSG_HANDLED;
352 command = keybind_lookup_keymap_command (listbox_map, key);
353 if (command == CK_IgnoreKey)
354 return MSG_NOT_HANDLED;
355 return listbox_execute_cmd (l, command);
358 /* --------------------------------------------------------------------------------------------- */
360 /* Listbox item adding function */
361 static inline void
362 listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
364 if (l->list == NULL)
366 l->list = g_queue_new ();
367 pos = LISTBOX_APPEND_AT_END;
370 switch (pos)
372 case LISTBOX_APPEND_AT_END:
373 g_queue_push_tail (l->list, e);
374 break;
376 case LISTBOX_APPEND_BEFORE:
377 g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
378 break;
380 case LISTBOX_APPEND_AFTER:
381 g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
382 break;
384 case LISTBOX_APPEND_SORTED:
385 g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
386 break;
388 default:
389 break;
393 /* --------------------------------------------------------------------------------------------- */
395 static inline void
396 listbox_destroy (WListbox * l)
398 listbox_remove_list (l);
401 /* --------------------------------------------------------------------------------------------- */
403 static cb_ret_t
404 listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
406 WListbox *l = LISTBOX (w);
407 WDialog *h = w->owner;
408 cb_ret_t ret_code;
410 switch (msg)
412 case MSG_INIT:
413 return MSG_HANDLED;
415 case MSG_HOTKEY:
417 int pos, action;
419 pos = listbox_check_hotkey (l, parm);
420 if (pos < 0)
421 return MSG_NOT_HANDLED;
423 listbox_select_entry (l, pos);
424 send_message (h, w, MSG_ACTION, l->pos, NULL);
426 if (l->callback != NULL)
427 action = l->callback (l);
428 else
429 action = LISTBOX_DONE;
431 if (action == LISTBOX_DONE)
433 h->ret_value = B_ENTER;
434 dlg_stop (h);
437 return MSG_HANDLED;
440 case MSG_KEY:
441 ret_code = listbox_key (l, parm);
442 if (ret_code != MSG_NOT_HANDLED)
444 listbox_draw (l, TRUE);
445 send_message (h, w, MSG_ACTION, l->pos, NULL);
447 return ret_code;
449 case MSG_ACTION:
450 return listbox_execute_cmd (l, parm);
452 case MSG_CURSOR:
453 widget_move (l, l->cursor_y, 0);
454 send_message (h, w, MSG_ACTION, l->pos, NULL);
455 return MSG_HANDLED;
457 case MSG_FOCUS:
458 case MSG_UNFOCUS:
459 case MSG_DRAW:
460 listbox_draw (l, msg != MSG_UNFOCUS);
461 return MSG_HANDLED;
463 case MSG_DESTROY:
464 listbox_destroy (l);
465 return MSG_HANDLED;
467 case MSG_RESIZE:
468 return MSG_HANDLED;
470 default:
471 return widget_default_callback (w, sender, msg, parm, data);
475 /* --------------------------------------------------------------------------------------------- */
477 static int
478 listbox_event (Gpm_Event * event, void *data)
480 WListbox *l = LISTBOX (data);
481 Widget *w = WIDGET (data);
483 if (!mouse_global_in_widget (event, w))
484 return MOU_UNHANDLED;
486 /* Single click */
487 if ((event->type & GPM_DOWN) != 0)
488 dlg_select_widget (l);
490 if (listbox_is_empty (l))
491 return MOU_NORMAL;
493 if ((event->type & (GPM_DOWN | GPM_DRAG)) != 0)
495 int ret = MOU_REPEAT;
496 Gpm_Event local;
498 local = mouse_get_local (event, w);
499 if (local.y < 1)
500 listbox_back_n (l, -local.y + 1);
501 else if (local.y > w->lines)
502 listbox_fwd_n (l, local.y - w->lines);
503 else if ((local.buttons & GPM_B_UP) != 0)
505 listbox_back (l, FALSE);
506 ret = MOU_NORMAL;
508 else if ((local.buttons & GPM_B_DOWN) != 0)
510 listbox_fwd (l, FALSE);
511 ret = MOU_NORMAL;
513 else
514 listbox_select_entry (l, listbox_y_pos (l, local.y - 1));
516 listbox_draw (l, TRUE);
517 return ret;
520 /* Double click */
521 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE))
523 Gpm_Event local;
524 int action;
526 local = mouse_get_local (event, w);
527 dlg_select_widget (l);
528 listbox_select_entry (l, listbox_y_pos (l, local.y - 1));
530 if (l->callback != NULL)
531 action = l->callback (l);
532 else
533 action = LISTBOX_DONE;
535 if (action == LISTBOX_DONE)
537 w->owner->ret_value = B_ENTER;
538 dlg_stop (w->owner);
542 return MOU_NORMAL;
545 /* --------------------------------------------------------------------------------------------- */
546 /*** public functions ****************************************************************************/
547 /* --------------------------------------------------------------------------------------------- */
549 WListbox *
550 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
552 WListbox *l;
553 Widget *w;
555 if (height <= 0)
556 height = 1;
558 l = g_new (WListbox, 1);
559 w = WIDGET (l);
560 widget_init (w, y, x, height, width, listbox_callback, listbox_event);
562 l->list = NULL;
563 l->top = l->pos = 0;
564 l->deletable = deletable;
565 l->callback = callback;
566 l->allow_duplicates = TRUE;
567 l->scrollbar = !mc_global.tty.slow_terminal;
568 widget_want_hotkey (w, TRUE);
569 widget_want_cursor (w, FALSE);
571 return l;
574 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
598 /* Selects the first entry and scrolls the list to the top */
599 void
600 listbox_select_first (WListbox * l)
602 l->pos = l->top = 0;
605 /* --------------------------------------------------------------------------------------------- */
607 /* Selects the last entry and scrolls the list to the bottom */
608 void
609 listbox_select_last (WListbox * l)
611 int lines = WIDGET (l)->lines;
612 int length = 0;
614 if (!listbox_is_empty (l))
615 length = g_queue_get_length (l->list);
617 l->pos = length > 0 ? length - 1 : 0;
618 l->top = length > lines ? length - lines : 0;
621 /* --------------------------------------------------------------------------------------------- */
623 void
624 listbox_select_entry (WListbox * l, int dest)
626 GList *le;
627 int pos;
628 gboolean top_seen = FALSE;
630 if (listbox_is_empty (l) || dest < 0)
631 return;
633 /* Special case */
634 for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
636 if (pos == l->top)
637 top_seen = TRUE;
639 if (pos == dest)
641 l->pos = dest;
642 if (!top_seen)
643 l->top = l->pos;
644 else
646 int lines = WIDGET (l)->lines;
648 if (l->pos - l->top >= lines)
649 l->top = l->pos - lines + 1;
651 return;
655 /* If we are unable to find it, set decent values */
656 l->pos = l->top = 0;
659 /* --------------------------------------------------------------------------------------------- */
661 /* Returns the current string text as well as the associated extra data */
662 void
663 listbox_get_current (WListbox * l, char **string, void **extra)
665 WLEntry *e = NULL;
666 gboolean ok;
668 if (l != NULL)
669 e = listbox_get_nth_item (l, l->pos);
671 ok = (e != NULL);
673 if (string != NULL)
674 *string = ok ? e->text : NULL;
676 if (extra != NULL)
677 *extra = ok ? e->data : NULL;
680 /* --------------------------------------------------------------------------------------------- */
682 WLEntry *
683 listbox_get_nth_item (const WListbox * l, int pos)
685 if (!listbox_is_empty (l) && pos >= 0)
687 GList *item;
689 item = g_queue_peek_nth_link (l->list, (guint) pos);
690 if (item != NULL)
691 return LENTRY (item->data);
694 return NULL;
697 /* --------------------------------------------------------------------------------------------- */
699 GList *
700 listbox_get_first_link (const WListbox * l)
702 return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
705 /* --------------------------------------------------------------------------------------------- */
707 void
708 listbox_remove_current (WListbox * l)
710 if (!listbox_is_empty (l))
712 GList *current;
713 int length;
715 current = g_queue_peek_nth_link (l->list, (guint) l->pos);
716 listbox_entry_free (LENTRY (current->data));
717 g_queue_delete_link (l->list, current);
719 length = g_queue_get_length (l->list);
721 if (length == 0)
722 l->top = l->pos = 0;
723 else if (l->pos >= length)
724 l->pos = length - 1;
728 /* --------------------------------------------------------------------------------------------- */
730 gboolean
731 listbox_is_empty (const WListbox * l)
733 return (l == NULL || l->list == NULL || g_queue_is_empty (l->list));
736 /* --------------------------------------------------------------------------------------------- */
738 void
739 listbox_set_list (WListbox * l, GList * list)
741 listbox_remove_list (l);
743 if (l != NULL)
745 GList *ll;
747 l->list = g_queue_new ();
749 for (ll = list; ll != NULL; ll = g_list_next (ll))
750 g_queue_push_tail (l->list, ll->data);
752 g_list_free (list);
756 /* --------------------------------------------------------------------------------------------- */
758 void
759 listbox_remove_list (WListbox * l)
761 if (l != NULL)
763 if (l->list != NULL)
765 g_queue_foreach (l->list, (GFunc) listbox_entry_free, NULL);
766 g_queue_free (l->list);
769 l->list = NULL;
770 l->pos = l->top = 0;
774 /* --------------------------------------------------------------------------------------------- */
776 char *
777 listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data,
778 gboolean free_data)
780 WLEntry *entry;
782 if (l == NULL)
783 return NULL;
785 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
786 return NULL;
788 entry = g_new (WLEntry, 1);
789 entry->text = g_strdup (text);
790 entry->data = data;
791 entry->free_data = free_data;
792 entry->hotkey = hotkey;
794 listbox_append_item (l, entry, pos);
796 return entry->text;
799 /* --------------------------------------------------------------------------------------------- */