Code indentation.
[midnight-commander.git] / lib / widget / listbox.c
blob3e475ef435f9fe15e327d4a0039bd4a15796f0a0
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 const int max_line = l->widget.lines - 1;
87 int line = 0;
88 int i;
90 /* Are we at the top? */
91 widget_move (&l->widget, 0, l->widget.cols);
92 if (l->top == 0)
93 tty_print_one_vline (TRUE);
94 else
95 tty_print_char ('^');
97 /* Are we at the bottom? */
98 widget_move (&l->widget, max_line, l->widget.cols);
99 if ((l->top + l->widget.lines == l->count) || (l->widget.lines >= l->count))
100 tty_print_one_vline (TRUE);
101 else
102 tty_print_char ('v');
104 /* Now draw the nice relative pointer */
105 if (l->count != 0)
106 line = 1 + ((l->pos * (l->widget.lines - 2)) / l->count);
108 for (i = 1; i < max_line; i++)
110 widget_move (&l->widget, i, l->widget.cols);
111 if (i != line)
112 tty_print_one_vline (TRUE);
113 else
114 tty_print_char ('*');
118 /* --------------------------------------------------------------------------------------------- */
120 static void
121 listbox_draw (WListbox * l, gboolean focused)
123 const Dlg_head *h = l->widget.owner;
124 const gboolean disabled = (((Widget *) l)->options & W_DISABLED) != 0;
125 const int normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
126 /* *INDENT-OFF* */
127 int selc = disabled
128 ? DISABLED_COLOR
129 : focused
130 ? h->color[DLG_COLOR_HOT_FOCUS]
131 : h->color[DLG_COLOR_FOCUS];
132 /* *INDENT-ON* */
134 GList *le;
135 int pos;
136 int i;
137 int sel_line = -1;
139 le = g_list_nth (l->list, l->top);
140 /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
141 pos = (le == NULL) ? 0 : l->top;
143 for (i = 0; i < l->widget.lines; i++)
145 const char *text;
147 /* Display the entry */
148 if (pos == l->pos && sel_line == -1)
150 sel_line = i;
151 tty_setcolor (selc);
153 else
154 tty_setcolor (normalc);
156 widget_move (&l->widget, i, 1);
158 if ((i > 0 && pos >= l->count) || (l->list == NULL) || (le == NULL))
159 text = "";
160 else
162 WLEntry *e = (WLEntry *) le->data;
163 text = e->text;
164 le = g_list_next (le);
165 pos++;
168 tty_print_string (str_fit_to_term (text, l->widget.cols - 2, J_LEFT_FIT));
171 l->cursor_y = sel_line;
173 if (l->scrollbar && (l->count > l->widget.lines))
175 tty_setcolor (normalc);
176 listbox_drawscroll (l);
180 /* --------------------------------------------------------------------------------------------- */
182 static int
183 listbox_check_hotkey (WListbox * l, int key)
185 int i;
186 GList *le;
188 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
190 WLEntry *e = (WLEntry *) le->data;
192 if (e->hotkey == key)
193 return i;
196 return (-1);
199 /* --------------------------------------------------------------------------------------------- */
201 /* Selects from base the pos element */
202 static int
203 listbox_select_pos (WListbox * l, int base, int pos)
205 int last = l->count - 1;
207 base += pos;
208 base = min (base, last);
210 return base;
213 /* --------------------------------------------------------------------------------------------- */
215 static void
216 listbox_fwd (WListbox * l)
218 if (l->pos + 1 >= l->count)
219 listbox_select_first (l);
220 else
221 listbox_select_entry (l, l->pos + 1);
224 /* --------------------------------------------------------------------------------------------- */
226 static void
227 listbox_back (WListbox * l)
229 if (l->pos <= 0)
230 listbox_select_last (l);
231 else
232 listbox_select_entry (l, l->pos - 1);
235 /* --------------------------------------------------------------------------------------------- */
237 static cb_ret_t
238 listbox_execute_cmd (WListbox * l, unsigned long command)
240 cb_ret_t ret = MSG_HANDLED;
241 int i;
243 switch (command)
245 case CK_Up:
246 listbox_back (l);
247 break;
248 case CK_Down:
249 listbox_fwd (l);
250 break;
251 case CK_Top:
252 listbox_select_first (l);
253 break;
254 case CK_Bottom:
255 listbox_select_last (l);
256 break;
257 case CK_PageUp:
258 for (i = 0; (i < l->widget.lines - 1) && (l->pos > 0); i++)
259 listbox_back (l);
260 break;
261 case CK_PageDown:
262 for (i = 0; (i < l->widget.lines - 1) && (l->pos < l->count - 1); i++)
263 listbox_fwd (l);
264 break;
265 case CK_Delete:
266 if (l->deletable)
268 gboolean is_last = (l->pos + 1 >= l->count);
269 gboolean is_more = (l->top + l->widget.lines >= l->count);
271 listbox_remove_current (l);
272 if ((l->top > 0) && (is_last || is_more))
273 l->top--;
275 break;
276 case CK_Clear:
277 if (l->deletable && mc_global.widget.confirm_history_cleanup
278 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
279 && (query_dialog (Q_ ("DialogTitle|History cleanup"),
280 _("Do you want clean this history?"),
281 D_ERROR, 2, _("&Yes"), _("&No")) == 0))
282 listbox_remove_list (l);
283 break;
284 default:
285 ret = MSG_NOT_HANDLED;
288 return ret;
291 /* --------------------------------------------------------------------------------------------- */
293 /* Return MSG_HANDLED if we want a redraw */
294 static cb_ret_t
295 listbox_key (WListbox * l, int key)
297 unsigned long command;
299 if (l->list == NULL)
300 return MSG_NOT_HANDLED;
302 /* focus on listbox item N by '0'..'9' keys */
303 if (key >= '0' && key <= '9')
305 int oldpos = l->pos;
306 listbox_select_entry (l, key - '0');
308 /* need scroll to item? */
309 if (abs (oldpos - l->pos) > l->widget.lines)
310 l->top = l->pos;
312 return MSG_HANDLED;
315 command = keybind_lookup_keymap_command (listbox_map, key);
316 if (command == CK_IgnoreKey)
317 return MSG_NOT_HANDLED;
318 return listbox_execute_cmd (l, command);
321 /* --------------------------------------------------------------------------------------------- */
323 /* Listbox item adding function */
324 static inline void
325 listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
327 switch (pos)
329 case LISTBOX_APPEND_AT_END:
330 l->list = g_list_append (l->list, e);
331 break;
333 case LISTBOX_APPEND_BEFORE:
334 l->list = g_list_insert_before (l->list, g_list_nth (l->list, l->pos), e);
335 if (l->pos > 0)
336 l->pos--;
337 break;
339 case LISTBOX_APPEND_AFTER:
340 l->list = g_list_insert (l->list, e, l->pos + 1);
341 break;
343 case LISTBOX_APPEND_SORTED:
344 l->list = g_list_insert_sorted (l->list, e, (GCompareFunc) listbox_entry_cmp);
345 break;
347 default:
348 return;
351 l->count++;
354 /* --------------------------------------------------------------------------------------------- */
356 static inline void
357 listbox_destroy (WListbox * l)
359 listbox_remove_list (l);
362 /* --------------------------------------------------------------------------------------------- */
364 static cb_ret_t
365 listbox_callback (Widget * w, widget_msg_t msg, int parm)
367 WListbox *l = (WListbox *) w;
368 Dlg_head *h = l->widget.owner;
369 cb_ret_t ret_code;
371 switch (msg)
373 case WIDGET_INIT:
374 return MSG_HANDLED;
376 case WIDGET_HOTKEY:
378 int pos, action;
380 pos = listbox_check_hotkey (l, parm);
381 if (pos < 0)
382 return MSG_NOT_HANDLED;
384 listbox_select_entry (l, pos);
385 h->callback (h, w, DLG_ACTION, l->pos, NULL);
387 if (l->callback != NULL)
388 action = l->callback (l);
389 else
390 action = LISTBOX_DONE;
392 if (action == LISTBOX_DONE)
394 h->ret_value = B_ENTER;
395 dlg_stop (h);
398 return MSG_HANDLED;
401 case WIDGET_KEY:
402 ret_code = listbox_key (l, parm);
403 if (ret_code != MSG_NOT_HANDLED)
405 listbox_draw (l, TRUE);
406 h->callback (h, w, DLG_ACTION, l->pos, NULL);
408 return ret_code;
410 case WIDGET_COMMAND:
411 return listbox_execute_cmd (l, parm);
413 case WIDGET_CURSOR:
414 widget_move (&l->widget, l->cursor_y, 0);
415 h->callback (h, w, DLG_ACTION, l->pos, NULL);
416 return MSG_HANDLED;
418 case WIDGET_FOCUS:
419 case WIDGET_UNFOCUS:
420 case WIDGET_DRAW:
421 listbox_draw (l, msg != WIDGET_UNFOCUS);
422 return MSG_HANDLED;
424 case WIDGET_DESTROY:
425 listbox_destroy (l);
426 return MSG_HANDLED;
428 case WIDGET_RESIZED:
429 return MSG_HANDLED;
431 default:
432 return default_proc (msg, parm);
436 /* --------------------------------------------------------------------------------------------- */
438 static int
439 listbox_event (Gpm_Event * event, void *data)
441 WListbox *l = (WListbox *) data;
442 Widget *w = (Widget *) data;
444 if (!mouse_global_in_widget (event, w))
445 return MOU_UNHANDLED;
447 /* Single click */
448 if ((event->type & GPM_DOWN) != 0)
449 dlg_select_widget (l);
451 if (l->list == NULL)
452 return MOU_NORMAL;
454 if ((event->type & (GPM_DOWN | GPM_DRAG)) != 0)
456 int ret = MOU_REPEAT;
457 Gpm_Event local;
458 int i;
460 local = mouse_get_local (event, w);
461 if (local.y < 1)
462 for (i = -local.y; i >= 0; i--)
463 listbox_back (l);
464 else if (local.y > w->lines)
465 for (i = local.y - w->lines; i > 0; i--)
466 listbox_fwd (l);
467 else if ((local.buttons & GPM_B_UP) != 0)
469 listbox_back (l);
470 ret = MOU_NORMAL;
472 else if ((local.buttons & GPM_B_DOWN) != 0)
474 listbox_fwd (l);
475 ret = MOU_NORMAL;
477 else
478 listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
480 /* We need to refresh ourselves since the dialog manager doesn't */
481 /* know about this event */
482 listbox_draw (l, TRUE);
483 return ret;
486 /* Double click */
487 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE))
489 Gpm_Event local;
490 int action;
492 local = mouse_get_local (event, w);
493 dlg_select_widget (l);
494 listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
496 if (l->callback != NULL)
497 action = l->callback (l);
498 else
499 action = LISTBOX_DONE;
501 if (action == LISTBOX_DONE)
503 w->owner->ret_value = B_ENTER;
504 dlg_stop (w->owner);
508 return MOU_NORMAL;
511 /* --------------------------------------------------------------------------------------------- */
512 /*** public functions ****************************************************************************/
513 /* --------------------------------------------------------------------------------------------- */
515 WListbox *
516 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
518 WListbox *l;
520 if (height <= 0)
521 height = 1;
523 l = g_new (WListbox, 1);
524 init_widget (&l->widget, y, x, height, width, listbox_callback, listbox_event);
526 l->list = NULL;
527 l->top = l->pos = 0;
528 l->count = 0;
529 l->deletable = deletable;
530 l->callback = callback;
531 l->allow_duplicates = TRUE;
532 l->scrollbar = !mc_global.tty.slow_terminal;
533 widget_want_hotkey (l->widget, TRUE);
534 widget_want_cursor (l->widget, FALSE);
536 return l;
539 /* --------------------------------------------------------------------------------------------- */
542 listbox_search_text (WListbox * l, const char *text)
544 if (l != NULL)
546 int i;
547 GList *le;
549 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
551 WLEntry *e = (WLEntry *) le->data;
553 if (strcmp (e->text, text) == 0)
554 return i;
558 return (-1);
561 /* --------------------------------------------------------------------------------------------- */
563 /* Selects the first entry and scrolls the list to the top */
564 void
565 listbox_select_first (WListbox * l)
567 l->pos = l->top = 0;
570 /* --------------------------------------------------------------------------------------------- */
572 /* Selects the last entry and scrolls the list to the bottom */
573 void
574 listbox_select_last (WListbox * l)
576 l->pos = l->count - 1;
577 l->top = l->count > l->widget.lines ? l->count - l->widget.lines : 0;
580 /* --------------------------------------------------------------------------------------------- */
582 void
583 listbox_select_entry (WListbox * l, int dest)
585 GList *le;
586 int pos;
587 gboolean top_seen = FALSE;
589 if (dest < 0)
590 return;
592 /* Special case */
593 for (pos = 0, le = l->list; le != NULL; pos++, le = g_list_next (le))
595 if (pos == l->top)
596 top_seen = TRUE;
598 if (pos == dest)
600 l->pos = dest;
601 if (!top_seen)
602 l->top = l->pos;
603 else if (l->pos - l->top >= l->widget.lines)
604 l->top = l->pos - l->widget.lines + 1;
605 return;
609 /* If we are unable to find it, set decent values */
610 l->pos = l->top = 0;
613 /* --------------------------------------------------------------------------------------------- */
615 /* Returns the current string text as well as the associated extra data */
616 void
617 listbox_get_current (WListbox * l, char **string, void **extra)
619 WLEntry *e = NULL;
620 gboolean ok;
622 if (l != NULL)
623 e = (WLEntry *) g_list_nth_data (l->list, l->pos);
625 ok = (e != NULL);
627 if (string != NULL)
628 *string = ok ? e->text : NULL;
630 if (extra != NULL)
631 *extra = ok ? e->data : NULL;
634 /* --------------------------------------------------------------------------------------------- */
636 void
637 listbox_remove_current (WListbox * l)
639 if ((l != NULL) && (l->count != 0))
641 GList *current;
643 current = g_list_nth (l->list, l->pos);
644 l->list = g_list_remove_link (l->list, current);
645 listbox_entry_free ((WLEntry *) current->data);
646 g_list_free_1 (current);
647 l->count--;
649 if (l->count == 0)
650 l->top = l->pos = 0;
651 else if (l->pos >= l->count)
652 l->pos = l->count - 1;
656 /* --------------------------------------------------------------------------------------------- */
658 void
659 listbox_set_list (WListbox * l, GList * list)
661 listbox_remove_list (l);
663 if (l != NULL)
665 l->list = list;
666 l->top = l->pos = 0;
667 l->count = g_list_length (list);
671 /* --------------------------------------------------------------------------------------------- */
673 void
674 listbox_remove_list (WListbox * l)
676 if ((l != NULL) && (l->count != 0))
678 g_list_foreach (l->list, (GFunc) listbox_entry_free, NULL);
679 g_list_free (l->list);
680 l->list = NULL;
681 l->count = l->pos = l->top = 0;
685 /* --------------------------------------------------------------------------------------------- */
687 char *
688 listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data)
690 WLEntry *entry;
692 if (l == NULL)
693 return NULL;
695 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
696 return NULL;
698 entry = g_new (WLEntry, 1);
699 entry->text = g_strdup (text);
700 entry->data = data;
701 entry->hotkey = hotkey;
703 listbox_append_item (l, entry, pos);
705 return entry->text;
708 /* --------------------------------------------------------------------------------------------- */