listbox.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. /*
  2. Widgets for the Midnight Commander
  3. Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
  4. 2004, 2005, 2006, 2007, 2009, 2010, 2011, 2013
  5. The Free Software Foundation, Inc.
  6. Authors:
  7. Radek Doulik, 1994, 1995
  8. Miguel de Icaza, 1994, 1995
  9. Jakub Jelinek, 1995
  10. Andrej Borsenkow, 1996
  11. Norbert Warmuth, 1997
  12. Andrew Borodin <aborodin@vmail.ru>, 2009, 2010, 2013
  13. This file is part of the Midnight Commander.
  14. The Midnight Commander is free software: you can redistribute it
  15. and/or modify it under the terms of the GNU General Public License as
  16. published by the Free Software Foundation, either version 3 of the License,
  17. or (at your option) any later version.
  18. The Midnight Commander 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.
  22. You should have received a copy of the GNU General Public License
  23. along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. */
  25. /** \file listbox.c
  26. * \brief Source: WListbox widget
  27. */
  28. #include <config.h>
  29. #include <stdlib.h>
  30. #include "lib/global.h"
  31. #include "lib/tty/tty.h"
  32. #include "lib/tty/mouse.h"
  33. #include "lib/skin.h"
  34. #include "lib/strutil.h"
  35. #include "lib/util.h" /* Q_() */
  36. #include "lib/keybind.h" /* global_keymap_t */
  37. #include "lib/widget.h"
  38. /*** global variables ****************************************************************************/
  39. const global_keymap_t *listbox_map = NULL;
  40. /*** file scope macro definitions ****************************************************************/
  41. /*** file scope type declarations ****************************************************************/
  42. /*** file scope variables ************************************************************************/
  43. /*** file scope functions ************************************************************************/
  44. static int
  45. listbox_entry_cmp (const void *a, const void *b)
  46. {
  47. const WLEntry *ea = (const WLEntry *) a;
  48. const WLEntry *eb = (const WLEntry *) b;
  49. return strcmp (ea->text, eb->text);
  50. }
  51. /* --------------------------------------------------------------------------------------------- */
  52. static void
  53. listbox_entry_free (void *data)
  54. {
  55. WLEntry *e = data;
  56. g_free (e->text);
  57. g_free (e);
  58. }
  59. /* --------------------------------------------------------------------------------------------- */
  60. static void
  61. listbox_drawscroll (WListbox * l)
  62. {
  63. Widget *w = WIDGET (l);
  64. int max_line = w->lines - 1;
  65. int line = 0;
  66. int i;
  67. /* Are we at the top? */
  68. widget_move (w, 0, w->cols);
  69. if (l->top == 0)
  70. tty_print_one_vline (TRUE);
  71. else
  72. tty_print_char ('^');
  73. /* Are we at the bottom? */
  74. widget_move (w, max_line, w->cols);
  75. if ((l->top + w->lines == l->count) || (w->lines >= l->count))
  76. tty_print_one_vline (TRUE);
  77. else
  78. tty_print_char ('v');
  79. /* Now draw the nice relative pointer */
  80. if (l->count != 0)
  81. line = 1 + ((l->pos * (w->lines - 2)) / l->count);
  82. for (i = 1; i < max_line; i++)
  83. {
  84. widget_move (w, i, w->cols);
  85. if (i != line)
  86. tty_print_one_vline (TRUE);
  87. else
  88. tty_print_char ('*');
  89. }
  90. }
  91. /* --------------------------------------------------------------------------------------------- */
  92. static void
  93. listbox_draw (WListbox * l, gboolean focused)
  94. {
  95. Widget *w = WIDGET (l);
  96. const WDialog *h = w->owner;
  97. const gboolean disabled = (w->options & W_DISABLED) != 0;
  98. const int normalc = disabled ? DISABLED_COLOR : h->color[DLG_COLOR_NORMAL];
  99. /* *INDENT-OFF* */
  100. int selc = disabled
  101. ? DISABLED_COLOR
  102. : focused
  103. ? h->color[DLG_COLOR_HOT_FOCUS]
  104. : h->color[DLG_COLOR_FOCUS];
  105. /* *INDENT-ON* */
  106. GList *le;
  107. int pos;
  108. int i;
  109. int sel_line = -1;
  110. le = g_list_nth (l->list, l->top);
  111. /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
  112. pos = (le == NULL) ? 0 : l->top;
  113. for (i = 0; i < w->lines; i++)
  114. {
  115. const char *text;
  116. /* Display the entry */
  117. if (pos == l->pos && sel_line == -1)
  118. {
  119. sel_line = i;
  120. tty_setcolor (selc);
  121. }
  122. else
  123. tty_setcolor (normalc);
  124. widget_move (l, i, 1);
  125. if ((i > 0 && pos >= l->count) || (l->list == NULL) || (le == NULL))
  126. text = "";
  127. else
  128. {
  129. WLEntry *e = LENTRY (le->data);
  130. text = e->text;
  131. le = g_list_next (le);
  132. pos++;
  133. }
  134. tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
  135. }
  136. l->cursor_y = sel_line;
  137. if (l->scrollbar && (l->count > w->lines))
  138. {
  139. tty_setcolor (normalc);
  140. listbox_drawscroll (l);
  141. }
  142. }
  143. /* --------------------------------------------------------------------------------------------- */
  144. static int
  145. listbox_check_hotkey (WListbox * l, int key)
  146. {
  147. int i;
  148. GList *le;
  149. for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
  150. {
  151. WLEntry *e = LENTRY (le->data);
  152. if (e->hotkey == key)
  153. return i;
  154. }
  155. return (-1);
  156. }
  157. /* --------------------------------------------------------------------------------------------- */
  158. /* Selects from base the pos element */
  159. static int
  160. listbox_select_pos (WListbox * l, int base, int pos)
  161. {
  162. int last = l->count - 1;
  163. base += pos;
  164. base = min (base, last);
  165. return base;
  166. }
  167. /* --------------------------------------------------------------------------------------------- */
  168. static void
  169. listbox_fwd (WListbox * l)
  170. {
  171. if (l->pos + 1 >= l->count)
  172. listbox_select_first (l);
  173. else
  174. listbox_select_entry (l, l->pos + 1);
  175. }
  176. /* --------------------------------------------------------------------------------------------- */
  177. static void
  178. listbox_back (WListbox * l)
  179. {
  180. if (l->pos <= 0)
  181. listbox_select_last (l);
  182. else
  183. listbox_select_entry (l, l->pos - 1);
  184. }
  185. /* --------------------------------------------------------------------------------------------- */
  186. static cb_ret_t
  187. listbox_execute_cmd (WListbox * l, unsigned long command)
  188. {
  189. cb_ret_t ret = MSG_HANDLED;
  190. int i;
  191. Widget *w = WIDGET (l);
  192. switch (command)
  193. {
  194. case CK_Up:
  195. listbox_back (l);
  196. break;
  197. case CK_Down:
  198. listbox_fwd (l);
  199. break;
  200. case CK_Top:
  201. listbox_select_first (l);
  202. break;
  203. case CK_Bottom:
  204. listbox_select_last (l);
  205. break;
  206. case CK_PageUp:
  207. for (i = 0; (i < w->lines - 1) && (l->pos > 0); i++)
  208. listbox_back (l);
  209. break;
  210. case CK_PageDown:
  211. for (i = 0; (i < w->lines - 1) && (l->pos < l->count - 1); i++)
  212. listbox_fwd (l);
  213. break;
  214. case CK_Delete:
  215. if (l->deletable)
  216. {
  217. gboolean is_last = (l->pos + 1 >= l->count);
  218. gboolean is_more = (l->top + w->lines >= l->count);
  219. listbox_remove_current (l);
  220. if ((l->top > 0) && (is_last || is_more))
  221. l->top--;
  222. }
  223. break;
  224. case CK_Clear:
  225. if (l->deletable && mc_global.widget.confirm_history_cleanup
  226. /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
  227. && (query_dialog (Q_ ("DialogTitle|History cleanup"),
  228. _("Do you want clean this history?"),
  229. D_ERROR, 2, _("&Yes"), _("&No")) == 0))
  230. listbox_remove_list (l);
  231. break;
  232. default:
  233. ret = MSG_NOT_HANDLED;
  234. }
  235. return ret;
  236. }
  237. /* --------------------------------------------------------------------------------------------- */
  238. /* Return MSG_HANDLED if we want a redraw */
  239. static cb_ret_t
  240. listbox_key (WListbox * l, int key)
  241. {
  242. unsigned long command;
  243. if (l->list == NULL)
  244. return MSG_NOT_HANDLED;
  245. /* focus on listbox item N by '0'..'9' keys */
  246. if (key >= '0' && key <= '9')
  247. {
  248. int oldpos = l->pos;
  249. listbox_select_entry (l, key - '0');
  250. /* need scroll to item? */
  251. if (abs (oldpos - l->pos) > WIDGET (l)->lines)
  252. l->top = l->pos;
  253. return MSG_HANDLED;
  254. }
  255. command = keybind_lookup_keymap_command (listbox_map, key);
  256. if (command == CK_IgnoreKey)
  257. return MSG_NOT_HANDLED;
  258. return listbox_execute_cmd (l, command);
  259. }
  260. /* --------------------------------------------------------------------------------------------- */
  261. /* Listbox item adding function */
  262. static inline void
  263. listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
  264. {
  265. switch (pos)
  266. {
  267. case LISTBOX_APPEND_AT_END:
  268. l->list = g_list_append (l->list, e);
  269. break;
  270. case LISTBOX_APPEND_BEFORE:
  271. l->list = g_list_insert_before (l->list, g_list_nth (l->list, l->pos), e);
  272. if (l->pos > 0)
  273. l->pos--;
  274. break;
  275. case LISTBOX_APPEND_AFTER:
  276. l->list = g_list_insert (l->list, e, l->pos + 1);
  277. break;
  278. case LISTBOX_APPEND_SORTED:
  279. l->list = g_list_insert_sorted (l->list, e, (GCompareFunc) listbox_entry_cmp);
  280. break;
  281. default:
  282. return;
  283. }
  284. l->count++;
  285. }
  286. /* --------------------------------------------------------------------------------------------- */
  287. static inline void
  288. listbox_destroy (WListbox * l)
  289. {
  290. listbox_remove_list (l);
  291. }
  292. /* --------------------------------------------------------------------------------------------- */
  293. static cb_ret_t
  294. listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
  295. {
  296. WListbox *l = LISTBOX (w);
  297. WDialog *h = w->owner;
  298. cb_ret_t ret_code;
  299. switch (msg)
  300. {
  301. case MSG_INIT:
  302. return MSG_HANDLED;
  303. case MSG_HOTKEY:
  304. {
  305. int pos, action;
  306. pos = listbox_check_hotkey (l, parm);
  307. if (pos < 0)
  308. return MSG_NOT_HANDLED;
  309. listbox_select_entry (l, pos);
  310. send_message (h, w, MSG_ACTION, l->pos, NULL);
  311. if (l->callback != NULL)
  312. action = l->callback (l);
  313. else
  314. action = LISTBOX_DONE;
  315. if (action == LISTBOX_DONE)
  316. {
  317. h->ret_value = B_ENTER;
  318. dlg_stop (h);
  319. }
  320. return MSG_HANDLED;
  321. }
  322. case MSG_KEY:
  323. ret_code = listbox_key (l, parm);
  324. if (ret_code != MSG_NOT_HANDLED)
  325. {
  326. listbox_draw (l, TRUE);
  327. send_message (h, w, MSG_ACTION, l->pos, NULL);
  328. }
  329. return ret_code;
  330. case MSG_ACTION:
  331. return listbox_execute_cmd (l, parm);
  332. case MSG_CURSOR:
  333. widget_move (l, l->cursor_y, 0);
  334. send_message (h, w, MSG_ACTION, l->pos, NULL);
  335. return MSG_HANDLED;
  336. case MSG_FOCUS:
  337. case MSG_UNFOCUS:
  338. case MSG_DRAW:
  339. listbox_draw (l, msg != MSG_UNFOCUS);
  340. return MSG_HANDLED;
  341. case MSG_DESTROY:
  342. listbox_destroy (l);
  343. return MSG_HANDLED;
  344. case MSG_RESIZE:
  345. return MSG_HANDLED;
  346. default:
  347. return widget_default_callback (w, sender, msg, parm, data);
  348. }
  349. }
  350. /* --------------------------------------------------------------------------------------------- */
  351. static int
  352. listbox_event (Gpm_Event * event, void *data)
  353. {
  354. WListbox *l = LISTBOX (data);
  355. Widget *w = WIDGET (data);
  356. if (!mouse_global_in_widget (event, w))
  357. return MOU_UNHANDLED;
  358. /* Single click */
  359. if ((event->type & GPM_DOWN) != 0)
  360. dlg_select_widget (l);
  361. if (l->list == NULL)
  362. return MOU_NORMAL;
  363. if ((event->type & (GPM_DOWN | GPM_DRAG)) != 0)
  364. {
  365. int ret = MOU_REPEAT;
  366. Gpm_Event local;
  367. int i;
  368. local = mouse_get_local (event, w);
  369. if (local.y < 1)
  370. for (i = -local.y; i >= 0; i--)
  371. listbox_back (l);
  372. else if (local.y > w->lines)
  373. for (i = local.y - w->lines; i > 0; i--)
  374. listbox_fwd (l);
  375. else if ((local.buttons & GPM_B_UP) != 0)
  376. {
  377. listbox_back (l);
  378. ret = MOU_NORMAL;
  379. }
  380. else if ((local.buttons & GPM_B_DOWN) != 0)
  381. {
  382. listbox_fwd (l);
  383. ret = MOU_NORMAL;
  384. }
  385. else
  386. listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
  387. /* We need to refresh ourselves since the dialog manager doesn't */
  388. /* know about this event */
  389. listbox_draw (l, TRUE);
  390. return ret;
  391. }
  392. /* Double click */
  393. if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE))
  394. {
  395. Gpm_Event local;
  396. int action;
  397. local = mouse_get_local (event, w);
  398. dlg_select_widget (l);
  399. listbox_select_entry (l, listbox_select_pos (l, l->top, local.y - 1));
  400. if (l->callback != NULL)
  401. action = l->callback (l);
  402. else
  403. action = LISTBOX_DONE;
  404. if (action == LISTBOX_DONE)
  405. {
  406. w->owner->ret_value = B_ENTER;
  407. dlg_stop (w->owner);
  408. }
  409. }
  410. return MOU_NORMAL;
  411. }
  412. /* --------------------------------------------------------------------------------------------- */
  413. /*** public functions ****************************************************************************/
  414. /* --------------------------------------------------------------------------------------------- */
  415. WListbox *
  416. listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
  417. {
  418. WListbox *l;
  419. Widget *w;
  420. if (height <= 0)
  421. height = 1;
  422. l = g_new (WListbox, 1);
  423. w = WIDGET (l);
  424. widget_init (w, y, x, height, width, listbox_callback, listbox_event);
  425. l->list = NULL;
  426. l->top = l->pos = 0;
  427. l->count = 0;
  428. l->deletable = deletable;
  429. l->callback = callback;
  430. l->allow_duplicates = TRUE;
  431. l->scrollbar = !mc_global.tty.slow_terminal;
  432. widget_want_hotkey (w, TRUE);
  433. widget_want_cursor (w, FALSE);
  434. return l;
  435. }
  436. /* --------------------------------------------------------------------------------------------- */
  437. int
  438. listbox_search_text (WListbox * l, const char *text)
  439. {
  440. if (l != NULL)
  441. {
  442. int i;
  443. GList *le;
  444. for (i = 0, le = l->list; le != NULL; i++, le = g_list_next (le))
  445. {
  446. WLEntry *e = LENTRY (le->data);
  447. if (strcmp (e->text, text) == 0)
  448. return i;
  449. }
  450. }
  451. return (-1);
  452. }
  453. /* --------------------------------------------------------------------------------------------- */
  454. /* Selects the first entry and scrolls the list to the top */
  455. void
  456. listbox_select_first (WListbox * l)
  457. {
  458. l->pos = l->top = 0;
  459. }
  460. /* --------------------------------------------------------------------------------------------- */
  461. /* Selects the last entry and scrolls the list to the bottom */
  462. void
  463. listbox_select_last (WListbox * l)
  464. {
  465. int lines = WIDGET (l)->lines;
  466. l->pos = l->count - 1;
  467. l->top = l->count > lines ? l->count - lines : 0;
  468. }
  469. /* --------------------------------------------------------------------------------------------- */
  470. void
  471. listbox_select_entry (WListbox * l, int dest)
  472. {
  473. GList *le;
  474. int pos;
  475. gboolean top_seen = FALSE;
  476. if (dest < 0)
  477. return;
  478. /* Special case */
  479. for (pos = 0, le = l->list; le != NULL; pos++, le = g_list_next (le))
  480. {
  481. if (pos == l->top)
  482. top_seen = TRUE;
  483. if (pos == dest)
  484. {
  485. l->pos = dest;
  486. if (!top_seen)
  487. l->top = l->pos;
  488. else
  489. {
  490. int lines = WIDGET (l)->lines;
  491. if (l->pos - l->top >= lines)
  492. l->top = l->pos - lines + 1;
  493. }
  494. return;
  495. }
  496. }
  497. /* If we are unable to find it, set decent values */
  498. l->pos = l->top = 0;
  499. }
  500. /* --------------------------------------------------------------------------------------------- */
  501. /* Returns the current string text as well as the associated extra data */
  502. void
  503. listbox_get_current (WListbox * l, char **string, void **extra)
  504. {
  505. WLEntry *e = NULL;
  506. gboolean ok;
  507. if (l != NULL)
  508. e = LENTRY (g_list_nth_data (l->list, l->pos));
  509. ok = (e != NULL);
  510. if (string != NULL)
  511. *string = ok ? e->text : NULL;
  512. if (extra != NULL)
  513. *extra = ok ? e->data : NULL;
  514. }
  515. /* --------------------------------------------------------------------------------------------- */
  516. void
  517. listbox_remove_current (WListbox * l)
  518. {
  519. if ((l != NULL) && (l->count != 0))
  520. {
  521. GList *current;
  522. current = g_list_nth (l->list, l->pos);
  523. l->list = g_list_remove_link (l->list, current);
  524. listbox_entry_free (LENTRY (current->data));
  525. g_list_free_1 (current);
  526. l->count--;
  527. if (l->count == 0)
  528. l->top = l->pos = 0;
  529. else if (l->pos >= l->count)
  530. l->pos = l->count - 1;
  531. }
  532. }
  533. /* --------------------------------------------------------------------------------------------- */
  534. void
  535. listbox_set_list (WListbox * l, GList * list)
  536. {
  537. listbox_remove_list (l);
  538. if (l != NULL)
  539. {
  540. l->list = list;
  541. l->top = l->pos = 0;
  542. l->count = g_list_length (list);
  543. }
  544. }
  545. /* --------------------------------------------------------------------------------------------- */
  546. void
  547. listbox_remove_list (WListbox * l)
  548. {
  549. if ((l != NULL) && (l->count != 0))
  550. {
  551. g_list_foreach (l->list, (GFunc) listbox_entry_free, NULL);
  552. g_list_free (l->list);
  553. l->list = NULL;
  554. l->count = l->pos = l->top = 0;
  555. }
  556. }
  557. /* --------------------------------------------------------------------------------------------- */
  558. char *
  559. listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data)
  560. {
  561. WLEntry *entry;
  562. if (l == NULL)
  563. return NULL;
  564. if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
  565. return NULL;
  566. entry = g_new (WLEntry, 1);
  567. entry->text = g_strdup (text);
  568. entry->data = data;
  569. entry->hotkey = hotkey;
  570. listbox_append_item (l, entry, pos);
  571. return entry->text;
  572. }
  573. /* --------------------------------------------------------------------------------------------- */