root/lib/widget/menu.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. menu_arrange
  2. menubar_paint_idx
  3. menubar_draw_drop
  4. menubar_set_color
  5. menubar_draw
  6. menubar_remove
  7. menubar_left
  8. menubar_right
  9. menubar_finish
  10. menubar_drop
  11. menubar_execute
  12. menubar_down
  13. menubar_up
  14. menubar_first
  15. menubar_last
  16. menubar_try_drop_menu
  17. menubar_try_exec_menu
  18. menubar_execute_cmd
  19. menubar_handle_key
  20. menubar_refresh
  21. menubar_free_menu
  22. menubar_callback
  23. menubar_get_menu_by_x_coord
  24. menubar_mouse_on_menu
  25. menubar_change_selected_item
  26. menubar_mouse_callback
  27. menu_entry_create
  28. menu_entry_free
  29. create_menu
  30. menu_set_name
  31. destroy_menu
  32. menubar_new
  33. menubar_set_menu
  34. menubar_add_menu
  35. menubar_arrange
  36. find_menubar
  37. menubar_activate

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

/* [previous][next][first][last][top][bottom][index][help]  */