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

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