root/lib/widget/listbox.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. listbox_entry_cmp
  2. listbox_entry_free
  3. listbox_drawscroll
  4. listbox_draw
  5. listbox_check_hotkey
  6. listbox_y_pos
  7. listbox_fwd
  8. listbox_fwd_n
  9. listbox_back
  10. listbox_back_n
  11. listbox_execute_cmd
  12. listbox_key
  13. listbox_add_entry
  14. listbox_on_change
  15. listbox_do_action
  16. listbox_run_hotkey
  17. listbox_destroy
  18. listbox_callback
  19. listbox_mouse_callback
  20. listbox_new
  21. listbox_search_text
  22. listbox_search_data
  23. listbox_select_first
  24. listbox_select_last
  25. listbox_set_current
  26. listbox_get_length
  27. listbox_get_current
  28. listbox_get_nth_entry
  29. listbox_get_first_link
  30. listbox_remove_current
  31. listbox_is_empty
  32. listbox_set_list
  33. listbox_remove_list
  34. listbox_add_item
  35. listbox_add_item_take

   1 /*
   2    Widgets for the Midnight Commander
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   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-2022
  14 
  15    This file is part of the Midnight Commander.
  16 
  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.
  21 
  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.
  26 
  27    You should have received a copy of the GNU General Public License
  28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  29  */
  30 
  31 /** \file listbox.c
  32  *  \brief Source: WListbox widget
  33  */
  34 
  35 #include <config.h>
  36 
  37 #include <stdlib.h>
  38 
  39 #include "lib/global.h"
  40 
  41 #include "lib/tty/tty.h"
  42 #include "lib/skin.h"
  43 #include "lib/strutil.h"
  44 #include "lib/util.h"  // Q_()
  45 #include "lib/widget.h"
  46 
  47 /*** global variables ****************************************************************************/
  48 
  49 const global_keymap_t *listbox_map = NULL;
  50 
  51 /*** file scope macro definitions ****************************************************************/
  52 
  53 /* Gives the position of the last item. */
  54 #define LISTBOX_LAST(l) (listbox_is_empty (l) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
  55 
  56 /*** file scope type declarations ****************************************************************/
  57 
  58 /*** forward declarations (file scope functions) *************************************************/
  59 
  60 /*** file scope variables ************************************************************************/
  61 
  62 /* --------------------------------------------------------------------------------------------- */
  63 /*** file scope functions ************************************************************************/
  64 /* --------------------------------------------------------------------------------------------- */
  65 
  66 static int
  67 listbox_entry_cmp (const void *a, const void *b, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help]  */
  68 {
  69     const WLEntry *ea = (const WLEntry *) a;
  70     const WLEntry *eb = (const WLEntry *) b;
  71 
  72     (void) user_data;
  73 
  74     return strcmp (ea->text, eb->text);
  75 }
  76 
  77 /* --------------------------------------------------------------------------------------------- */
  78 
  79 static void
  80 listbox_entry_free (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
  81 {
  82     WLEntry *e = data;
  83 
  84     g_free (e->text);
  85     if (e->free_data)
  86         g_free (e->data);
  87     g_free (e);
  88 }
  89 
  90 /* --------------------------------------------------------------------------------------------- */
  91 
  92 static void
  93 listbox_drawscroll (const WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
  94 {
  95     const WRect *w = &CONST_WIDGET (l)->rect;
  96     int max_line = w->lines - 1;
  97     int line = 0;
  98     int i;
  99     int length;
 100 
 101     // Are we at the top?
 102     widget_gotoyx (l, 0, w->cols);
 103     if (l->top == 0)
 104         tty_print_one_vline (TRUE);
 105     else
 106         tty_print_char ('^');
 107 
 108     length = g_queue_get_length (l->list);
 109 
 110     // Are we at the bottom?
 111     widget_gotoyx (w, max_line, w->cols);
 112     if (l->top + w->lines == length || w->lines >= length)
 113         tty_print_one_vline (TRUE);
 114     else
 115         tty_print_char ('v');
 116 
 117     // Now draw the nice relative pointer
 118     if (length != 0)
 119         line = 1 + ((l->current * (w->lines - 2)) / length);
 120 
 121     for (i = 1; i < max_line; i++)
 122     {
 123         widget_gotoyx (l, i, w->cols);
 124         if (i != line)
 125             tty_print_one_vline (TRUE);
 126         else
 127             tty_print_char ('*');
 128     }
 129 }
 130 
 131 /* --------------------------------------------------------------------------------------------- */
 132 
 133 static void
 134 listbox_draw (WListbox *l, gboolean focused)
     /* [previous][next][first][last][top][bottom][index][help]  */
 135 {
 136     Widget *wl = WIDGET (l);
 137     const WRect *w = &CONST_WIDGET (l)->rect;
 138     const int *colors;
 139     gboolean disabled;
 140     int normalc, selc;
 141     int length = 0;
 142     GList *le = NULL;
 143     int pos;
 144     int i;
 145     int sel_line = -1;
 146 
 147     colors = widget_get_colors (wl);
 148 
 149     disabled = widget_get_state (wl, WST_DISABLED);
 150     normalc = disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL];
 151     selc = disabled ? DISABLED_COLOR : colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_FOCUS];
 152 
 153     if (l->list != NULL)
 154     {
 155         length = g_queue_get_length (l->list);
 156         le = g_queue_peek_nth_link (l->list, (guint) l->top);
 157     }
 158 
 159     //    pos = (le == NULL) ? 0 : g_list_position (l->list, le);
 160     pos = (le == NULL) ? 0 : l->top;
 161 
 162     for (i = 0; i < w->lines; i++)
 163     {
 164         const char *text = "";
 165 
 166         // Display the entry
 167         if (pos == l->current && sel_line == -1)
 168         {
 169             sel_line = i;
 170             tty_setcolor (selc);
 171         }
 172         else
 173             tty_setcolor (normalc);
 174 
 175         widget_gotoyx (l, i, 1);
 176 
 177         if (l->list != NULL && le != NULL && (i == 0 || pos < length))
 178         {
 179             WLEntry *e = LENTRY (le->data);
 180 
 181             text = e->text;
 182             le = g_list_next (le);
 183             pos++;
 184         }
 185 
 186         tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
 187     }
 188 
 189     l->cursor_y = sel_line;
 190 
 191     if (l->scrollbar && length > w->lines)
 192     {
 193         tty_setcolor (normalc);
 194         listbox_drawscroll (l);
 195     }
 196 }
 197 
 198 /* --------------------------------------------------------------------------------------------- */
 199 
 200 static int
 201 listbox_check_hotkey (WListbox *l, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 202 {
 203     if (!listbox_is_empty (l))
 204     {
 205         int i;
 206         GList *le;
 207 
 208         for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
 209         {
 210             WLEntry *e = LENTRY (le->data);
 211 
 212             if (e->hotkey == key)
 213                 return i;
 214         }
 215     }
 216 
 217     return (-1);
 218 }
 219 
 220 /* --------------------------------------------------------------------------------------------- */
 221 
 222 /* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
 223 static int
 224 listbox_y_pos (WListbox *l, int y)
     /* [previous][next][first][last][top][bottom][index][help]  */
 225 {
 226     return MIN (l->top + y, LISTBOX_LAST (l));
 227 }
 228 
 229 /* --------------------------------------------------------------------------------------------- */
 230 
 231 static void
 232 listbox_fwd (WListbox *l, gboolean wrap)
     /* [previous][next][first][last][top][bottom][index][help]  */
 233 {
 234     if (!listbox_is_empty (l))
 235     {
 236         if ((guint) l->current + 1 < g_queue_get_length (l->list))
 237             listbox_set_current (l, l->current + 1);
 238         else if (wrap)
 239             listbox_select_first (l);
 240     }
 241 }
 242 
 243 /* --------------------------------------------------------------------------------------------- */
 244 
 245 static void
 246 listbox_fwd_n (WListbox *l, int n)
     /* [previous][next][first][last][top][bottom][index][help]  */
 247 {
 248     listbox_set_current (l, MIN (l->current + n, LISTBOX_LAST (l)));
 249 }
 250 
 251 /* --------------------------------------------------------------------------------------------- */
 252 
 253 static void
 254 listbox_back (WListbox *l, gboolean wrap)
     /* [previous][next][first][last][top][bottom][index][help]  */
 255 {
 256     if (!listbox_is_empty (l))
 257     {
 258         if (l->current > 0)
 259             listbox_set_current (l, l->current - 1);
 260         else if (wrap)
 261             listbox_select_last (l);
 262     }
 263 }
 264 
 265 /* --------------------------------------------------------------------------------------------- */
 266 
 267 static void
 268 listbox_back_n (WListbox *l, int n)
     /* [previous][next][first][last][top][bottom][index][help]  */
 269 {
 270     listbox_set_current (l, MAX (l->current - n, 0));
 271 }
 272 
 273 /* --------------------------------------------------------------------------------------------- */
 274 
 275 static cb_ret_t
 276 listbox_execute_cmd (WListbox *l, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 277 {
 278     cb_ret_t ret = MSG_HANDLED;
 279     const WRect *w = &CONST_WIDGET (l)->rect;
 280 
 281     if (l->list == NULL || g_queue_is_empty (l->list))
 282         return MSG_NOT_HANDLED;
 283 
 284     switch (command)
 285     {
 286     case CK_Up:
 287         listbox_back (l, TRUE);
 288         break;
 289     case CK_Down:
 290         listbox_fwd (l, TRUE);
 291         break;
 292     case CK_Top:
 293         listbox_select_first (l);
 294         break;
 295     case CK_Bottom:
 296         listbox_select_last (l);
 297         break;
 298     case CK_PageUp:
 299         listbox_back_n (l, w->lines - 1);
 300         break;
 301     case CK_PageDown:
 302         listbox_fwd_n (l, w->lines - 1);
 303         break;
 304     case CK_Delete:
 305         if (l->deletable)
 306         {
 307             gboolean is_last, is_more;
 308             int length;
 309 
 310             length = g_queue_get_length (l->list);
 311 
 312             is_last = (l->current + 1 >= length);
 313             is_more = (l->top + w->lines >= length);
 314 
 315             listbox_remove_current (l);
 316             if ((l->top > 0) && (is_last || is_more))
 317                 l->top--;
 318         }
 319         break;
 320     case CK_Clear:
 321         if (l->deletable
 322             && mc_global.widget.confirm_history_cleanup
 323             // TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix
 324             && (query_dialog (Q_ ("DialogTitle|History cleanup"),
 325                               _ ("Do you want clean this history?"), D_ERROR, 2, _ ("&Yes"),
 326                               _ ("&No"))
 327                 == 0))
 328             listbox_remove_list (l);
 329         break;
 330     case CK_View:
 331     case CK_Edit:
 332     case CK_Enter:
 333         ret = send_message (WIDGET (l)->owner, l, MSG_NOTIFY, command, NULL);
 334         break;
 335     default:
 336         ret = MSG_NOT_HANDLED;
 337     }
 338 
 339     return ret;
 340 }
 341 
 342 /* --------------------------------------------------------------------------------------------- */
 343 
 344 /* Return MSG_HANDLED if we want a redraw */
 345 static cb_ret_t
 346 listbox_key (WListbox *l, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 347 {
 348     long command;
 349 
 350     if (l->list == NULL)
 351         return MSG_NOT_HANDLED;
 352 
 353     // focus on listbox item N by '0'..'9' keys
 354     if (key >= '0' && key <= '9')
 355     {
 356         listbox_set_current (l, key - '0');
 357         return MSG_HANDLED;
 358     }
 359 
 360     command = widget_lookup_key (WIDGET (l), key);
 361     if (command == CK_IgnoreKey)
 362         return MSG_NOT_HANDLED;
 363     return listbox_execute_cmd (l, command);
 364 }
 365 
 366 /* --------------------------------------------------------------------------------------------- */
 367 
 368 /* Listbox item adding function */
 369 static inline void
 370 listbox_add_entry (WListbox *l, WLEntry *e, listbox_append_t pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 371 {
 372     if (l->list == NULL)
 373     {
 374         l->list = g_queue_new ();
 375         pos = LISTBOX_APPEND_AT_END;
 376     }
 377 
 378     switch (pos)
 379     {
 380     case LISTBOX_APPEND_AT_END:
 381         g_queue_push_tail (l->list, e);
 382         break;
 383 
 384     case LISTBOX_APPEND_BEFORE:
 385         g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
 386         break;
 387 
 388     case LISTBOX_APPEND_AFTER:
 389         g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
 390         break;
 391 
 392     case LISTBOX_APPEND_SORTED:
 393         g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
 394         break;
 395 
 396     default:
 397         break;
 398     }
 399 }
 400 
 401 /* --------------------------------------------------------------------------------------------- */
 402 
 403 /* Call this whenever the user changes the selected item. */
 404 static void
 405 listbox_on_change (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 406 {
 407     listbox_draw (l, TRUE);
 408     send_message (WIDGET (l)->owner, l, MSG_NOTIFY, 0, NULL);
 409 }
 410 
 411 /* --------------------------------------------------------------------------------------------- */
 412 
 413 static void
 414 listbox_do_action (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 415 {
 416     int action;
 417 
 418     if (listbox_is_empty (l))
 419         return;
 420 
 421     if (l->callback != NULL)
 422         action = l->callback (l);
 423     else
 424         action = LISTBOX_DONE;
 425 
 426     if (action == LISTBOX_DONE)
 427     {
 428         WDialog *h = DIALOG (WIDGET (l)->owner);
 429 
 430         h->ret_value = B_ENTER;
 431         dlg_close (h);
 432     }
 433 }
 434 
 435 /* --------------------------------------------------------------------------------------------- */
 436 
 437 static void
 438 listbox_run_hotkey (WListbox *l, int pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 439 {
 440     listbox_set_current (l, pos);
 441     listbox_on_change (l);
 442     listbox_do_action (l);
 443 }
 444 
 445 /* --------------------------------------------------------------------------------------------- */
 446 
 447 static inline void
 448 listbox_destroy (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 449 {
 450     listbox_remove_list (l);
 451 }
 452 
 453 /* --------------------------------------------------------------------------------------------- */
 454 
 455 static cb_ret_t
 456 listbox_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 457 {
 458     WListbox *l = LISTBOX (w);
 459 
 460     switch (msg)
 461     {
 462     case MSG_HOTKEY:
 463     {
 464         int pos;
 465 
 466         pos = listbox_check_hotkey (l, parm);
 467         if (pos < 0)
 468             return MSG_NOT_HANDLED;
 469 
 470         listbox_run_hotkey (l, pos);
 471 
 472         return MSG_HANDLED;
 473     }
 474 
 475     case MSG_KEY:
 476     {
 477         cb_ret_t ret_code;
 478 
 479         ret_code = listbox_key (l, parm);
 480         if (ret_code != MSG_NOT_HANDLED)
 481             listbox_on_change (l);
 482         return ret_code;
 483     }
 484 
 485     case MSG_ACTION:
 486         return listbox_execute_cmd (l, parm);
 487 
 488     case MSG_CURSOR:
 489         widget_gotoyx (l, l->cursor_y, 0);
 490         return MSG_HANDLED;
 491 
 492     case MSG_DRAW:
 493         listbox_draw (l, widget_get_state (w, WST_FOCUSED));
 494         return MSG_HANDLED;
 495 
 496     case MSG_DESTROY:
 497         listbox_destroy (l);
 498         return MSG_HANDLED;
 499 
 500     default:
 501         return widget_default_callback (w, sender, msg, parm, data);
 502     }
 503 }
 504 
 505 /* --------------------------------------------------------------------------------------------- */
 506 
 507 static void
 508 listbox_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 509 {
 510     WListbox *l = LISTBOX (w);
 511     int old_current;
 512 
 513     old_current = l->current;
 514 
 515     switch (msg)
 516     {
 517     case MSG_MOUSE_DOWN:
 518         widget_select (w);
 519         listbox_set_current (l, listbox_y_pos (l, event->y));
 520         break;
 521 
 522     case MSG_MOUSE_SCROLL_UP:
 523         listbox_back (l, FALSE);
 524         break;
 525 
 526     case MSG_MOUSE_SCROLL_DOWN:
 527         listbox_fwd (l, FALSE);
 528         break;
 529 
 530     case MSG_MOUSE_DRAG:
 531         event->result.repeat = TRUE;  // It'd be functional even without this
 532         listbox_set_current (l, listbox_y_pos (l, event->y));
 533         break;
 534 
 535     case MSG_MOUSE_CLICK:
 536         // We don't call listbox_set_current() here: MSG_MOUSE_DOWN/DRAG did this already
 537         if (event->count == GPM_DOUBLE)  // Double click
 538             listbox_do_action (l);
 539         break;
 540 
 541     default:
 542         break;
 543     }
 544 
 545     // If the selection has changed, we redraw the widget and notify the dialog.
 546     if (l->current != old_current)
 547         listbox_on_change (l);
 548 }
 549 
 550 /* --------------------------------------------------------------------------------------------- */
 551 /*** public functions ****************************************************************************/
 552 /* --------------------------------------------------------------------------------------------- */
 553 
 554 WListbox *
 555 listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
     /* [previous][next][first][last][top][bottom][index][help]  */
 556 {
 557     WRect r = { y, x, 1, width };
 558     WListbox *l;
 559     Widget *w;
 560 
 561     l = g_new (WListbox, 1);
 562     w = WIDGET (l);
 563     r.lines = height > 0 ? height : 1;
 564     widget_init (w, &r, listbox_callback, listbox_mouse_callback);
 565     w->options |= WOP_SELECTABLE | WOP_WANT_HOTKEY;
 566     w->keymap = listbox_map;
 567 
 568     l->list = NULL;
 569     l->top = l->current = 0;
 570     l->deletable = deletable;
 571     l->callback = callback;
 572     l->allow_duplicates = TRUE;
 573     l->scrollbar = !mc_global.tty.slow_terminal;
 574 
 575     return l;
 576 }
 577 
 578 /* --------------------------------------------------------------------------------------------- */
 579 
 580 /**
 581  * Finds item by its label.
 582  */
 583 int
 584 listbox_search_text (WListbox *l, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 585 {
 586     if (!listbox_is_empty (l))
 587     {
 588         int i;
 589         GList *le;
 590 
 591         for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
 592         {
 593             WLEntry *e = LENTRY (le->data);
 594 
 595             if (strcmp (e->text, text) == 0)
 596                 return i;
 597         }
 598     }
 599 
 600     return (-1);
 601 }
 602 
 603 /* --------------------------------------------------------------------------------------------- */
 604 
 605 /**
 606  * Finds item by its 'data' slot.
 607  */
 608 int
 609 listbox_search_data (WListbox *l, const void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 610 {
 611     if (!listbox_is_empty (l))
 612     {
 613         int i;
 614         GList *le;
 615 
 616         for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
 617         {
 618             WLEntry *e = LENTRY (le->data);
 619 
 620             if (e->data == data)
 621                 return i;
 622         }
 623     }
 624 
 625     return (-1);
 626 }
 627 
 628 /* --------------------------------------------------------------------------------------------- */
 629 
 630 /* Select the first entry and scrolls the list to the top */
 631 void
 632 listbox_select_first (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 633 {
 634     l->current = l->top = 0;
 635 }
 636 
 637 /* --------------------------------------------------------------------------------------------- */
 638 
 639 /* Selects the last entry and scrolls the list to the bottom */
 640 void
 641 listbox_select_last (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 642 {
 643     int lines = WIDGET (l)->rect.lines;
 644     int length;
 645 
 646     length = listbox_get_length (l);
 647 
 648     l->current = DOZ (length, 1);
 649     l->top = DOZ (length, lines);
 650 }
 651 
 652 /* --------------------------------------------------------------------------------------------- */
 653 
 654 void
 655 listbox_set_current (WListbox *l, int dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 656 {
 657     GList *le;
 658     int pos;
 659     gboolean top_seen = FALSE;
 660 
 661     if (listbox_is_empty (l) || dest < 0)
 662         return;
 663 
 664     // Special case
 665     for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
 666     {
 667         if (pos == l->top)
 668             top_seen = TRUE;
 669 
 670         if (pos == dest)
 671         {
 672             l->current = dest;
 673             if (!top_seen)
 674                 l->top = l->current;
 675             else
 676             {
 677                 int lines = WIDGET (l)->rect.lines;
 678 
 679                 if (l->current - l->top >= lines)
 680                     l->top = l->current - lines + 1;
 681             }
 682             return;
 683         }
 684     }
 685 
 686     // If we are unable to find it, set decent values
 687     l->current = l->top = 0;
 688 }
 689 
 690 /* --------------------------------------------------------------------------------------------- */
 691 
 692 int
 693 listbox_get_length (const WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 694 {
 695     return listbox_is_empty (l) ? 0 : (int) g_queue_get_length (l->list);
 696 }
 697 
 698 /* --------------------------------------------------------------------------------------------- */
 699 
 700 /* Returns the current string text as well as the associated extra data */
 701 void
 702 listbox_get_current (WListbox *l, char **string, void **extra)
     /* [previous][next][first][last][top][bottom][index][help]  */
 703 {
 704     WLEntry *e = NULL;
 705     gboolean ok;
 706 
 707     if (l != NULL)
 708         e = listbox_get_nth_entry (l, l->current);
 709 
 710     ok = (e != NULL);
 711 
 712     if (string != NULL)
 713         *string = ok ? e->text : NULL;
 714 
 715     if (extra != NULL)
 716         *extra = ok ? e->data : NULL;
 717 }
 718 
 719 /* --------------------------------------------------------------------------------------------- */
 720 
 721 WLEntry *
 722 listbox_get_nth_entry (const WListbox *l, int pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 723 {
 724     if (!listbox_is_empty (l) && pos >= 0)
 725     {
 726         GList *item;
 727 
 728         item = g_queue_peek_nth_link (l->list, (guint) pos);
 729         if (item != NULL)
 730             return LENTRY (item->data);
 731     }
 732 
 733     return NULL;
 734 }
 735 
 736 /* --------------------------------------------------------------------------------------------- */
 737 
 738 GList *
 739 listbox_get_first_link (const WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 740 {
 741     return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
 742 }
 743 
 744 /* --------------------------------------------------------------------------------------------- */
 745 
 746 void
 747 listbox_remove_current (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 748 {
 749     if (!listbox_is_empty (l))
 750     {
 751         GList *current;
 752         int length;
 753 
 754         current = g_queue_peek_nth_link (l->list, (guint) l->current);
 755         listbox_entry_free (current->data);
 756         g_queue_delete_link (l->list, current);
 757 
 758         length = g_queue_get_length (l->list);
 759 
 760         if (length == 0)
 761             l->top = l->current = 0;
 762         else if (l->current >= length)
 763             l->current = length - 1;
 764     }
 765 }
 766 
 767 /* --------------------------------------------------------------------------------------------- */
 768 
 769 gboolean
 770 listbox_is_empty (const WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 771 {
 772     return (l == NULL || l->list == NULL || g_queue_is_empty (l->list));
 773 }
 774 
 775 /* --------------------------------------------------------------------------------------------- */
 776 
 777 /**
 778  * Set new listbox items list.
 779  *
 780  * @param l WListbox object
 781  * @param list list of WLEntry objects
 782  */
 783 void
 784 listbox_set_list (WListbox *l, GQueue *list)
     /* [previous][next][first][last][top][bottom][index][help]  */
 785 {
 786     listbox_remove_list (l);
 787 
 788     if (l != NULL)
 789         l->list = list;
 790 }
 791 
 792 /* --------------------------------------------------------------------------------------------- */
 793 
 794 void
 795 listbox_remove_list (WListbox *l)
     /* [previous][next][first][last][top][bottom][index][help]  */
 796 {
 797     if (l != NULL)
 798     {
 799         if (l->list != NULL)
 800         {
 801             g_queue_free_full (l->list, (GDestroyNotify) listbox_entry_free);
 802             l->list = NULL;
 803         }
 804 
 805         l->current = l->top = 0;
 806     }
 807 }
 808 
 809 /* --------------------------------------------------------------------------------------------- */
 810 
 811 /**
 812  * Add new intem to the listbox.
 813  *
 814  * @param l WListbox object
 815  * @param pos position of the item
 816  * @param hotkey position of the item
 817  * @param text item text. @l takes the copy of @text.
 818  * @param data item data
 819  * @param free_data if TRUE free the @data when @l is destroyed,
 820  *
 821  * @returns pointer to copy of @text.
 822  */
 823 char *
 824 listbox_add_item (WListbox *l, listbox_append_t pos, int hotkey, const char *text, void *data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 825                   gboolean free_data)
 826 {
 827     return listbox_add_item_take (l, pos, hotkey, g_strdup (text), data, free_data);
 828 }
 829 
 830 /* --------------------------------------------------------------------------------------------- */
 831 
 832 /**
 833  * Add new intem to the listbox.
 834  *
 835  * @param l WListbox object
 836  * @param pos position of the item
 837  * @param hotkey position of the item
 838  * @param text item text. Ownership of the text is transferred to the @l.
 839  * @param data item data
 840  * @param free_data if TRUE free the @data when @l is destroyed,
 841  *
 842  * After this call, @text belongs to the @l and may no longer be modified by the caller.
 843  *
 844  * @returns pointer to @text.
 845  */
 846 char *
 847 listbox_add_item_take (WListbox *l, listbox_append_t pos, int hotkey, char *text, void *data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 848                        gboolean free_data)
 849 {
 850     WLEntry *entry;
 851 
 852     if (l == NULL)
 853         return NULL;
 854 
 855     if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
 856         return NULL;
 857 
 858     entry = g_new (WLEntry, 1);
 859     entry->text = text;
 860     entry->data = data;
 861     entry->free_data = free_data;
 862     entry->hotkey = hotkey;
 863 
 864     listbox_add_entry (l, entry, pos);
 865 
 866     return entry->text;
 867 }
 868 
 869 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */