listbox.c 17 KB

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