listbox.c 18 KB

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