listbox.c 18 KB

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