menu.c 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. /*
  2. Pulldown menu code
  3. Copyright (C) 1994-2019
  4. Free Software Foundation, Inc.
  5. Written by:
  6. Andrew Borodin <aborodin@vmail.ru>, 2012, 2013, 2016
  7. This file is part of the Midnight Commander.
  8. The Midnight Commander is free software: you can redistribute it
  9. and/or modify it under the terms of the GNU General Public License as
  10. published by the Free Software Foundation, either version 3 of the License,
  11. or (at your option) any later version.
  12. The Midnight Commander is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. GNU General Public License for more details.
  16. You should have received a copy of the GNU General Public License
  17. along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /** \file menu.c
  20. * \brief Source: pulldown menu code
  21. */
  22. #include <config.h>
  23. #include <ctype.h>
  24. #include <stdarg.h>
  25. #include <string.h>
  26. #include <sys/types.h>
  27. #include "lib/global.h"
  28. #include "lib/tty/tty.h"
  29. #include "lib/skin.h"
  30. #include "lib/tty/key.h" /* key macros */
  31. #include "lib/keybind.h" /* global_keymap_t */
  32. #include "lib/strutil.h"
  33. #include "lib/widget.h"
  34. #include "lib/event.h" /* mc_event_raise() */
  35. /*** global variables ****************************************************************************/
  36. const global_keymap_t *menu_map;
  37. /*** file scope macro definitions ****************************************************************/
  38. #define MENUENTRY(x) ((menu_entry_t *)(x))
  39. #define MENU(x) ((menu_t *)(x))
  40. /*** file scope type declarations ****************************************************************/
  41. struct menu_entry_t
  42. {
  43. unsigned char first_letter;
  44. hotkey_t text;
  45. long command;
  46. char *shortcut;
  47. };
  48. struct menu_t
  49. {
  50. int start_x; /* position relative to menubar start */
  51. hotkey_t text;
  52. GList *entries;
  53. size_t max_entry_len; /* cached max length of entry texts (text + shortcut) */
  54. size_t max_hotkey_len; /* cached max length of shortcuts */
  55. unsigned int selected; /* pointer to current menu entry */
  56. char *help_node;
  57. };
  58. /*** file scope variables ************************************************************************/
  59. /*** file scope functions ************************************************************************/
  60. /* --------------------------------------------------------------------------------------------- */
  61. static void
  62. menu_arrange (menu_t * menu, dlg_shortcut_str get_shortcut)
  63. {
  64. if (menu != NULL)
  65. {
  66. GList *i;
  67. size_t max_shortcut_len = 0;
  68. menu->max_entry_len = 1;
  69. menu->max_hotkey_len = 1;
  70. for (i = menu->entries; i != NULL; i = g_list_next (i))
  71. {
  72. menu_entry_t *entry = MENUENTRY (i->data);
  73. if (entry != NULL)
  74. {
  75. size_t len;
  76. len = (size_t) hotkey_width (entry->text);
  77. menu->max_hotkey_len = MAX (menu->max_hotkey_len, len);
  78. if (get_shortcut != NULL)
  79. entry->shortcut = get_shortcut (entry->command);
  80. if (entry->shortcut != NULL)
  81. {
  82. len = (size_t) str_term_width1 (entry->shortcut);
  83. max_shortcut_len = MAX (max_shortcut_len, len);
  84. }
  85. }
  86. }
  87. menu->max_entry_len = menu->max_hotkey_len + max_shortcut_len;
  88. }
  89. }
  90. /* --------------------------------------------------------------------------------------------- */
  91. static void
  92. menubar_paint_idx (WMenuBar * menubar, unsigned int idx, int color)
  93. {
  94. Widget *w = WIDGET (menubar);
  95. const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  96. const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, idx));
  97. const int y = 2 + idx;
  98. int x = menu->start_x;
  99. if (x + menu->max_entry_len + 4 > (gsize) w->cols)
  100. x = w->cols - menu->max_entry_len - 4;
  101. if (entry == NULL)
  102. {
  103. /* menu separator */
  104. tty_setcolor (MENU_ENTRY_COLOR);
  105. widget_move (w, y, x - 1);
  106. tty_print_alt_char (ACS_LTEE, FALSE);
  107. tty_draw_hline (w->y + y, w->x + x, ACS_HLINE, menu->max_entry_len + 3);
  108. widget_move (w, y, x + menu->max_entry_len + 3);
  109. tty_print_alt_char (ACS_RTEE, FALSE);
  110. }
  111. else
  112. {
  113. int yt, xt;
  114. /* menu text */
  115. tty_setcolor (color);
  116. widget_move (w, y, x);
  117. tty_print_char ((unsigned char) entry->first_letter);
  118. tty_getyx (&yt, &xt);
  119. tty_draw_hline (yt, xt, ' ', menu->max_entry_len + 2); /* clear line */
  120. tty_print_string (entry->text.start);
  121. if (entry->text.hotkey != NULL)
  122. {
  123. tty_setcolor (color == MENU_SELECTED_COLOR ? MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
  124. tty_print_string (entry->text.hotkey);
  125. tty_setcolor (color);
  126. }
  127. if (entry->text.end != NULL)
  128. tty_print_string (entry->text.end);
  129. if (entry->shortcut != NULL)
  130. {
  131. widget_move (w, y, x + menu->max_hotkey_len + 3);
  132. tty_print_string (entry->shortcut);
  133. }
  134. /* move cursor to the start of entry text */
  135. widget_move (w, y, x + 1);
  136. }
  137. }
  138. /* --------------------------------------------------------------------------------------------- */
  139. static void
  140. menubar_draw_drop (WMenuBar * menubar)
  141. {
  142. Widget *w = WIDGET (menubar);
  143. const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  144. const unsigned int count = g_list_length (menu->entries);
  145. int column = menu->start_x - 1;
  146. unsigned int i;
  147. if (column + menu->max_entry_len + 5 > (gsize) w->cols)
  148. column = w->cols - menu->max_entry_len - 5;
  149. tty_setcolor (MENU_ENTRY_COLOR);
  150. tty_draw_box (w->y + 1, w->x + column, count + 2, menu->max_entry_len + 5, FALSE);
  151. for (i = 0; i < count; i++)
  152. menubar_paint_idx (menubar, i,
  153. i == menu->selected ? MENU_SELECTED_COLOR : MENU_ENTRY_COLOR);
  154. }
  155. /* --------------------------------------------------------------------------------------------- */
  156. static void
  157. menubar_set_color (WMenuBar * menubar, gboolean current, gboolean hotkey)
  158. {
  159. if (!widget_get_state (WIDGET (menubar), WST_FOCUSED))
  160. tty_setcolor (MENU_INACTIVE_COLOR);
  161. else if (current)
  162. tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
  163. else
  164. tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
  165. }
  166. /* --------------------------------------------------------------------------------------------- */
  167. static void
  168. menubar_draw (WMenuBar * menubar)
  169. {
  170. Widget *w = WIDGET (menubar);
  171. GList *i;
  172. /* First draw the complete menubar */
  173. tty_setcolor (widget_get_state (w, WST_FOCUSED) ? MENU_ENTRY_COLOR : MENU_INACTIVE_COLOR);
  174. tty_draw_hline (w->y, w->x, ' ', w->cols);
  175. /* Now each one of the entries */
  176. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  177. {
  178. menu_t *menu = MENU (i->data);
  179. gboolean is_selected = (menubar->selected == (gsize) g_list_position (menubar->menu, i));
  180. menubar_set_color (menubar, is_selected, FALSE);
  181. widget_move (w, 0, menu->start_x);
  182. tty_print_char (' ');
  183. tty_print_string (menu->text.start);
  184. if (menu->text.hotkey != NULL)
  185. {
  186. menubar_set_color (menubar, is_selected, TRUE);
  187. tty_print_string (menu->text.hotkey);
  188. menubar_set_color (menubar, is_selected, FALSE);
  189. }
  190. if (menu->text.end != NULL)
  191. tty_print_string (menu->text.end);
  192. tty_print_char (' ');
  193. }
  194. if (menubar->is_dropped)
  195. menubar_draw_drop (menubar);
  196. else
  197. widget_move (w, 0, MENU (g_list_nth_data (menubar->menu, menubar->selected))->start_x);
  198. }
  199. /* --------------------------------------------------------------------------------------------- */
  200. static void
  201. menubar_remove (WMenuBar * menubar)
  202. {
  203. WDialog *h;
  204. if (!menubar->is_dropped)
  205. return;
  206. /* HACK: before refresh the dialog, change the current widget to keep the order
  207. of overlapped widgets. This is useful in multi-window editor.
  208. In general, menubar should be a special object, not an ordinary widget
  209. in the current dialog. */
  210. h = WIDGET (menubar)->owner;
  211. h->current = g_list_find (h->widgets, dlg_find_by_id (h, menubar->previous_widget));
  212. menubar->is_dropped = FALSE;
  213. do_refresh ();
  214. menubar->is_dropped = TRUE;
  215. /* restore current widget */
  216. h->current = g_list_find (h->widgets, menubar);
  217. }
  218. /* --------------------------------------------------------------------------------------------- */
  219. static void
  220. menubar_left (WMenuBar * menubar)
  221. {
  222. menubar_remove (menubar);
  223. if (menubar->selected == 0)
  224. menubar->selected = g_list_length (menubar->menu) - 1;
  225. else
  226. menubar->selected--;
  227. menubar_draw (menubar);
  228. }
  229. /* --------------------------------------------------------------------------------------------- */
  230. static void
  231. menubar_right (WMenuBar * menubar)
  232. {
  233. menubar_remove (menubar);
  234. menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
  235. menubar_draw (menubar);
  236. }
  237. /* --------------------------------------------------------------------------------------------- */
  238. static void
  239. menubar_finish (WMenuBar * menubar)
  240. {
  241. Widget *w = WIDGET (menubar);
  242. widget_set_state (w, WST_FOCUSED, FALSE);
  243. menubar->is_dropped = FALSE;
  244. w->lines = 1;
  245. widget_want_hotkey (w, FALSE);
  246. widget_set_options (w, WOP_SELECTABLE, FALSE);
  247. /* Move the menubar to the bottom so that widgets displayed on top of
  248. * an "invisible" menubar get the first chance to respond to mouse events. */
  249. widget_set_bottom (w);
  250. dlg_select_by_id (w->owner, menubar->previous_widget);
  251. do_refresh ();
  252. }
  253. /* --------------------------------------------------------------------------------------------- */
  254. static void
  255. menubar_drop (WMenuBar * menubar, unsigned int selected)
  256. {
  257. menubar->is_dropped = TRUE;
  258. menubar->selected = selected;
  259. menubar_draw (menubar);
  260. }
  261. /* --------------------------------------------------------------------------------------------- */
  262. static void
  263. menubar_execute (WMenuBar * menubar)
  264. {
  265. const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  266. const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
  267. if ((entry != NULL) && (entry->command != CK_IgnoreKey))
  268. {
  269. Widget *w = WIDGET (menubar);
  270. mc_global.widget.is_right = (menubar->selected != 0);
  271. menubar_finish (menubar);
  272. send_message (w->owner, w, MSG_ACTION, entry->command, NULL);
  273. do_refresh ();
  274. }
  275. }
  276. /* --------------------------------------------------------------------------------------------- */
  277. static void
  278. menubar_down (WMenuBar * menubar)
  279. {
  280. menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  281. const unsigned int len = g_list_length (menu->entries);
  282. menu_entry_t *entry;
  283. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  284. do
  285. {
  286. menu->selected = (menu->selected + 1) % len;
  287. entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
  288. }
  289. while ((entry == NULL) || (entry->command == CK_IgnoreKey));
  290. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  291. }
  292. /* --------------------------------------------------------------------------------------------- */
  293. static void
  294. menubar_up (WMenuBar * menubar)
  295. {
  296. menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  297. const unsigned int len = g_list_length (menu->entries);
  298. menu_entry_t *entry;
  299. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  300. do
  301. {
  302. if (menu->selected == 0)
  303. menu->selected = len - 1;
  304. else
  305. menu->selected--;
  306. entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
  307. }
  308. while ((entry == NULL) || (entry->command == CK_IgnoreKey));
  309. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  310. }
  311. /* --------------------------------------------------------------------------------------------- */
  312. static void
  313. menubar_first (WMenuBar * menubar)
  314. {
  315. if (menubar->is_dropped)
  316. {
  317. menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  318. if (menu->selected == 0)
  319. return;
  320. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  321. menu->selected = 0;
  322. while (TRUE)
  323. {
  324. menu_entry_t *entry;
  325. entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
  326. if ((entry == NULL) || (entry->command == CK_IgnoreKey))
  327. menu->selected++;
  328. else
  329. break;
  330. }
  331. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  332. }
  333. else
  334. {
  335. menubar->selected = 0;
  336. menubar_draw (menubar);
  337. }
  338. }
  339. /* --------------------------------------------------------------------------------------------- */
  340. static void
  341. menubar_last (WMenuBar * menubar)
  342. {
  343. if (menubar->is_dropped)
  344. {
  345. menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  346. const unsigned int len = g_list_length (menu->entries);
  347. menu_entry_t *entry;
  348. if (menu->selected == len - 1)
  349. return;
  350. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  351. menu->selected = len;
  352. do
  353. {
  354. menu->selected--;
  355. entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
  356. }
  357. while ((entry == NULL) || (entry->command == CK_IgnoreKey));
  358. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  359. }
  360. else
  361. {
  362. menubar->selected = g_list_length (menubar->menu) - 1;
  363. menubar_draw (menubar);
  364. }
  365. }
  366. /* --------------------------------------------------------------------------------------------- */
  367. static cb_ret_t
  368. menubar_try_drop_menu (WMenuBar * menubar, int hotkey)
  369. {
  370. GList *i;
  371. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  372. {
  373. menu_t *menu = MENU (i->data);
  374. if (menu->text.hotkey != NULL && hotkey == g_ascii_tolower (menu->text.hotkey[0]))
  375. {
  376. menubar_drop (menubar, g_list_position (menubar->menu, i));
  377. return MSG_HANDLED;
  378. }
  379. }
  380. return MSG_NOT_HANDLED;
  381. }
  382. /* --------------------------------------------------------------------------------------------- */
  383. static cb_ret_t
  384. menubar_try_exec_menu (WMenuBar * menubar, int hotkey)
  385. {
  386. menu_t *menu;
  387. GList *i;
  388. menu = g_list_nth_data (menubar->menu, menubar->selected);
  389. for (i = menu->entries; i != NULL; i = g_list_next (i))
  390. {
  391. const menu_entry_t *entry = MENUENTRY (i->data);
  392. if (entry != NULL && entry->text.hotkey != NULL
  393. && hotkey == g_ascii_tolower (entry->text.hotkey[0]))
  394. {
  395. menu->selected = g_list_position (menu->entries, i);
  396. menubar_execute (menubar);
  397. return MSG_HANDLED;
  398. }
  399. }
  400. return MSG_NOT_HANDLED;
  401. }
  402. /* --------------------------------------------------------------------------------------------- */
  403. static cb_ret_t
  404. menubar_execute_cmd (WMenuBar * menubar, unsigned long command)
  405. {
  406. cb_ret_t ret = MSG_HANDLED;
  407. switch (command)
  408. {
  409. case CK_Help:
  410. {
  411. ev_help_t event_data = { NULL, NULL };
  412. if (menubar->is_dropped)
  413. event_data.node =
  414. MENU (g_list_nth_data (menubar->menu, menubar->selected))->help_node;
  415. else
  416. event_data.node = "[Menu Bar]";
  417. mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
  418. menubar_draw (menubar);
  419. }
  420. break;
  421. case CK_Left:
  422. menubar_left (menubar);
  423. break;
  424. case CK_Right:
  425. menubar_right (menubar);
  426. break;
  427. case CK_Up:
  428. if (menubar->is_dropped)
  429. menubar_up (menubar);
  430. break;
  431. case CK_Down:
  432. if (menubar->is_dropped)
  433. menubar_down (menubar);
  434. else
  435. menubar_drop (menubar, menubar->selected);
  436. break;
  437. case CK_Home:
  438. menubar_first (menubar);
  439. break;
  440. case CK_End:
  441. menubar_last (menubar);
  442. break;
  443. case CK_Enter:
  444. if (menubar->is_dropped)
  445. menubar_execute (menubar);
  446. else
  447. menubar_drop (menubar, menubar->selected);
  448. break;
  449. case CK_Quit:
  450. menubar_finish (menubar);
  451. break;
  452. default:
  453. ret = MSG_NOT_HANDLED;
  454. break;
  455. }
  456. return ret;
  457. }
  458. /* --------------------------------------------------------------------------------------------- */
  459. static int
  460. menubar_handle_key (WMenuBar * menubar, int key)
  461. {
  462. unsigned long cmd;
  463. cb_ret_t ret = MSG_NOT_HANDLED;
  464. cmd = keybind_lookup_keymap_command (menu_map, key);
  465. if (cmd != CK_IgnoreKey)
  466. ret = menubar_execute_cmd (menubar, cmd);
  467. if (ret != MSG_HANDLED)
  468. {
  469. if (menubar->is_dropped)
  470. ret = menubar_try_exec_menu (menubar, key);
  471. else
  472. ret = menubar_try_drop_menu (menubar, key);
  473. }
  474. return ret;
  475. }
  476. /* --------------------------------------------------------------------------------------------- */
  477. static gboolean
  478. menubar_refresh (WMenuBar * menubar)
  479. {
  480. Widget *w = WIDGET (menubar);
  481. if (!widget_get_state (w, WST_FOCUSED))
  482. return FALSE;
  483. /* Trick to get all the mouse events */
  484. w->lines = LINES;
  485. /* Trick to get all of the hotkeys */
  486. widget_want_hotkey (w, TRUE);
  487. return TRUE;
  488. }
  489. /* --------------------------------------------------------------------------------------------- */
  490. static void
  491. menubar_free_menu (WMenuBar * menubar)
  492. {
  493. if (menubar->menu != NULL)
  494. g_list_free_full (menubar->menu, (GDestroyNotify) destroy_menu);
  495. }
  496. /* --------------------------------------------------------------------------------------------- */
  497. static cb_ret_t
  498. menubar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
  499. {
  500. WMenuBar *menubar = MENUBAR (w);
  501. switch (msg)
  502. {
  503. /* We do not want the focus unless we have been activated */
  504. case MSG_FOCUS:
  505. if (menubar_refresh (menubar))
  506. {
  507. menubar_draw (menubar);
  508. return MSG_HANDLED;
  509. }
  510. return MSG_NOT_HANDLED;
  511. case MSG_UNFOCUS:
  512. return widget_get_state (w, WST_FOCUSED) ? MSG_NOT_HANDLED : MSG_HANDLED;
  513. /* We don't want the buttonbar to activate while using the menubar */
  514. case MSG_HOTKEY:
  515. case MSG_KEY:
  516. if (widget_get_state (w, WST_FOCUSED))
  517. {
  518. menubar_handle_key (menubar, parm);
  519. return MSG_HANDLED;
  520. }
  521. return MSG_NOT_HANDLED;
  522. case MSG_CURSOR:
  523. /* Put the cursor in a suitable place */
  524. return MSG_NOT_HANDLED;
  525. case MSG_DRAW:
  526. if (menubar->is_visible || menubar_refresh (menubar))
  527. menubar_draw (menubar);
  528. return MSG_HANDLED;
  529. case MSG_RESIZE:
  530. /* try show menu after screen resize */
  531. menubar_refresh (menubar);
  532. return MSG_HANDLED;
  533. case MSG_DESTROY:
  534. menubar_free_menu (menubar);
  535. return MSG_HANDLED;
  536. default:
  537. return widget_default_callback (w, sender, msg, parm, data);
  538. }
  539. }
  540. /* --------------------------------------------------------------------------------------------- */
  541. static unsigned int
  542. menubar_get_menu_by_x_coord (const WMenuBar * menubar, int x)
  543. {
  544. unsigned int i;
  545. GList *menu;
  546. for (i = 0, menu = menubar->menu;
  547. menu != NULL && x >= MENU (menu->data)->start_x; i++, menu = g_list_next (menu))
  548. ;
  549. /* Don't set the invalid value -1 */
  550. if (i != 0)
  551. i--;
  552. return i;
  553. }
  554. /* --------------------------------------------------------------------------------------------- */
  555. static gboolean
  556. menubar_mouse_on_menu (const WMenuBar * menubar, int y, int x)
  557. {
  558. Widget *w = WIDGET (menubar);
  559. menu_t *menu;
  560. int left_x, right_x, bottom_y;
  561. if (!menubar->is_dropped)
  562. return FALSE;
  563. menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  564. left_x = menu->start_x;
  565. right_x = left_x + menu->max_entry_len + 3;
  566. if (right_x > w->cols)
  567. {
  568. left_x = w->cols - (menu->max_entry_len + 3);
  569. right_x = w->cols;
  570. }
  571. bottom_y = g_list_length (menu->entries) + 2; /* skip bar and top frame */
  572. return (x >= left_x && x < right_x && y > 1 && y < bottom_y);
  573. }
  574. /* --------------------------------------------------------------------------------------------- */
  575. static void
  576. menubar_change_selected_item (WMenuBar * menubar, int y)
  577. {
  578. menu_t *menu;
  579. menu_entry_t *entry;
  580. y -= 2; /* skip bar and top frame */
  581. menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  582. entry = MENUENTRY (g_list_nth_data (menu->entries, y));
  583. if (entry != NULL && entry->command != CK_IgnoreKey)
  584. {
  585. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  586. menu->selected = y;
  587. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  588. }
  589. }
  590. /* --------------------------------------------------------------------------------------------- */
  591. static void
  592. menubar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
  593. {
  594. static gboolean was_drag = FALSE;
  595. WMenuBar *menubar = MENUBAR (w);
  596. gboolean mouse_on_drop;
  597. mouse_on_drop = menubar_mouse_on_menu (menubar, event->y, event->x);
  598. switch (msg)
  599. {
  600. case MSG_MOUSE_DOWN:
  601. was_drag = FALSE;
  602. if (event->y == 0)
  603. {
  604. /* events on menubar */
  605. unsigned int selected;
  606. selected = menubar_get_menu_by_x_coord (menubar, event->x);
  607. menubar_activate (menubar, TRUE, selected);
  608. menubar_remove (menubar); /* if already shown */
  609. menubar_drop (menubar, selected);
  610. }
  611. else if (mouse_on_drop)
  612. menubar_change_selected_item (menubar, event->y);
  613. else
  614. {
  615. /* mouse click outside menubar or dropdown -- close menu */
  616. menubar_finish (menubar);
  617. /*
  618. * @FIXME.
  619. *
  620. * Unless we clear the 'capture' flag, we'll receive MSG_MOUSE_DRAG
  621. * events belonging to this click (in case the user drags the mouse,
  622. * of course).
  623. *
  624. * For the time being, we mark this with FIXME as this flag should
  625. * preferably be regarded as "implementation detail" and not be
  626. * touched by us. We should think of some other way of communicating
  627. * this to the system.
  628. */
  629. w->mouse.capture = FALSE;
  630. }
  631. break;
  632. case MSG_MOUSE_UP:
  633. if (was_drag && mouse_on_drop)
  634. menubar_execute (menubar);
  635. was_drag = FALSE;
  636. break;
  637. case MSG_MOUSE_CLICK:
  638. was_drag = FALSE;
  639. if ((event->buttons & GPM_B_MIDDLE) != 0 && event->y > 0 && menubar->is_dropped)
  640. {
  641. /* middle click -- everywhere */
  642. menubar_execute (menubar);
  643. }
  644. else if (mouse_on_drop)
  645. menubar_execute (menubar);
  646. else if (event->y > 0)
  647. /* releasing the mouse button outside the menu -- close menu */
  648. menubar_finish (menubar);
  649. break;
  650. case MSG_MOUSE_DRAG:
  651. if (event->y == 0)
  652. {
  653. menubar_remove (menubar);
  654. menubar_drop (menubar, menubar_get_menu_by_x_coord (menubar, event->x));
  655. }
  656. else if (mouse_on_drop)
  657. menubar_change_selected_item (menubar, event->y);
  658. was_drag = TRUE;
  659. break;
  660. case MSG_MOUSE_SCROLL_UP:
  661. case MSG_MOUSE_SCROLL_DOWN:
  662. was_drag = FALSE;
  663. if (widget_get_state (w, WST_FOCUSED))
  664. {
  665. if (event->y == 0)
  666. {
  667. /* menubar: left/right */
  668. if (msg == MSG_MOUSE_SCROLL_UP)
  669. menubar_left (menubar);
  670. else
  671. menubar_right (menubar);
  672. }
  673. else if (mouse_on_drop)
  674. {
  675. /* drop-down menu: up/down */
  676. if (msg == MSG_MOUSE_SCROLL_UP)
  677. menubar_up (menubar);
  678. else
  679. menubar_down (menubar);
  680. }
  681. }
  682. break;
  683. default:
  684. was_drag = FALSE;
  685. break;
  686. }
  687. }
  688. /* --------------------------------------------------------------------------------------------- */
  689. /*** public functions ****************************************************************************/
  690. /* --------------------------------------------------------------------------------------------- */
  691. menu_entry_t *
  692. menu_entry_create (const char *name, long command)
  693. {
  694. menu_entry_t *entry;
  695. entry = g_new (menu_entry_t, 1);
  696. entry->first_letter = ' ';
  697. entry->text = parse_hotkey (name);
  698. entry->command = command;
  699. entry->shortcut = NULL;
  700. return entry;
  701. }
  702. /* --------------------------------------------------------------------------------------------- */
  703. void
  704. menu_entry_free (menu_entry_t * entry)
  705. {
  706. if (entry != NULL)
  707. {
  708. release_hotkey (entry->text);
  709. g_free (entry->shortcut);
  710. g_free (entry);
  711. }
  712. }
  713. /* --------------------------------------------------------------------------------------------- */
  714. menu_t *
  715. create_menu (const char *name, GList * entries, const char *help_node)
  716. {
  717. menu_t *menu;
  718. menu = g_new (menu_t, 1);
  719. menu->start_x = 0;
  720. menu->text = parse_hotkey (name);
  721. menu->entries = entries;
  722. menu->max_entry_len = 1;
  723. menu->max_hotkey_len = 0;
  724. menu->selected = 0;
  725. menu->help_node = g_strdup (help_node);
  726. return menu;
  727. }
  728. /* --------------------------------------------------------------------------------------------- */
  729. void
  730. menu_set_name (menu_t * menu, const char *name)
  731. {
  732. release_hotkey (menu->text);
  733. menu->text = parse_hotkey (name);
  734. }
  735. /* --------------------------------------------------------------------------------------------- */
  736. void
  737. destroy_menu (menu_t * menu)
  738. {
  739. release_hotkey (menu->text);
  740. g_list_free_full (menu->entries, (GDestroyNotify) menu_entry_free);
  741. g_free (menu->help_node);
  742. g_free (menu);
  743. }
  744. /* --------------------------------------------------------------------------------------------- */
  745. WMenuBar *
  746. menubar_new (GList * menu, gboolean visible)
  747. {
  748. WMenuBar *menubar;
  749. Widget *w;
  750. menubar = g_new0 (WMenuBar, 1);
  751. w = WIDGET (menubar);
  752. widget_init (w, 0, 0, 1, COLS, menubar_callback, menubar_mouse_callback);
  753. w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_TOP;
  754. /* initially, menubar is not selectable */
  755. widget_set_options (w, WOP_SELECTABLE, FALSE);
  756. w->options |= WOP_TOP_SELECT;
  757. menubar->is_visible = visible;
  758. menubar_set_menu (menubar, menu);
  759. return menubar;
  760. }
  761. /* --------------------------------------------------------------------------------------------- */
  762. void
  763. menubar_set_menu (WMenuBar * menubar, GList * menu)
  764. {
  765. /* delete previous menu */
  766. menubar_free_menu (menubar);
  767. /* add new menu */
  768. menubar->is_dropped = FALSE;
  769. menubar->menu = menu;
  770. menubar->selected = 0;
  771. menubar_arrange (menubar);
  772. widget_set_state (WIDGET (menubar), WST_FOCUSED, FALSE);
  773. }
  774. /* --------------------------------------------------------------------------------------------- */
  775. void
  776. menubar_add_menu (WMenuBar * menubar, menu_t * menu)
  777. {
  778. if (menu != NULL)
  779. {
  780. menu_arrange (menu, WIDGET (menubar)->owner->get_shortcut);
  781. menubar->menu = g_list_append (menubar->menu, menu);
  782. }
  783. menubar_arrange (menubar);
  784. }
  785. /* --------------------------------------------------------------------------------------------- */
  786. /**
  787. * Properly space menubar items. Should be called when menubar is created
  788. * and also when widget width is changed (i.e. upon xterm resize).
  789. */
  790. void
  791. menubar_arrange (WMenuBar * menubar)
  792. {
  793. int start_x = 1;
  794. GList *i;
  795. int gap;
  796. if (menubar->menu == NULL)
  797. return;
  798. gap = WIDGET (menubar)->cols - 2;
  799. /* First, calculate gap between items... */
  800. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  801. {
  802. menu_t *menu = MENU (i->data);
  803. /* preserve length here, to be used below */
  804. menu->start_x = hotkey_width (menu->text) + 2;
  805. gap -= menu->start_x;
  806. }
  807. if (g_list_next (menubar->menu) == NULL)
  808. gap = 1;
  809. else
  810. gap /= (g_list_length (menubar->menu) - 1);
  811. if (gap <= 0)
  812. {
  813. /* We are out of luck - window is too narrow... */
  814. gap = 1;
  815. }
  816. else if (gap >= 3)
  817. gap = 3;
  818. /* ...and now fix start positions of menubar items */
  819. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  820. {
  821. menu_t *menu = MENU (i->data);
  822. int len = menu->start_x;
  823. menu->start_x = start_x;
  824. start_x += len + gap;
  825. }
  826. }
  827. /* --------------------------------------------------------------------------------------------- */
  828. /** Find MenuBar widget in the dialog */
  829. WMenuBar *
  830. find_menubar (const WDialog * h)
  831. {
  832. return MENUBAR (find_widget_type (h, menubar_callback));
  833. }
  834. /* --------------------------------------------------------------------------------------------- */
  835. /**
  836. * Activate menu bar.
  837. *
  838. * @param menubar menu bar object
  839. * @param dropped whether dropdown menus should be drooped or not
  840. * @which number of active dropdown menu
  841. */
  842. void
  843. menubar_activate (WMenuBar * menubar, gboolean dropped, int which)
  844. {
  845. Widget *w = WIDGET (menubar);
  846. if (!widget_get_state (w, WST_FOCUSED))
  847. {
  848. widget_set_options (w, WOP_SELECTABLE, TRUE);
  849. widget_set_state (w, WST_FOCUSED, TRUE); /* FIXME: unneeded? */
  850. menubar->is_dropped = dropped;
  851. if (which >= 0)
  852. menubar->selected = (guint) which;
  853. menubar->previous_widget = dlg_get_current_widget_id (w->owner);
  854. /* Bring it to the top so it receives all mouse events before any other widget.
  855. * See also comment in menubar_finish(). */
  856. widget_select (w);
  857. }
  858. }
  859. /* --------------------------------------------------------------------------------------------- */