Manual pages: mcmcdiffmceditmcview

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_help
  19. menubar_execute_cmd
  20. menubar_handle_key
  21. menubar_refresh
  22. menubar_free_menu
  23. menubar_callback
  24. menubar_get_menu_by_x_coord
  25. menubar_mouse_on_menu
  26. menubar_change_selected_item
  27. menubar_mouse_callback
  28. menu_entry_new
  29. menu_entry_free
  30. menu_new
  31. menu_set_name
  32. menu_free
  33. menubar_new
  34. menubar_set_menu
  35. menubar_add_menu
  36. menubar_arrange
  37. menubar_find
  38. menubar_activate

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

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