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_entry
  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-2026
   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_FRAME_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                              CORE_SHADOW_COLOR);
 198 
 199     tty_setcolor (MENU_FRAME_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 /*
 732  * Whether the mouse is over a dropped down menu entry (separators excluded).
 733  */
 734 static gboolean
 735 menubar_mouse_on_menu_entry (const WMenuBar *menubar, int y, int x)
     /* [previous][next][first][last][top][bottom][index][help]  */
 736 {
 737     const WRect *w = &CONST_WIDGET (menubar)->rect;
 738     menu_t *menu;
 739     menu_entry_t *entry;
 740     int left_x, right_x, bottom_y;
 741 
 742     if (!menubar->is_dropped)
 743         return FALSE;
 744 
 745     menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
 746     entry = MENUENTRY (g_list_nth_data (menu->entries, y - 2));
 747     if (entry == NULL)
 748         return FALSE;  // separator line
 749 
 750     left_x = menu->start_x;
 751     right_x = left_x + menu->max_entry_len + 2;
 752     if (right_x > w->cols - 1)
 753     {
 754         left_x = w->cols - 1 - (menu->max_entry_len + 2);
 755         right_x = w->cols - 1;
 756     }
 757 
 758     bottom_y = g_list_length (menu->entries) + 2;  // skip bar and top frame
 759 
 760     return (x >= left_x && x < right_x && y > 1 && y < bottom_y);
 761 }
 762 
 763 /* --------------------------------------------------------------------------------------------- */
 764 
 765 static void
 766 menubar_change_selected_item (WMenuBar *menubar, int y)
     /* [previous][next][first][last][top][bottom][index][help]  */
 767 {
 768     menu_t *menu;
 769     menu_entry_t *entry;
 770 
 771     y -= 2;  // skip bar and top frame
 772     menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
 773     entry = MENUENTRY (g_list_nth_data (menu->entries, y));
 774 
 775     if (entry != NULL && entry->command != CK_IgnoreKey)
 776     {
 777         menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
 778         menu->current = y;
 779         menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
 780     }
 781 }
 782 
 783 /* --------------------------------------------------------------------------------------------- */
 784 
 785 static void
 786 menubar_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 787 {
 788     static gboolean was_drag = FALSE;
 789 
 790     WMenuBar *menubar = MENUBAR (w);
 791     gboolean mouse_on_drop;
 792 
 793     mouse_on_drop = menubar_mouse_on_menu_entry (menubar, event->y, event->x);
 794 
 795     switch (msg)
 796     {
 797     case MSG_MOUSE_DOWN:
 798         was_drag = FALSE;
 799 
 800         if (event->y == 0)
 801         {
 802             // events on menubar
 803             unsigned int selected;
 804 
 805             selected = menubar_get_menu_by_x_coord (menubar, event->x);
 806             menubar_activate (menubar, TRUE, selected);
 807             menubar_remove (menubar);  // if already shown
 808             menubar_drop (menubar, selected);
 809         }
 810         else if (mouse_on_drop)
 811             menubar_change_selected_item (menubar, event->y);
 812         else
 813         {
 814             // mouse click outside menubar or dropdown -- close menu
 815             menubar_finish (menubar);
 816 
 817             /*
 818              * @FIXME.
 819              *
 820              * Unless we clear the 'capture' flag, we'll receive MSG_MOUSE_DRAG
 821              * events belonging to this click (in case the user drags the mouse,
 822              * of course).
 823              *
 824              * For the time being, we mark this with FIXME as this flag should
 825              * preferably be regarded as "implementation detail" and not be
 826              * touched by us. We should think of some other way of communicating
 827              * this to the system.
 828              */
 829             w->mouse.capture = FALSE;
 830         }
 831         break;
 832 
 833     case MSG_MOUSE_UP:
 834         if (was_drag && mouse_on_drop)
 835             menubar_execute (menubar);
 836         was_drag = FALSE;
 837         break;
 838 
 839     case MSG_MOUSE_CLICK:
 840         was_drag = FALSE;
 841 
 842         if ((event->buttons & GPM_B_MIDDLE) != 0 && event->y > 0 && menubar->is_dropped)
 843         {
 844             // middle click -- everywhere
 845             menubar_execute (menubar);
 846         }
 847         else if (mouse_on_drop)
 848             menubar_execute (menubar);
 849         else if (event->y > 0)
 850             // releasing the mouse button outside the menu -- close menu
 851             menubar_finish (menubar);
 852         break;
 853 
 854     case MSG_MOUSE_DRAG:
 855         if (event->y == 0)
 856         {
 857             menubar_remove (menubar);
 858             menubar_drop (menubar, menubar_get_menu_by_x_coord (menubar, event->x));
 859         }
 860         else if (mouse_on_drop)
 861             menubar_change_selected_item (menubar, event->y);
 862 
 863         was_drag = TRUE;
 864         break;
 865 
 866     case MSG_MOUSE_SCROLL_UP:
 867     case MSG_MOUSE_SCROLL_DOWN:
 868         was_drag = FALSE;
 869 
 870         if (widget_get_state (w, WST_FOCUSED))
 871         {
 872             if (event->y == 0)
 873             {
 874                 // menubar: left/right
 875                 if (msg == MSG_MOUSE_SCROLL_UP)
 876                     menubar_left (menubar);
 877                 else
 878                     menubar_right (menubar);
 879             }
 880             else if (mouse_on_drop)
 881             {
 882                 // drop-down menu: up/down
 883                 if (msg == MSG_MOUSE_SCROLL_UP)
 884                     menubar_up (menubar);
 885                 else
 886                     menubar_down (menubar);
 887             }
 888         }
 889         break;
 890 
 891     default:
 892         was_drag = FALSE;
 893         break;
 894     }
 895 }
 896 
 897 /* --------------------------------------------------------------------------------------------- */
 898 /*** public functions ****************************************************************************/
 899 /* --------------------------------------------------------------------------------------------- */
 900 
 901 menu_entry_t *
 902 menu_entry_new (const char *name, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 903 {
 904     menu_entry_t *entry;
 905 
 906     entry = g_new (menu_entry_t, 1);
 907     entry->first_letter = ' ';
 908     entry->text = hotkey_new (name);
 909     entry->command = command;
 910     entry->shortcut = NULL;
 911 
 912     return entry;
 913 }
 914 
 915 /* --------------------------------------------------------------------------------------------- */
 916 
 917 void
 918 menu_entry_free (menu_entry_t *entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
 919 {
 920     if (entry != NULL)
 921     {
 922         hotkey_free (entry->text);
 923         g_free (entry->shortcut);
 924         g_free (entry);
 925     }
 926 }
 927 
 928 /* --------------------------------------------------------------------------------------------- */
 929 
 930 menu_t *
 931 menu_new (const char *name, GList *entries, const char *help_node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 932 {
 933     menu_t *menu;
 934 
 935     menu = g_new (menu_t, 1);
 936     menu->start_x = 0;
 937     menu->text = hotkey_new (name);
 938     menu->entries = entries;
 939     menu->max_entry_len = 1;
 940     menu->max_hotkey_len = 0;
 941     menu->current = 0;
 942     menu->help_node = g_strdup (help_node);
 943 
 944     return menu;
 945 }
 946 
 947 /* --------------------------------------------------------------------------------------------- */
 948 
 949 void
 950 menu_set_name (menu_t *menu, const char *name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 951 {
 952     hotkey_free (menu->text);
 953     menu->text = hotkey_new (name);
 954 }
 955 
 956 /* --------------------------------------------------------------------------------------------- */
 957 
 958 void
 959 menu_free (menu_t *menu)
     /* [previous][next][first][last][top][bottom][index][help]  */
 960 {
 961     hotkey_free (menu->text);
 962     g_list_free_full (menu->entries, (GDestroyNotify) menu_entry_free);
 963     g_free (menu->help_node);
 964     g_free (menu);
 965 }
 966 
 967 /* --------------------------------------------------------------------------------------------- */
 968 
 969 WMenuBar *
 970 menubar_new (GList *menu)
     /* [previous][next][first][last][top][bottom][index][help]  */
 971 {
 972     WRect r = { 0, 0, 1, COLS };
 973     WMenuBar *menubar;
 974     Widget *w;
 975 
 976     menubar = g_new0 (WMenuBar, 1);
 977     w = WIDGET (menubar);
 978     widget_init (w, &r, menubar_callback, menubar_mouse_callback);
 979     w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_TOP;
 980     w->options |= WOP_TOP_SELECT;
 981     w->keymap = menu_map;
 982     menubar_set_menu (menubar, menu);
 983 
 984     return menubar;
 985 }
 986 
 987 /* --------------------------------------------------------------------------------------------- */
 988 
 989 void
 990 menubar_set_menu (WMenuBar *menubar, GList *menu)
     /* [previous][next][first][last][top][bottom][index][help]  */
 991 {
 992     // delete previous menu
 993     menubar_free_menu (menubar);
 994     // add new menu
 995     menubar->is_dropped = FALSE;
 996     menubar->menu = menu;
 997     menubar->current = 0;
 998     menubar_arrange (menubar);
 999     widget_set_state (WIDGET (menubar), WST_FOCUSED, FALSE);
1000 }
1001 
1002 /* --------------------------------------------------------------------------------------------- */
1003 
1004 void
1005 menubar_add_menu (WMenuBar *menubar, menu_t *menu)
     /* [previous][next][first][last][top][bottom][index][help]  */
1006 {
1007     if (menu != NULL)
1008     {
1009         menu_arrange (menu, DIALOG (WIDGET (menubar)->owner)->get_shortcut);
1010         menubar->menu = g_list_append (menubar->menu, menu);
1011     }
1012 
1013     menubar_arrange (menubar);
1014 }
1015 
1016 /* --------------------------------------------------------------------------------------------- */
1017 /**
1018  * Properly space menubar items. Should be called when menubar is created
1019  * and also when widget width is changed (i.e. upon xterm resize).
1020  */
1021 
1022 void
1023 menubar_arrange (WMenuBar *menubar)
     /* [previous][next][first][last][top][bottom][index][help]  */
1024 {
1025     int start_x = 1;
1026     GList *i;
1027     int gap;
1028 
1029     if (menubar->menu == NULL)
1030         return;
1031 
1032     gap = WIDGET (menubar)->rect.cols - 2;
1033 
1034     // First, calculate gap between items...
1035     for (i = menubar->menu; i != NULL; i = g_list_next (i))
1036     {
1037         menu_t *menu = MENU (i->data);
1038 
1039         // preserve length here, to be used below
1040         menu->start_x = hotkey_width (menu->text) + 2;
1041         gap -= menu->start_x;
1042     }
1043 
1044     if (g_list_next (menubar->menu) == NULL)
1045         gap = 1;
1046     else
1047         gap /= (g_list_length (menubar->menu) - 1);
1048 
1049     if (gap <= 0)
1050     {
1051         // We are out of luck - window is too narrow...
1052         gap = 1;
1053     }
1054     else if (gap >= 3)
1055         gap = 3;
1056 
1057     // ...and now fix start positions of menubar items
1058     for (i = menubar->menu; i != NULL; i = g_list_next (i))
1059     {
1060         menu_t *menu = MENU (i->data);
1061         int len = menu->start_x;
1062 
1063         menu->start_x = start_x;
1064         start_x += len + gap;
1065     }
1066 }
1067 
1068 /* --------------------------------------------------------------------------------------------- */
1069 /** Find MenuBar widget in the dialog */
1070 
1071 WMenuBar *
1072 menubar_find (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1073 {
1074     return MENUBAR (widget_find_by_type (CONST_WIDGET (h), menubar_callback));
1075 }
1076 
1077 /* --------------------------------------------------------------------------------------------- */
1078 
1079 /**
1080  * Activate menu bar.
1081  *
1082  * @param menubar menu bar object
1083  * @param dropped whether dropdown menus should be drooped or not
1084  * @which number of active dropdown menu
1085  */
1086 void
1087 menubar_activate (WMenuBar *menubar, gboolean dropped, int which)
     /* [previous][next][first][last][top][bottom][index][help]  */
1088 {
1089     Widget *w = WIDGET (menubar);
1090 
1091     widget_show (w);
1092 
1093     if (!widget_get_state (w, WST_FOCUSED))
1094     {
1095         widget_set_options (w, WOP_SELECTABLE, TRUE);
1096 
1097         menubar->is_dropped = dropped;
1098         if (which >= 0)
1099             menubar->current = (guint) which;
1100 
1101         menubar->previous_widget = group_get_current_widget_id (w->owner);
1102 
1103         /* Bring it to the top so it receives all mouse events before any other widget.
1104          * See also comment in menubar_finish(). */
1105         widget_select (w);
1106     }
1107 }
1108 
1109 /* --------------------------------------------------------------------------------------------- */

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