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

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