menu.c 31 KB

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