root/lib/widget/menu.c

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

DEFINITIONS

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

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

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