menu.c 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  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 = NULL;
  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 (const WMenuBar * menubar, unsigned int idx, int color)
  93. {
  94. const Widget *w = CONST_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_gotoyx (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_gotoyx (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_gotoyx (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_gotoyx (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_gotoyx (w, y, x + 1);
  136. }
  137. }
  138. /* --------------------------------------------------------------------------------------------- */
  139. static void
  140. menubar_draw_drop (const WMenuBar * menubar)
  141. {
  142. const Widget *w = CONST_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 (const WMenuBar * menubar, gboolean current, gboolean hotkey)
  158. {
  159. if (!widget_get_state (CONST_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 (const WMenuBar * menubar)
  169. {
  170. const Widget *w = CONST_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_gotoyx (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_gotoyx (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 inline void
  491. menubar_free_menu (WMenuBar * menubar)
  492. {
  493. g_clear_list (&menubar->menu, (GDestroyNotify) destroy_menu);
  494. }
  495. /* --------------------------------------------------------------------------------------------- */
  496. static cb_ret_t
  497. menubar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
  498. {
  499. WMenuBar *menubar = MENUBAR (w);
  500. switch (msg)
  501. {
  502. /* We do not want the focus unless we have been activated */
  503. case MSG_FOCUS:
  504. if (menubar_refresh (menubar))
  505. {
  506. menubar_draw (menubar);
  507. return MSG_HANDLED;
  508. }
  509. return MSG_NOT_HANDLED;
  510. case MSG_UNFOCUS:
  511. return widget_get_state (w, WST_FOCUSED) ? MSG_NOT_HANDLED : MSG_HANDLED;
  512. /* We don't want the buttonbar to activate while using the menubar */
  513. case MSG_HOTKEY:
  514. case MSG_KEY:
  515. if (widget_get_state (w, WST_FOCUSED))
  516. {
  517. menubar_handle_key (menubar, parm);
  518. return MSG_HANDLED;
  519. }
  520. return MSG_NOT_HANDLED;
  521. case MSG_CURSOR:
  522. /* Put the cursor in a suitable place */
  523. return MSG_NOT_HANDLED;
  524. case MSG_DRAW:
  525. if (menubar->is_visible || menubar_refresh (menubar))
  526. menubar_draw (menubar);
  527. return MSG_HANDLED;
  528. case MSG_RESIZE:
  529. /* try show menu after screen resize */
  530. menubar_refresh (menubar);
  531. return MSG_HANDLED;
  532. case MSG_DESTROY:
  533. menubar_free_menu (menubar);
  534. return MSG_HANDLED;
  535. default:
  536. return widget_default_callback (w, sender, msg, parm, data);
  537. }
  538. }
  539. /* --------------------------------------------------------------------------------------------- */
  540. static unsigned int
  541. menubar_get_menu_by_x_coord (const WMenuBar * menubar, int x)
  542. {
  543. unsigned int i;
  544. GList *menu;
  545. for (i = 0, menu = menubar->menu;
  546. menu != NULL && x >= MENU (menu->data)->start_x; i++, menu = g_list_next (menu))
  547. ;
  548. /* Don't set the invalid value -1 */
  549. if (i != 0)
  550. i--;
  551. return i;
  552. }
  553. /* --------------------------------------------------------------------------------------------- */
  554. static gboolean
  555. menubar_mouse_on_menu (const WMenuBar * menubar, int y, int x)
  556. {
  557. Widget *w = WIDGET (menubar);
  558. menu_t *menu;
  559. int left_x, right_x, bottom_y;
  560. if (!menubar->is_dropped)
  561. return FALSE;
  562. menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  563. left_x = menu->start_x;
  564. right_x = left_x + menu->max_entry_len + 3;
  565. if (right_x > w->cols)
  566. {
  567. left_x = w->cols - (menu->max_entry_len + 3);
  568. right_x = w->cols;
  569. }
  570. bottom_y = g_list_length (menu->entries) + 2; /* skip bar and top frame */
  571. return (x >= left_x && x < right_x && y > 1 && y < bottom_y);
  572. }
  573. /* --------------------------------------------------------------------------------------------- */
  574. static void
  575. menubar_change_selected_item (WMenuBar * menubar, int y)
  576. {
  577. menu_t *menu;
  578. menu_entry_t *entry;
  579. y -= 2; /* skip bar and top frame */
  580. menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
  581. entry = MENUENTRY (g_list_nth_data (menu->entries, y));
  582. if (entry != NULL && entry->command != CK_IgnoreKey)
  583. {
  584. menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
  585. menu->selected = y;
  586. menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
  587. }
  588. }
  589. /* --------------------------------------------------------------------------------------------- */
  590. static void
  591. menubar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
  592. {
  593. static gboolean was_drag = FALSE;
  594. WMenuBar *menubar = MENUBAR (w);
  595. gboolean mouse_on_drop;
  596. mouse_on_drop = menubar_mouse_on_menu (menubar, event->y, event->x);
  597. switch (msg)
  598. {
  599. case MSG_MOUSE_DOWN:
  600. was_drag = FALSE;
  601. if (event->y == 0)
  602. {
  603. /* events on menubar */
  604. unsigned int selected;
  605. selected = menubar_get_menu_by_x_coord (menubar, event->x);
  606. menubar_activate (menubar, TRUE, selected);
  607. menubar_remove (menubar); /* if already shown */
  608. menubar_drop (menubar, selected);
  609. }
  610. else if (mouse_on_drop)
  611. menubar_change_selected_item (menubar, event->y);
  612. else
  613. {
  614. /* mouse click outside menubar or dropdown -- close menu */
  615. menubar_finish (menubar);
  616. /*
  617. * @FIXME.
  618. *
  619. * Unless we clear the 'capture' flag, we'll receive MSG_MOUSE_DRAG
  620. * events belonging to this click (in case the user drags the mouse,
  621. * of course).
  622. *
  623. * For the time being, we mark this with FIXME as this flag should
  624. * preferably be regarded as "implementation detail" and not be
  625. * touched by us. We should think of some other way of communicating
  626. * this to the system.
  627. */
  628. w->mouse.capture = FALSE;
  629. }
  630. break;
  631. case MSG_MOUSE_UP:
  632. if (was_drag && mouse_on_drop)
  633. menubar_execute (menubar);
  634. was_drag = FALSE;
  635. break;
  636. case MSG_MOUSE_CLICK:
  637. was_drag = FALSE;
  638. if ((event->buttons & GPM_B_MIDDLE) != 0 && event->y > 0 && menubar->is_dropped)
  639. {
  640. /* middle click -- everywhere */
  641. menubar_execute (menubar);
  642. }
  643. else if (mouse_on_drop)
  644. menubar_execute (menubar);
  645. else if (event->y > 0)
  646. /* releasing the mouse button outside the menu -- close menu */
  647. menubar_finish (menubar);
  648. break;
  649. case MSG_MOUSE_DRAG:
  650. if (event->y == 0)
  651. {
  652. menubar_remove (menubar);
  653. menubar_drop (menubar, menubar_get_menu_by_x_coord (menubar, event->x));
  654. }
  655. else if (mouse_on_drop)
  656. menubar_change_selected_item (menubar, event->y);
  657. was_drag = TRUE;
  658. break;
  659. case MSG_MOUSE_SCROLL_UP:
  660. case MSG_MOUSE_SCROLL_DOWN:
  661. was_drag = FALSE;
  662. if (widget_get_state (w, WST_FOCUSED))
  663. {
  664. if (event->y == 0)
  665. {
  666. /* menubar: left/right */
  667. if (msg == MSG_MOUSE_SCROLL_UP)
  668. menubar_left (menubar);
  669. else
  670. menubar_right (menubar);
  671. }
  672. else if (mouse_on_drop)
  673. {
  674. /* drop-down menu: up/down */
  675. if (msg == MSG_MOUSE_SCROLL_UP)
  676. menubar_up (menubar);
  677. else
  678. menubar_down (menubar);
  679. }
  680. }
  681. break;
  682. default:
  683. was_drag = FALSE;
  684. break;
  685. }
  686. }
  687. /* --------------------------------------------------------------------------------------------- */
  688. /*** public functions ****************************************************************************/
  689. /* --------------------------------------------------------------------------------------------- */
  690. menu_entry_t *
  691. menu_entry_create (const char *name, long command)
  692. {
  693. menu_entry_t *entry;
  694. entry = g_new (menu_entry_t, 1);
  695. entry->first_letter = ' ';
  696. entry->text = hotkey_new (name);
  697. entry->command = command;
  698. entry->shortcut = NULL;
  699. return entry;
  700. }
  701. /* --------------------------------------------------------------------------------------------- */
  702. void
  703. menu_entry_free (menu_entry_t * entry)
  704. {
  705. if (entry != NULL)
  706. {
  707. hotkey_free (entry->text);
  708. g_free (entry->shortcut);
  709. g_free (entry);
  710. }
  711. }
  712. /* --------------------------------------------------------------------------------------------- */
  713. menu_t *
  714. create_menu (const char *name, GList * entries, const char *help_node)
  715. {
  716. menu_t *menu;
  717. menu = g_new (menu_t, 1);
  718. menu->start_x = 0;
  719. menu->text = hotkey_new (name);
  720. menu->entries = entries;
  721. menu->max_entry_len = 1;
  722. menu->max_hotkey_len = 0;
  723. menu->selected = 0;
  724. menu->help_node = g_strdup (help_node);
  725. return menu;
  726. }
  727. /* --------------------------------------------------------------------------------------------- */
  728. void
  729. menu_set_name (menu_t * menu, const char *name)
  730. {
  731. hotkey_free (menu->text);
  732. menu->text = hotkey_new (name);
  733. }
  734. /* --------------------------------------------------------------------------------------------- */
  735. void
  736. destroy_menu (menu_t * menu)
  737. {
  738. hotkey_free (menu->text);
  739. g_list_free_full (menu->entries, (GDestroyNotify) menu_entry_free);
  740. g_free (menu->help_node);
  741. g_free (menu);
  742. }
  743. /* --------------------------------------------------------------------------------------------- */
  744. WMenuBar *
  745. menubar_new (GList * menu, gboolean visible)
  746. {
  747. WMenuBar *menubar;
  748. Widget *w;
  749. menubar = g_new0 (WMenuBar, 1);
  750. w = WIDGET (menubar);
  751. widget_init (w, 0, 0, 1, COLS, menubar_callback, menubar_mouse_callback);
  752. w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_TOP;
  753. /* initially, menubar is not selectable */
  754. widget_set_options (w, WOP_SELECTABLE, FALSE);
  755. w->options |= WOP_TOP_SELECT;
  756. menubar->is_visible = visible;
  757. menubar_set_menu (menubar, menu);
  758. return menubar;
  759. }
  760. /* --------------------------------------------------------------------------------------------- */
  761. void
  762. menubar_set_menu (WMenuBar * menubar, GList * menu)
  763. {
  764. /* delete previous menu */
  765. menubar_free_menu (menubar);
  766. /* add new menu */
  767. menubar->is_dropped = FALSE;
  768. menubar->menu = menu;
  769. menubar->selected = 0;
  770. menubar_arrange (menubar);
  771. widget_set_state (WIDGET (menubar), WST_FOCUSED, FALSE);
  772. }
  773. /* --------------------------------------------------------------------------------------------- */
  774. void
  775. menubar_add_menu (WMenuBar * menubar, menu_t * menu)
  776. {
  777. if (menu != NULL)
  778. {
  779. menu_arrange (menu, WIDGET (menubar)->owner->get_shortcut);
  780. menubar->menu = g_list_append (menubar->menu, menu);
  781. }
  782. menubar_arrange (menubar);
  783. }
  784. /* --------------------------------------------------------------------------------------------- */
  785. /**
  786. * Properly space menubar items. Should be called when menubar is created
  787. * and also when widget width is changed (i.e. upon xterm resize).
  788. */
  789. void
  790. menubar_arrange (WMenuBar * menubar)
  791. {
  792. int start_x = 1;
  793. GList *i;
  794. int gap;
  795. if (menubar->menu == NULL)
  796. return;
  797. gap = WIDGET (menubar)->cols - 2;
  798. /* First, calculate gap between items... */
  799. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  800. {
  801. menu_t *menu = MENU (i->data);
  802. /* preserve length here, to be used below */
  803. menu->start_x = hotkey_width (menu->text) + 2;
  804. gap -= menu->start_x;
  805. }
  806. if (g_list_next (menubar->menu) == NULL)
  807. gap = 1;
  808. else
  809. gap /= (g_list_length (menubar->menu) - 1);
  810. if (gap <= 0)
  811. {
  812. /* We are out of luck - window is too narrow... */
  813. gap = 1;
  814. }
  815. else if (gap >= 3)
  816. gap = 3;
  817. /* ...and now fix start positions of menubar items */
  818. for (i = menubar->menu; i != NULL; i = g_list_next (i))
  819. {
  820. menu_t *menu = MENU (i->data);
  821. int len = menu->start_x;
  822. menu->start_x = start_x;
  823. start_x += len + gap;
  824. }
  825. }
  826. /* --------------------------------------------------------------------------------------------- */
  827. /** Find MenuBar widget in the dialog */
  828. WMenuBar *
  829. find_menubar (const WDialog * h)
  830. {
  831. return MENUBAR (find_widget_type (h, menubar_callback));
  832. }
  833. /* --------------------------------------------------------------------------------------------- */
  834. /**
  835. * Activate menu bar.
  836. *
  837. * @param menubar menu bar object
  838. * @param dropped whether dropdown menus should be drooped or not
  839. * @which number of active dropdown menu
  840. */
  841. void
  842. menubar_activate (WMenuBar * menubar, gboolean dropped, int which)
  843. {
  844. Widget *w = WIDGET (menubar);
  845. if (!widget_get_state (w, WST_FOCUSED))
  846. {
  847. widget_set_options (w, WOP_SELECTABLE, TRUE);
  848. widget_set_state (w, WST_FOCUSED, TRUE); /* FIXME: unneeded? */
  849. menubar->is_dropped = dropped;
  850. if (which >= 0)
  851. menubar->selected = (guint) which;
  852. menubar->previous_widget = dlg_get_current_widget_id (w->owner);
  853. /* Bring it to the top so it receives all mouse events before any other widget.
  854. * See also comment in menubar_finish(). */
  855. widget_select (w);
  856. }
  857. }
  858. /* --------------------------------------------------------------------------------------------- */