ade0f44509cac0452cd5d69bbb2822744a90bdd8
[midnight-commander.git] / lib / widget / listbox.c
blobade0f44509cac0452cd5d69bbb2822744a90bdd8
1 /*
2 Widgets for the Midnight Commander
4 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
5 2004, 2005, 2006, 2007, 2009, 2010, 2011
6 The Free Software Foundation, Inc.
8 Authors:
9 Radek Doulik, 1994, 1995
10 Miguel de Icaza, 1994, 1995
11 Jakub Jelinek, 1995
12 Andrej Borsenkow, 1996
13 Norbert Warmuth, 1997
14 Andrew Borodin <aborodin@vmail.ru>, 2009, 2010
16 This file is part of the Midnight Commander.
18 The Midnight Commander is free software: you can redistribute it
19 and/or modify it under the terms of the GNU General Public License as
20 published by the Free Software Foundation, either version 3 of the License,
21 or (at your option) any later version.
23 The Midnight Commander is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
28 You should have received a copy of the GNU General Public License
29 along with this program. If not, see <http://www.gnu.org/licenses/>.
32 /** \file listbox.c
33 * \brief Source: WListbox widget
36 #include <config.h>
38 #include <stdlib.h>
40 #include "lib/global.h"
42 #include "lib/tty/tty.h"
43 #include "lib/tty/mouse.h"
44 #include "lib/skin.h"
45 #include "lib/strutil.h"
46 #include "lib/util.h" /* Q_() */
47 #include "lib/keybind.h" /* global_keymap_t */
48 #include "lib/widget.h"
50 /*** global variables ****************************************************************************/
52 const global_keymap_t *listbox_map = NULL;
54 /*** file scope macro definitions ****************************************************************/
56 /*** file scope type declarations ****************************************************************/
58 /*** file scope variables ************************************************************************/
60 /*** file scope functions ************************************************************************/
62 static int
63 listbox_entry_cmp (const void *a, const void *b)
65 const WLEntry *ea = (const WLEntry *) a;
66 const WLEntry *eb = (const WLEntry *) b;
68 return strcmp (ea->text, eb->text);
71 /* --------------------------------------------------------------------------------------------- */
73 static void
74 listbox_entry_free (void *data)
76 WLEntry *e = data;
77 g_free (e->text);
78 g_free (e);
81 /* --------------------------------------------------------------------------------------------- */
83 static void
84 listbox_drawscroll (WListbox * l)
86 Widget *w = WIDGET (l);
87 int max_line = w->lines - 1;
88 int line = 0;
89 int i;
91 /* Are we at the top? */
92 widget_move (w, 0, w->cols);
93 if (l->top == 0)
94 tty_print_one_vline (TRUE);
95 else
96 tty_print_char ('^');
98 /* Are we at the bottom? */
99 widget_move (w, max_line, w->cols);
100 if ((l->top + w->lines == l->count) || (w->lines >= l->count))
101 tty_print_one_vline (TRUE);
102 else
103 tty_print_char ('v');
105 /* Now draw the nice relative pointer */
106 if (l->count != 0)
107 line = 1 + ((l->pos * (w->lines - 2)) / l->count);
109 for (i = 1; i < max_line; i++)
111 widget_move (w, i, w->cols);
112 if (i != line)
113 tty_print_one_vline (TRUE);
114 else
115 tty_print_char ('*');
119 /* --------------------------------------------------------------------------------------------- */
121 static void
122 listbox_draw (WListbox * l, gboolean focused)
124 Widget *w = WIDGET (l);
125 const WDialog *h = w->owner;
126 const gboolean disabled = (w->options & W_DISABLED) != 0;
127 const int normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
128 /* *INDENT-OFF* */
129 int selc = disabled
130 ? DISABLED_COLOR
131 : focused
132 ? h->color[DLG_COLOR_HOT_FOCUS]
133 : h->color[DLG_COLOR_FOCUS];
134 /* *INDENT-ON* */
136 GList *le;
137 int pos;
138 int i;
139 int sel_line = -1;
141 le = g_list_nth (l->list, l->top);
142 /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
143 pos = (le == NULL) ? 0 : l->top;
145 for (i = 0; i < w->lines; i++)
147 const char *text;
149 /* Display the entry */
150 if (pos == l->pos && sel_line == -1)
152 sel_line = i;
153 tty_setcolor (selc);
155 else
156 tty_setcolor (normalc);
158 widget_move (l, i, 1);
160 if ((i > 0 && pos >= l->count) || (l->list == NULL) || (le == NULL))
161 text = "";
162 else
164 WLEntry *e = (WLEntry *) le->data;
165 text = e->text;
166 le = g_list_next (le);
167 pos++;
170 tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
173 l->cursor_y = sel_line;
175 if (l->scrollbar && (l->count > w->lines))
177 tty_setcolor (normalc);
178 listbox_drawscroll (l);
182 /* --------------------------------------------------------------------------------------------- */
184 static int
185 listbox_check_hotkey (WListbox * l, int key)
187 int i;
188 GList *le;
190 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
192 WLEntry *e = (WLEntry *) le->data;
194 if (e->hotkey == key)
195 return i;
198 return (-1);
201 /* --------------------------------------------------------------------------------------------- */
203 /* Selects from base the pos element */
204 static int
205 listbox_select_pos (WListbox * l, int base, int pos)
207 int last = l->count - 1;
209 base += pos;
210 base = min (base, last);
212 return base;
215 /* --------------------------------------------------------------------------------------------- */
217 static void
218 listbox_fwd (WListbox * l)
220 if (l->pos + 1 >= l->count)
221 listbox_select_first (l);
222 else
223 listbox_select_entry (l, l->pos + 1);
226 /* --------------------------------------------------------------------------------------------- */
228 static void
229 listbox_back (WListbox * l)
231 if (l->pos <= 0)
232 listbox_select_last (l);
233 else
234 listbox_select_entry (l, l->pos - 1);
237 /* --------------------------------------------------------------------------------------------- */
239 static cb_ret_t
240 listbox_execute_cmd (WListbox * l, unsigned long command)
242 cb_ret_t ret = MSG_HANDLED;
243 int i;
244 Widget *w = WIDGET (l);
246 switch (command)
248 case CK_Up:
249 listbox_back (l);
250 break;
251 case CK_Down:
252 listbox_fwd (l);
253 break;
254 case CK_Top:
255 listbox_select_first (l);
256 break;
257 case CK_Bottom:
258 listbox_select_last (l);
259 break;
260 case CK_PageUp:
261 for (i = 0; (i < w->lines - 1) && (l->pos > 0); i++)
262 listbox_back (l);
263 break;
264 case CK_PageDown:
265 for (i = 0; (i < w->lines - 1) && (l->pos < l->count - 1); i++)
266 listbox_fwd (l);
267 break;
268 case CK_Delete:
269 if (l->deletable)
271 gboolean is_last = (l->pos + 1 >= l->count);
272 gboolean is_more = (l->top + w->lines >= l->count);
274 listbox_remove_current (l);
275 if ((l->top > 0) && (is_last || is_more))
276 l->top--;
278 break;
279 case CK_Clear:
280 if (l->deletable && mc_global.widget.confirm_history_cleanup
281 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
282 && (query_dialog (Q_ ("DialogTitle|History cleanup"),
283 _("Do you want clean this history?"),
284 D_ERROR, 2, _("&Yes"), _("&No")) == 0))
285 listbox_remove_list (l);
286 break;
287 default:
288 ret = MSG_NOT_HANDLED;
291 return ret;
294 /* --------------------------------------------------------------------------------------------- */
296 /* Return MSG_HANDLED if we want a redraw */
297 static cb_ret_t
298 listbox_key (WListbox * l, int key)
300 unsigned long command;
302 if (l->list == NULL)
303 return MSG_NOT_HANDLED;
305 /* focus on listbox item N by '0'..'9' keys */
306 if (key >= '0' && key <= '9')
308 int oldpos = l->pos;
309 listbox_select_entry (l, key - '0');
311 /* need scroll to item? */
312 if (abs (oldpos - l->pos) > WIDGET (l)->lines)
313 l->top = l->pos;
315 return MSG_HANDLED;
318 command = keybind_lookup_keymap_command (listbox_map, key);
319 if (command == CK_IgnoreKey)
320 return MSG_NOT_HANDLED;
321 return listbox_execute_cmd (l, command);
324 /* --------------------------------------------------------------------------------------------- */
326 /* Listbox item adding function */
327 static inline void
328 listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
330 switch (pos)
332 case LISTBOX_APPEND_AT_END:
333 l->list = g_list_append (l->list, e);
334 break;
336 case LISTBOX_APPEND_BEFORE:
337 l->list = g_list_insert_before (l->list, g_list_nth (l->list, l->pos), e);
338 if (l->pos > 0)
339 l->pos--;
340 break;
342 case LISTBOX_APPEND_AFTER:
343 l->list = g_list_insert (l->list, e, l->pos + 1);
344 break;
346 case LISTBOX_APPEND_SORTED:
347 l->list = g_list_insert_sorted (l->list, e, (GCompareFunc) listbox_entry_cmp);
348 break;
350 default:
351 return;
354 l->count++;
357 /* --------------------------------------------------------------------------------------------- */
359 static inline void
360 listbox_destroy (WListbox * l)
362 listbox_remove_list (l);
365 /* --------------------------------------------------------------------------------------------- */
367 static cb_ret_t
368 listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
370 WListbox *l = (WListbox *) w;
371 WDialog *h = w->owner;
372 cb_ret_t ret_code;
374 switch (msg)
376 case MSG_INIT:
377 return MSG_HANDLED;
379 case MSG_HOTKEY:
381 int pos, action;
383 pos = listbox_check_hotkey (l, parm);
384 if (pos < 0)
385 return MSG_NOT_HANDLED;
387 listbox_select_entry (l, pos);
388 send_message (h, w, MSG_ACTION, l->pos, NULL);
390 if (l->callback != NULL)
391 action = l->callback (l);
392 else
393 action = LISTBOX_DONE;
395 if (action == LISTBOX_DONE)
397 h->ret_value = B_ENTER;
398 dlg_stop (h);
401 return MSG_HANDLED;
404 case MSG_KEY:
405 ret_code = listbox_key (l, parm);
406 if (ret_code != MSG_NOT_HANDLED)
408 listbox_draw (l, TRUE);
409 send_message (h, w, MSG_ACTION, l->pos, NULL);
411 return ret_code;
413 case MSG_ACTION:
414 return listbox_execute_cmd (l, parm);
416 case MSG_CURSOR:
417 widget_move (l, l->cursor_y, 0);
418 send_message (h, w, MSG_ACTION, l->pos, NULL);
419 return MSG_HANDLED;
421 case MSG_FOCUS:
422 case MSG_UNFOCUS:
423 case MSG_DRAW:
424 listbox_draw (l, msg != MSG_UNFOCUS);
425 return MSG_HANDLED;
427 case MSG_DESTROY:
428 listbox_destroy (l);
429 return MSG_HANDLED;
431 case MSG_RESIZE:
432 return MSG_HANDLED;
434 default:
435 return widget_default_callback (w, sender, msg, parm, data);
439 /* --------------------------------------------------------------------------------------------- */
441 static int
442 listbox_event (Gpm_Event * event, void *data)
444 WListbox *l = (WListbox *) data;
445 Widget *w = WIDGET (data);
447 if (!mouse_global_in_widget (event, w))
448 return MOU_UNHANDLED;
450 /* Single click */
451 if ((event->type & GPM_DOWN) != 0)
452 dlg_select_widget (l);
454 if (l->list == NULL)
455 return MOU_NORMAL;
457 if ((event->type & (GPM_DOWN | GPM_DRAG)) != 0)
459 int ret = MOU_REPEAT;
460 Gpm_Event local;
461 int i;
463 local = mouse_get_local (event, w);
464 if (local.y < 1)
465 for (i = -local.y; i >= 0; i--)
466 listbox_back (l);
467 else if (local.y > w->lines)
468 for (i = local.y - w->lines; i > 0; i--)
469 listbox_fwd (l);
470 else if ((local.buttons & GPM_B_UP) != 0)
472 listbox_back (l);
473 ret = MOU_NORMAL;
475 else if ((local.buttons & GPM_B_DOWN) != 0)
477 listbox_fwd (l);
478 ret = MOU_NORMAL;
480 else
481 listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
483 /* We need to refresh ourselves since the dialog manager doesn't */
484 /* know about this event */
485 listbox_draw (l, TRUE);
486 return ret;
489 /* Double click */
490 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE))
492 Gpm_Event local;
493 int action;
495 local = mouse_get_local (event, w);
496 dlg_select_widget (l);
497 listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
499 if (l->callback != NULL)
500 action = l->callback (l);
501 else
502 action = LISTBOX_DONE;
504 if (action == LISTBOX_DONE)
506 w->owner->ret_value = B_ENTER;
507 dlg_stop (w->owner);
511 return MOU_NORMAL;
514 /* --------------------------------------------------------------------------------------------- */
515 /*** public functions ****************************************************************************/
516 /* --------------------------------------------------------------------------------------------- */
518 WListbox *
519 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
521 WListbox *l;
522 Widget *w;
524 if (height <= 0)
525 height = 1;
527 l = g_new (WListbox, 1);
528 w = WIDGET (l);
529 init_widget (w, y, x, height, width, listbox_callback, listbox_event);
531 l->list = NULL;
532 l->top = l->pos = 0;
533 l->count = 0;
534 l->deletable = deletable;
535 l->callback = callback;
536 l->allow_duplicates = TRUE;
537 l->scrollbar = !mc_global.tty.slow_terminal;
538 widget_want_hotkey (w, TRUE);
539 widget_want_cursor (w, FALSE);
541 return l;
544 /* --------------------------------------------------------------------------------------------- */
547 listbox_search_text (WListbox * l, const char *text)
549 if (l != NULL)
551 int i;
552 GList *le;
554 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
556 WLEntry *e = (WLEntry *) le->data;
558 if (strcmp (e->text, text) == 0)
559 return i;
563 return (-1);
566 /* --------------------------------------------------------------------------------------------- */
568 /* Selects the first entry and scrolls the list to the top */
569 void
570 listbox_select_first (WListbox * l)
572 l->pos = l->top = 0;
575 /* --------------------------------------------------------------------------------------------- */
577 /* Selects the last entry and scrolls the list to the bottom */
578 void
579 listbox_select_last (WListbox * l)
581 int lines = WIDGET (l)->lines;
583 l->pos = l->count - 1;
584 l->top = l->count > lines ? l->count - lines : 0;
587 /* --------------------------------------------------------------------------------------------- */
589 void
590 listbox_select_entry (WListbox * l, int dest)
592 GList *le;
593 int pos;
594 gboolean top_seen = FALSE;
596 if (dest < 0)
597 return;
599 /* Special case */
600 for (pos = 0, le = l->list; le != NULL; pos++, le = g_list_next (le))
602 if (pos == l->top)
603 top_seen = TRUE;
605 if (pos == dest)
607 l->pos = dest;
608 if (!top_seen)
609 l->top = l->pos;
610 else
612 int lines = WIDGET (l)->lines;
614 if (l->pos - l->top >= lines)
615 l->top = l->pos - lines + 1;
617 return;
621 /* If we are unable to find it, set decent values */
622 l->pos = l->top = 0;
625 /* --------------------------------------------------------------------------------------------- */
627 /* Returns the current string text as well as the associated extra data */
628 void
629 listbox_get_current (WListbox * l, char **string, void **extra)
631 WLEntry *e = NULL;
632 gboolean ok;
634 if (l != NULL)
635 e = (WLEntry *) g_list_nth_data (l->list, l->pos);
637 ok = (e != NULL);
639 if (string != NULL)
640 *string = ok ? e->text : NULL;
642 if (extra != NULL)
643 *extra = ok ? e->data : NULL;
646 /* --------------------------------------------------------------------------------------------- */
648 void
649 listbox_remove_current (WListbox * l)
651 if ((l != NULL) && (l->count != 0))
653 GList *current;
655 current = g_list_nth (l->list, l->pos);
656 l->list = g_list_remove_link (l->list, current);
657 listbox_entry_free ((WLEntry *) current->data);
658 g_list_free_1 (current);
659 l->count--;
661 if (l->count == 0)
662 l->top = l->pos = 0;
663 else if (l->pos >= l->count)
664 l->pos = l->count - 1;
668 /* --------------------------------------------------------------------------------------------- */
670 void
671 listbox_set_list (WListbox * l, GList * list)
673 listbox_remove_list (l);
675 if (l != NULL)
677 l->list = list;
678 l->top = l->pos = 0;
679 l->count = g_list_length (list);
683 /* --------------------------------------------------------------------------------------------- */
685 void
686 listbox_remove_list (WListbox * l)
688 if ((l != NULL) && (l->count != 0))
690 g_list_foreach (l->list, (GFunc) listbox_entry_free, NULL);
691 g_list_free (l->list);
692 l->list = NULL;
693 l->count = l->pos = l->top = 0;
697 /* --------------------------------------------------------------------------------------------- */
699 char *
700 listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data)
702 WLEntry *entry;
704 if (l == NULL)
705 return NULL;
707 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
708 return NULL;
710 entry = g_new (WLEntry, 1);
711 entry->text = g_strdup (text);
712 entry->data = data;
713 entry->hotkey = hotkey;
715 listbox_append_item (l, entry, pos);
717 return entry->text;
720 /* --------------------------------------------------------------------------------------------- */