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

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