2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2015
5 Free Software Foundation, Inc.
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
11 Andrej Borsenkow, 1996
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/>.
32 * \brief Source: WListbox widget
39 #include "lib/global.h"
41 #include "lib/tty/tty.h"
42 #include "lib/tty/mouse.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 ************************************************************************/
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
;
72 return strcmp (ea
->text
, eb
->text
);
75 /* --------------------------------------------------------------------------------------------- */
78 listbox_entry_free (void *data
)
88 /* --------------------------------------------------------------------------------------------- */
91 listbox_drawscroll (WListbox
* l
)
93 Widget
*w
= WIDGET (l
);
94 int max_line
= w
->lines
- 1;
99 /* Are we at the top? */
100 widget_move (w
, 0, w
->cols
);
102 tty_print_one_vline (TRUE
);
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
);
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
);
123 tty_print_one_vline (TRUE
);
125 tty_print_char ('*');
129 /* --------------------------------------------------------------------------------------------- */
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
];
142 ? h
->color
[DLG_COLOR_HOT_FOCUS
]
143 : h
->color
[DLG_COLOR_FOCUS
];
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)
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
);
181 le
= g_list_next (le
);
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 /* --------------------------------------------------------------------------------------------- */
200 listbox_check_hotkey (WListbox
* l
, int key
)
202 if (!listbox_is_empty (l
))
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
)
219 /* --------------------------------------------------------------------------------------------- */
221 /* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
223 listbox_y_pos (WListbox
* l
, int y
)
225 return min (l
->top
+ y
, LISTBOX_LAST (l
));
228 /* --------------------------------------------------------------------------------------------- */
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);
236 listbox_select_first (l
);
239 /* --------------------------------------------------------------------------------------------- */
242 listbox_fwd_n (WListbox
* l
, int n
)
244 listbox_select_entry (l
, min (l
->pos
+ n
, LISTBOX_LAST (l
)));
247 /* --------------------------------------------------------------------------------------------- */
250 listbox_back (WListbox
* l
, gboolean wrap
)
253 listbox_select_entry (l
, l
->pos
- 1);
255 listbox_select_last (l
);
258 /* --------------------------------------------------------------------------------------------- */
261 listbox_back_n (WListbox
* l
, int n
)
263 listbox_select_entry (l
, max (l
->pos
- n
, 0));
266 /* --------------------------------------------------------------------------------------------- */
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
;
280 listbox_back (l
, TRUE
);
283 listbox_fwd (l
, TRUE
);
286 listbox_select_first (l
);
289 listbox_select_last (l
);
292 listbox_back_n (l
, w
->lines
- 1);
295 listbox_fwd_n (l
, w
->lines
- 1);
300 gboolean is_last
, is_more
;
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
))
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
);
322 ret
= MSG_NOT_HANDLED
;
328 /* --------------------------------------------------------------------------------------------- */
330 /* Return MSG_HANDLED if we want a redraw */
332 listbox_key (WListbox
* l
, int key
)
334 unsigned long command
;
337 return MSG_NOT_HANDLED
;
339 /* focus on listbox item N by '0'..'9' keys */
340 if (key
>= '0' && key
<= '9')
343 listbox_select_entry (l
, key
- '0');
345 /* need scroll to item? */
346 if (abs (oldpos
- l
->pos
) > WIDGET (l
)->lines
)
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 */
362 listbox_append_item (WListbox
* l
, WLEntry
* e
, listbox_append_t pos
)
366 l
->list
= g_queue_new ();
367 pos
= LISTBOX_APPEND_AT_END
;
372 case LISTBOX_APPEND_AT_END
:
373 g_queue_push_tail (l
->list
, e
);
376 case LISTBOX_APPEND_BEFORE
:
377 g_queue_insert_before (l
->list
, g_queue_peek_nth_link (l
->list
, (guint
) l
->pos
), e
);
380 case LISTBOX_APPEND_AFTER
:
381 g_queue_insert_after (l
->list
, g_queue_peek_nth_link (l
->list
, (guint
) l
->pos
), e
);
384 case LISTBOX_APPEND_SORTED
:
385 g_queue_insert_sorted (l
->list
, e
, (GCompareDataFunc
) listbox_entry_cmp
, NULL
);
393 /* --------------------------------------------------------------------------------------------- */
396 listbox_destroy (WListbox
* l
)
398 listbox_remove_list (l
);
401 /* --------------------------------------------------------------------------------------------- */
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
;
419 pos
= listbox_check_hotkey (l
, parm
);
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
);
429 action
= LISTBOX_DONE
;
431 if (action
== LISTBOX_DONE
)
433 h
->ret_value
= B_ENTER
;
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
);
450 return listbox_execute_cmd (l
, parm
);
453 widget_move (l
, l
->cursor_y
, 0);
454 send_message (h
, w
, MSG_ACTION
, l
->pos
, NULL
);
460 listbox_draw (l
, msg
!= MSG_UNFOCUS
);
471 return widget_default_callback (w
, sender
, msg
, parm
, data
);
475 /* --------------------------------------------------------------------------------------------- */
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
;
487 if ((event
->type
& GPM_DOWN
) != 0)
488 dlg_select_widget (l
);
490 if (listbox_is_empty (l
))
493 if ((event
->type
& (GPM_DOWN
| GPM_DRAG
)) != 0)
495 int ret
= MOU_REPEAT
;
498 local
= mouse_get_local (event
, w
);
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
);
508 else if ((local
.buttons
& GPM_B_DOWN
) != 0)
510 listbox_fwd (l
, FALSE
);
514 listbox_select_entry (l
, listbox_y_pos (l
, local
.y
- 1));
516 listbox_draw (l
, TRUE
);
521 if ((event
->type
& (GPM_DOUBLE
| GPM_UP
)) == (GPM_UP
| GPM_DOUBLE
))
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
);
533 action
= LISTBOX_DONE
;
535 if (action
== LISTBOX_DONE
)
537 w
->owner
->ret_value
= B_ENTER
;
545 /* --------------------------------------------------------------------------------------------- */
546 /*** public functions ****************************************************************************/
547 /* --------------------------------------------------------------------------------------------- */
550 listbox_new (int y
, int x
, int height
, int width
, gboolean deletable
, lcback_fn callback
)
558 l
= g_new (WListbox
, 1);
560 widget_init (w
, y
, x
, height
, width
, listbox_callback
, listbox_event
);
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
);
574 /* --------------------------------------------------------------------------------------------- */
577 listbox_search_text (WListbox
* l
, const char *text
)
579 if (!listbox_is_empty (l
))
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)
596 /* --------------------------------------------------------------------------------------------- */
598 /* Selects the first entry and scrolls the list to the top */
600 listbox_select_first (WListbox
* l
)
605 /* --------------------------------------------------------------------------------------------- */
607 /* Selects the last entry and scrolls the list to the bottom */
609 listbox_select_last (WListbox
* l
)
611 int lines
= WIDGET (l
)->lines
;
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 /* --------------------------------------------------------------------------------------------- */
624 listbox_select_entry (WListbox
* l
, int dest
)
628 gboolean top_seen
= FALSE
;
630 if (listbox_is_empty (l
) || dest
< 0)
634 for (pos
= 0, le
= g_queue_peek_head_link (l
->list
); le
!= NULL
; pos
++, le
= g_list_next (le
))
646 int lines
= WIDGET (l
)->lines
;
648 if (l
->pos
- l
->top
>= lines
)
649 l
->top
= l
->pos
- lines
+ 1;
655 /* If we are unable to find it, set decent values */
659 /* --------------------------------------------------------------------------------------------- */
661 /* Returns the current string text as well as the associated extra data */
663 listbox_get_current (WListbox
* l
, char **string
, void **extra
)
669 e
= listbox_get_nth_item (l
, l
->pos
);
674 *string
= ok
? e
->text
: NULL
;
677 *extra
= ok
? e
->data
: NULL
;
680 /* --------------------------------------------------------------------------------------------- */
683 listbox_get_nth_item (const WListbox
* l
, int pos
)
685 if (!listbox_is_empty (l
) && pos
>= 0)
689 item
= g_queue_peek_nth_link (l
->list
, (guint
) pos
);
691 return LENTRY (item
->data
);
697 /* --------------------------------------------------------------------------------------------- */
700 listbox_get_first_link (const WListbox
* l
)
702 return (l
== NULL
|| l
->list
== NULL
) ? NULL
: g_queue_peek_head_link (l
->list
);
705 /* --------------------------------------------------------------------------------------------- */
708 listbox_remove_current (WListbox
* l
)
710 if (!listbox_is_empty (l
))
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
);
723 else if (l
->pos
>= length
)
728 /* --------------------------------------------------------------------------------------------- */
731 listbox_is_empty (const WListbox
* l
)
733 return (l
== NULL
|| l
->list
== NULL
|| g_queue_is_empty (l
->list
));
736 /* --------------------------------------------------------------------------------------------- */
739 listbox_set_list (WListbox
* l
, GList
* list
)
741 listbox_remove_list (l
);
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
);
756 /* --------------------------------------------------------------------------------------------- */
759 listbox_remove_list (WListbox
* l
)
765 g_queue_foreach (l
->list
, (GFunc
) listbox_entry_free
, NULL
);
766 g_queue_free (l
->list
);
774 /* --------------------------------------------------------------------------------------------- */
777 listbox_add_item (WListbox
* l
, listbox_append_t pos
, int hotkey
, const char *text
, void *data
,
785 if (!l
->allow_duplicates
&& (listbox_search_text (l
, text
) >= 0))
788 entry
= g_new (WLEntry
, 1);
789 entry
->text
= g_strdup (text
);
791 entry
->free_data
= free_data
;
792 entry
->hotkey
= hotkey
;
794 listbox_append_item (l
, entry
, pos
);
799 /* --------------------------------------------------------------------------------------------- */