listbox.c 18 KB

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