Ticket #2598: u7z: Improve handling of missing p7zip binaries.
[midnight-commander.git] / lib / widget / listbox.c
blobda7c368018a14fd9e8a4ae00be39247e0b893863
1 /* Widgets for the Midnight Commander
3 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
6 Authors: 1994, 1995 Radek Doulik
7 1994, 1995 Miguel de Icaza
8 1995 Jakub Jelinek
9 1996 Andrej Borsenkow
10 1997 Norbert Warmuth
11 2009, 2010 Andrew Borodin
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 /** \file listbox.c
30 * \brief Source: WListbox widget
33 #include <config.h>
35 #include <stdlib.h>
37 #include "lib/global.h"
39 #include "lib/tty/tty.h"
40 #include "lib/tty/mouse.h"
41 #include "lib/skin.h"
42 #include "lib/strutil.h"
43 #include "lib/util.h" /* Q_() */
44 #include "lib/keybind.h" /* global_keymap_t */
45 #include "lib/widget.h"
47 /*** global variables ****************************************************************************/
49 const global_keymap_t *listbox_map = NULL;
51 /*** file scope macro definitions ****************************************************************/
53 /*** file scope type declarations ****************************************************************/
55 /*** file scope variables ************************************************************************/
57 /*** file scope functions ************************************************************************/
59 static int
60 listbox_entry_cmp (const void *a, const void *b)
62 const WLEntry *ea = (const WLEntry *) a;
63 const WLEntry *eb = (const WLEntry *) b;
65 return strcmp (ea->text, eb->text);
68 /* --------------------------------------------------------------------------------------------- */
70 static void
71 listbox_entry_free (void *data)
73 WLEntry *e = data;
74 g_free (e->text);
75 g_free (e);
78 /* --------------------------------------------------------------------------------------------- */
80 static void
81 listbox_drawscroll (WListbox * l)
83 const int max_line = l->widget.lines - 1;
84 int line = 0;
85 int i;
87 /* Are we at the top? */
88 widget_move (&l->widget, 0, l->widget.cols);
89 if (l->top == 0)
90 tty_print_one_vline (TRUE);
91 else
92 tty_print_char ('^');
94 /* Are we at the bottom? */
95 widget_move (&l->widget, max_line, l->widget.cols);
96 if ((l->top + l->widget.lines == l->count) || (l->widget.lines >= l->count))
97 tty_print_one_vline (TRUE);
98 else
99 tty_print_char ('v');
101 /* Now draw the nice relative pointer */
102 if (l->count != 0)
103 line = 1 + ((l->pos * (l->widget.lines - 2)) / l->count);
105 for (i = 1; i < max_line; i++)
107 widget_move (&l->widget, i, l->widget.cols);
108 if (i != line)
109 tty_print_one_vline (TRUE);
110 else
111 tty_print_char ('*');
115 /* --------------------------------------------------------------------------------------------- */
117 static void
118 listbox_draw (WListbox * l, gboolean focused)
120 const Dlg_head *h = l->widget.owner;
121 const gboolean disabled = (((Widget *) l)->options & W_DISABLED) != 0;
122 const int normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
123 int selc =
124 disabled ? DISABLED_COLOR : focused ? h->color[DLG_COLOR_HOT_FOCUS] : h->
125 color[DLG_COLOR_FOCUS];
127 GList *le;
128 int pos;
129 int i;
130 int sel_line = -1;
132 le = g_list_nth (l->list, l->top);
133 /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
134 pos = (le == NULL) ? 0 : l->top;
136 for (i = 0; i < l->widget.lines; i++)
138 const char *text;
140 /* Display the entry */
141 if (pos == l->pos && sel_line == -1)
143 sel_line = i;
144 tty_setcolor (selc);
146 else
147 tty_setcolor (normalc);
149 widget_move (&l->widget, i, 1);
151 if ((i > 0 && pos >= l->count) || (l->list == NULL) || (le == NULL))
152 text = "";
153 else
155 WLEntry *e = (WLEntry *) le->data;
156 text = e->text;
157 le = g_list_next (le);
158 pos++;
161 tty_print_string (str_fit_to_term (text, l->widget.cols - 2, J_LEFT_FIT));
164 l->cursor_y = sel_line;
166 if (l->scrollbar && (l->count > l->widget.lines))
168 tty_setcolor (normalc);
169 listbox_drawscroll (l);
173 /* --------------------------------------------------------------------------------------------- */
175 static int
176 listbox_check_hotkey (WListbox * l, int key)
178 int i;
179 GList *le;
181 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
183 WLEntry *e = (WLEntry *) le->data;
185 if (e->hotkey == key)
186 return i;
189 return (-1);
192 /* --------------------------------------------------------------------------------------------- */
194 /* Selects from base the pos element */
195 static int
196 listbox_select_pos (WListbox * l, int base, int pos)
198 int last = l->count - 1;
200 base += pos;
201 base = min (base, last);
203 return base;
206 /* --------------------------------------------------------------------------------------------- */
208 static void
209 listbox_fwd (WListbox * l)
211 if (l->pos + 1 >= l->count)
212 listbox_select_first (l);
213 else
214 listbox_select_entry (l, l->pos + 1);
217 /* --------------------------------------------------------------------------------------------- */
219 static void
220 listbox_back (WListbox * l)
222 if (l->pos <= 0)
223 listbox_select_last (l);
224 else
225 listbox_select_entry (l, l->pos - 1);
228 /* --------------------------------------------------------------------------------------------- */
230 static cb_ret_t
231 listbox_execute_cmd (WListbox * l, unsigned long command)
233 cb_ret_t ret = MSG_HANDLED;
234 int i;
236 switch (command)
238 case CK_Up:
239 listbox_back (l);
240 break;
241 case CK_Down:
242 listbox_fwd (l);
243 break;
244 case CK_Top:
245 listbox_select_first (l);
246 break;
247 case CK_Bottom:
248 listbox_select_last (l);
249 break;
250 case CK_PageUp:
251 for (i = 0; (i < l->widget.lines - 1) && (l->pos > 0); i++)
252 listbox_back (l);
253 break;
254 case CK_PageDown:
255 for (i = 0; (i < l->widget.lines - 1) && (l->pos < l->count - 1); i++)
256 listbox_fwd (l);
257 break;
258 case CK_Delete:
259 if (l->deletable)
261 gboolean is_last = (l->pos + 1 >= l->count);
262 gboolean is_more = (l->top + l->widget.lines >= l->count);
264 listbox_remove_current (l);
265 if ((l->top > 0) && (is_last || is_more))
266 l->top--;
268 break;
269 case CK_Clear:
270 if (l->deletable && mc_global.widget.confirm_history_cleanup
271 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
272 && (query_dialog (Q_ ("DialogTitle|History cleanup"),
273 _("Do you want clean this history?"),
274 D_ERROR, 2, _("&Yes"), _("&No")) == 0))
275 listbox_remove_list (l);
276 break;
277 default:
278 ret = MSG_NOT_HANDLED;
281 return ret;
284 /* --------------------------------------------------------------------------------------------- */
286 /* Return MSG_HANDLED if we want a redraw */
287 static cb_ret_t
288 listbox_key (WListbox * l, int key)
290 unsigned long command;
292 if (l->list == NULL)
293 return MSG_NOT_HANDLED;
295 /* focus on listbox item N by '0'..'9' keys */
296 if (key >= '0' && key <= '9')
298 int oldpos = l->pos;
299 listbox_select_entry (l, key - '0');
301 /* need scroll to item? */
302 if (abs (oldpos - l->pos) > l->widget.lines)
303 l->top = l->pos;
305 return MSG_HANDLED;
308 command = keybind_lookup_keymap_command (listbox_map, key);
309 if (command == CK_IgnoreKey)
310 return MSG_NOT_HANDLED;
311 return listbox_execute_cmd (l, command);
314 /* --------------------------------------------------------------------------------------------- */
316 /* Listbox item adding function */
317 static inline void
318 listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
320 switch (pos)
322 case LISTBOX_APPEND_AT_END:
323 l->list = g_list_append (l->list, e);
324 break;
326 case LISTBOX_APPEND_BEFORE:
327 l->list = g_list_insert_before (l->list, g_list_nth (l->list, l->pos), e);
328 if (l->pos > 0)
329 l->pos--;
330 break;
332 case LISTBOX_APPEND_AFTER:
333 l->list = g_list_insert (l->list, e, l->pos + 1);
334 break;
336 case LISTBOX_APPEND_SORTED:
337 l->list = g_list_insert_sorted (l->list, e, (GCompareFunc) listbox_entry_cmp);
338 break;
340 default:
341 return;
344 l->count++;
347 /* --------------------------------------------------------------------------------------------- */
349 static inline void
350 listbox_destroy (WListbox * l)
352 listbox_remove_list (l);
355 /* --------------------------------------------------------------------------------------------- */
357 static cb_ret_t
358 listbox_callback (Widget * w, widget_msg_t msg, int parm)
360 WListbox *l = (WListbox *) w;
361 Dlg_head *h = l->widget.owner;
362 cb_ret_t ret_code;
364 switch (msg)
366 case WIDGET_INIT:
367 return MSG_HANDLED;
369 case WIDGET_HOTKEY:
371 int pos, action;
373 pos = listbox_check_hotkey (l, parm);
374 if (pos < 0)
375 return MSG_NOT_HANDLED;
377 listbox_select_entry (l, pos);
378 h->callback (h, w, DLG_ACTION, l->pos, NULL);
380 if (l->callback != NULL)
381 action = l->callback (l);
382 else
383 action = LISTBOX_DONE;
385 if (action == LISTBOX_DONE)
387 h->ret_value = B_ENTER;
388 dlg_stop (h);
391 return MSG_HANDLED;
394 case WIDGET_KEY:
395 ret_code = listbox_key (l, parm);
396 if (ret_code != MSG_NOT_HANDLED)
398 listbox_draw (l, TRUE);
399 h->callback (h, w, DLG_ACTION, l->pos, NULL);
401 return ret_code;
403 case WIDGET_COMMAND:
404 return listbox_execute_cmd (l, parm);
406 case WIDGET_CURSOR:
407 widget_move (&l->widget, l->cursor_y, 0);
408 h->callback (h, w, DLG_ACTION, l->pos, NULL);
409 return MSG_HANDLED;
411 case WIDGET_FOCUS:
412 case WIDGET_UNFOCUS:
413 case WIDGET_DRAW:
414 listbox_draw (l, msg != WIDGET_UNFOCUS);
415 return MSG_HANDLED;
417 case WIDGET_DESTROY:
418 listbox_destroy (l);
419 return MSG_HANDLED;
421 case WIDGET_RESIZED:
422 return MSG_HANDLED;
424 default:
425 return default_proc (msg, parm);
429 /* --------------------------------------------------------------------------------------------- */
431 static int
432 listbox_event (Gpm_Event * event, void *data)
434 WListbox *l = data;
435 int i;
437 Dlg_head *h = l->widget.owner;
439 /* Single click */
440 if (event->type & GPM_DOWN)
441 dlg_select_widget (l);
443 if (l->list == NULL)
444 return MOU_NORMAL;
446 if (event->type & (GPM_DOWN | GPM_DRAG))
448 int ret = MOU_REPEAT;
450 if (event->x < 0 || event->x > l->widget.cols)
451 return ret;
453 if (event->y < 1)
454 for (i = -event->y; i >= 0; i--)
455 listbox_back (l);
456 else if (event->y > l->widget.lines)
457 for (i = event->y - l->widget.lines; i > 0; i--)
458 listbox_fwd (l);
459 else if (event->buttons & GPM_B_UP)
461 listbox_back (l);
462 ret = MOU_NORMAL;
464 else if (event->buttons & GPM_B_DOWN)
466 listbox_fwd (l);
467 ret = MOU_NORMAL;
469 else
470 listbox_select_entry (l, listbox_select_pos (l, l->top, event->y - 1));
472 /* We need to refresh ourselves since the dialog manager doesn't */
473 /* know about this event */
474 listbox_draw (l, TRUE);
475 return ret;
478 /* Double click */
479 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE))
481 int action;
483 if (event->x < 0 || event->x >= l->widget.cols
484 || event->y < 1 || event->y > l->widget.lines)
485 return MOU_NORMAL;
487 dlg_select_widget (l);
488 listbox_select_entry (l, listbox_select_pos (l, l->top, event->y - 1));
490 if (l->callback != NULL)
491 action = l->callback (l);
492 else
493 action = LISTBOX_DONE;
495 if (action == LISTBOX_DONE)
497 h->ret_value = B_ENTER;
498 dlg_stop (h);
499 return MOU_NORMAL;
502 return MOU_NORMAL;
505 /* --------------------------------------------------------------------------------------------- */
506 /*** public functions ****************************************************************************/
507 /* --------------------------------------------------------------------------------------------- */
509 WListbox *
510 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
512 WListbox *l;
514 if (height <= 0)
515 height = 1;
517 l = g_new (WListbox, 1);
518 init_widget (&l->widget, y, x, height, width, listbox_callback, listbox_event);
520 l->list = NULL;
521 l->top = l->pos = 0;
522 l->count = 0;
523 l->deletable = deletable;
524 l->callback = callback;
525 l->allow_duplicates = TRUE;
526 l->scrollbar = !mc_global.tty.slow_terminal;
527 widget_want_hotkey (l->widget, TRUE);
528 widget_want_cursor (l->widget, FALSE);
530 return l;
533 /* --------------------------------------------------------------------------------------------- */
536 listbox_search_text (WListbox * l, const char *text)
538 if (l != NULL)
540 int i;
541 GList *le;
543 for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
545 WLEntry *e = (WLEntry *) le->data;
547 if (strcmp (e->text, text) == 0)
548 return i;
552 return (-1);
555 /* --------------------------------------------------------------------------------------------- */
557 /* Selects the first entry and scrolls the list to the top */
558 void
559 listbox_select_first (WListbox * l)
561 l->pos = l->top = 0;
564 /* --------------------------------------------------------------------------------------------- */
566 /* Selects the last entry and scrolls the list to the bottom */
567 void
568 listbox_select_last (WListbox * l)
570 l->pos = l->count - 1;
571 l->top = l->count > l->widget.lines ? l->count - l->widget.lines : 0;
574 /* --------------------------------------------------------------------------------------------- */
576 void
577 listbox_select_entry (WListbox * l, int dest)
579 GList *le;
580 int pos;
581 gboolean top_seen = FALSE;
583 if (dest < 0)
584 return;
586 /* Special case */
587 for (pos = 0, le = l->list; le != NULL; pos++, le = g_list_next (le))
589 if (pos == l->top)
590 top_seen = TRUE;
592 if (pos == dest)
594 l->pos = dest;
595 if (!top_seen)
596 l->top = l->pos;
597 else if (l->pos - l->top >= l->widget.lines)
598 l->top = l->pos - l->widget.lines + 1;
599 return;
603 /* If we are unable to find it, set decent values */
604 l->pos = l->top = 0;
607 /* --------------------------------------------------------------------------------------------- */
609 /* Returns the current string text as well as the associated extra data */
610 void
611 listbox_get_current (WListbox * l, char **string, void **extra)
613 WLEntry *e = NULL;
614 gboolean ok;
616 if (l != NULL)
617 e = (WLEntry *) g_list_nth_data (l->list, l->pos);
619 ok = (e != NULL);
621 if (string != NULL)
622 *string = ok ? e->text : NULL;
624 if (extra != NULL)
625 *extra = ok ? e->data : NULL;
628 /* --------------------------------------------------------------------------------------------- */
630 void
631 listbox_remove_current (WListbox * l)
633 if ((l != NULL) && (l->count != 0))
635 GList *current;
637 current = g_list_nth (l->list, l->pos);
638 l->list = g_list_remove_link (l->list, current);
639 listbox_entry_free ((WLEntry *) current->data);
640 g_list_free_1 (current);
641 l->count--;
643 if (l->count == 0)
644 l->top = l->pos = 0;
645 else if (l->pos >= l->count)
646 l->pos = l->count - 1;
650 /* --------------------------------------------------------------------------------------------- */
652 void
653 listbox_set_list (WListbox * l, GList * list)
655 listbox_remove_list (l);
657 if (l != NULL)
659 l->list = list;
660 l->top = l->pos = 0;
661 l->count = g_list_length (list);
665 /* --------------------------------------------------------------------------------------------- */
667 void
668 listbox_remove_list (WListbox * l)
670 if ((l != NULL) && (l->count != 0))
672 g_list_foreach (l->list, (GFunc) listbox_entry_free, NULL);
673 g_list_free (l->list);
674 l->list = NULL;
675 l->count = l->pos = l->top = 0;
679 /* --------------------------------------------------------------------------------------------- */
681 char *
682 listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data)
684 WLEntry *entry;
686 if (l == NULL)
687 return NULL;
689 if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
690 return NULL;
692 entry = g_new (WLEntry, 1);
693 entry->text = g_strdup (text);
694 entry->data = data;
695 entry->hotkey = hotkey;
697 listbox_append_item (l, entry, pos);
699 return entry->text;
702 /* --------------------------------------------------------------------------------------------- */