1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2005, Haiku, Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: Menu.cpp 23 // Authors: Marc Flerackers (mflerackers@androme.be) 24 // Stefano Ceccherini (burton666@libero.it) 25 // Description: BMenu display a menu of selectable items. 26 //------------------------------------------------------------------------------ 27 #include <Debug.h> 28 #include <File.h> 29 #include <FindDirectory.h> 30 #include <Menu.h> 31 #include <MenuItem.h> 32 #include <Path.h> 33 #include <PropertyInfo.h> 34 #include <Window.h> 35 36 #include <MenuWindow.h> 37 38 #ifndef COMPILE_FOR_R5 39 menu_info BMenu::sMenuInfo; 40 #endif 41 42 class _ExtraMenuData_ { 43 public: 44 menu_tracking_hook trackingHook; 45 void *trackingState; 46 47 _ExtraMenuData_(menu_tracking_hook func, void *state) 48 { 49 trackingHook = func; 50 trackingState = state; 51 }; 52 }; 53 54 55 static property_info 56 sPropList[] = { 57 { "Enabled", { B_GET_PROPERTY, 0 }, 58 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false " 59 "otherwise." }, 60 61 { "Enabled", { B_SET_PROPERTY, 0 }, 62 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item." }, 63 64 { "Label", { B_GET_PROPERTY, 0 }, 65 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item." }, 66 67 { "Label", { B_SET_PROPERTY, 0 }, 68 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item." }, 69 70 { "Mark", { B_GET_PROPERTY, 0 }, 71 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem " 72 "is marked; false otherwise." }, 73 74 { "Mark", { B_SET_PROPERTY, 0 }, 75 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem." }, 76 77 { "Menu", { B_CREATE_PROPERTY, 0 }, 78 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 79 "Adds a new menu item at the specified index with the text label found in \"data\" " 80 "and the int32 command found in \"what\" (used as the what field in the CMessage " 81 "sent by the item)." }, 82 83 { "Menu", { B_DELETE_PROPERTY, 0 }, 84 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 85 "Removes the selected menu or menus." }, 86 87 { "Menu", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 88 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 89 "Directs scripting message to the specified menu, first popping the current " 90 "specifier off the stack." }, 91 92 { "MenuItem", { B_COUNT_PROPERTIES, 0 }, 93 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu." }, 94 95 { "MenuItem", { B_CREATE_PROPERTY, 0 }, 96 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 97 "Adds a new menu item at the specified index with the text label found in \"data\" " 98 "and the int32 command found in \"what\" (used as the what field in the CMessage " 99 "sent by the item)." }, 100 101 { "MenuItem", { B_DELETE_PROPERTY, 0 }, 102 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 103 "Removes the specified menu item from its parent menu." }, 104 105 { "MenuItem", { B_EXECUTE_PROPERTY, 0 }, 106 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 107 "Invokes the specified menu item." }, 108 109 { "MenuItem", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 110 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 111 "Directs scripting message to the specified menu, first popping the current " 112 "specifier off the stack." }, 113 {0} 114 }; 115 116 117 BMenu::BMenu(const char *name, menu_layout layout) 118 : BView(BRect(), name, 0, B_WILL_DRAW), 119 fChosenItem(NULL), 120 fPad(14.0f, 2.0f, 20.0f, 0.0f), 121 fSelected(NULL), 122 fCachedMenuWindow(NULL), 123 fSuper(NULL), 124 fSuperitem(NULL), 125 fAscent(-1.0f), 126 fDescent(-1.0f), 127 fFontHeight(-1.0f), 128 fState(0), 129 fLayout(layout), 130 fExtraRect(NULL), 131 fMaxContentWidth(0.0f), 132 fInitMatrixSize(NULL), 133 fExtraMenuData(NULL), 134 fTrigger(0), 135 fResizeToFit(true), 136 fUseCachedMenuLayout(true), 137 fEnabled(true), 138 fDynamicName(false), 139 fRadioMode(false), 140 fTrackNewBounds(false), 141 fStickyMode(false), 142 fIgnoreHidden(true), 143 fTriggerEnabled(true), 144 fRedrawAfterSticky(false), 145 fAttachAborted(false) 146 { 147 InitData(NULL); 148 } 149 150 151 BMenu::BMenu(const char *name, float width, float height) 152 : BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW), 153 fChosenItem(NULL), 154 fSelected(NULL), 155 fCachedMenuWindow(NULL), 156 fSuper(NULL), 157 fSuperitem(NULL), 158 fAscent(-1.0f), 159 fDescent(-1.0f), 160 fFontHeight(-1.0f), 161 fState(0), 162 fLayout(B_ITEMS_IN_MATRIX), 163 fExtraRect(NULL), 164 fMaxContentWidth(0.0f), 165 fInitMatrixSize(NULL), 166 fExtraMenuData(NULL), 167 fTrigger(0), 168 fResizeToFit(true), 169 fUseCachedMenuLayout(true), 170 fEnabled(true), 171 fDynamicName(false), 172 fRadioMode(false), 173 fTrackNewBounds(false), 174 fStickyMode(false), 175 fIgnoreHidden(true), 176 fTriggerEnabled(true), 177 fRedrawAfterSticky(false), 178 fAttachAborted(false) 179 { 180 InitData(NULL); 181 } 182 183 184 BMenu::~BMenu() 185 { 186 RemoveItems(0, CountItems(), true); 187 188 DeleteMenuWindow(); 189 190 delete fInitMatrixSize; 191 delete fExtraMenuData; 192 } 193 194 195 BMenu::BMenu(BMessage *archive) 196 : BView(archive) 197 { 198 InitData(archive); 199 } 200 201 202 BArchivable * 203 BMenu::Instantiate(BMessage *data) 204 { 205 if (validate_instantiation(data, "BMenu")) 206 return new BMenu(data); 207 else 208 return NULL; 209 } 210 211 212 status_t 213 BMenu::Archive(BMessage *data, bool deep) const 214 { 215 status_t err = BView::Archive(data, deep); 216 217 if (err < B_OK) 218 return err; 219 220 if (Layout() != B_ITEMS_IN_ROW) { 221 err = data->AddInt32("_layout", Layout()); 222 223 if (err < B_OK) 224 return err; 225 } 226 227 err = data->AddBool("_rsize_to_fit", fResizeToFit); 228 229 if (err < B_OK) 230 return err; 231 232 err = data->AddBool("_disable", !IsEnabled()); 233 234 if (err < B_OK) 235 return err; 236 237 err = data->AddBool("_radio", IsRadioMode()); 238 239 if (err < B_OK) 240 return err; 241 242 err = data->AddBool("_trig_disabled", AreTriggersEnabled()); 243 244 if (err < B_OK) 245 return err; 246 247 err = data->AddBool("_dyn_label", fDynamicName); 248 249 if (err < B_OK) 250 return err; 251 252 err = data->AddFloat("_maxwidth", fMaxContentWidth); 253 254 if (err < B_OK) 255 return err; 256 257 if (deep) { 258 // TODO store items and rects 259 } 260 261 return err; 262 } 263 264 265 void 266 BMenu::AttachedToWindow() 267 { 268 BView::AttachedToWindow(); 269 270 InvalidateLayout(); 271 } 272 273 274 void 275 BMenu::DetachedFromWindow() 276 { 277 BView::DetachedFromWindow(); 278 } 279 280 281 bool 282 BMenu::AddItem(BMenuItem *item) 283 { 284 return AddItem(item, CountItems()); 285 } 286 287 288 bool 289 BMenu::AddItem(BMenuItem *item, int32 index) 290 { 291 if (fLayout == B_ITEMS_IN_MATRIX) 292 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only" 293 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 294 295 return _AddItem(item, index); 296 } 297 298 299 bool 300 BMenu::AddItem(BMenuItem *item, BRect frame) 301 { 302 if (fLayout != B_ITEMS_IN_MATRIX) 303 debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only" 304 " be called if the menu layout is B_ITEMS_IN_MATRIX"); 305 306 if (!item) 307 return false; 308 309 item->fBounds = frame; 310 311 return _AddItem(item, CountItems()); 312 } 313 314 315 bool 316 BMenu::AddItem(BMenu *submenu) 317 { 318 BMenuItem *item = new BMenuItem(submenu); 319 if (!item) 320 return false; 321 322 return _AddItem(item, CountItems()); 323 } 324 325 326 bool 327 BMenu::AddItem(BMenu *submenu, int32 index) 328 { 329 if (fLayout == B_ITEMS_IN_MATRIX) 330 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only" 331 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 332 333 BMenuItem *item = new BMenuItem(submenu); 334 if (!item) 335 return false; 336 337 return _AddItem(item, index); 338 } 339 340 341 bool 342 BMenu::AddItem(BMenu *submenu, BRect frame) 343 { 344 if (fLayout != B_ITEMS_IN_MATRIX) 345 debugger("BMenu::AddItem(BMenu *, BRect) this method can only" 346 " be called if the menu layout is B_ITEMS_IN_MATRIX"); 347 348 BMenuItem *item = new BMenuItem(submenu); 349 item->fBounds = frame; 350 351 return _AddItem(item, CountItems()); 352 } 353 354 355 bool 356 BMenu::AddList(BList *list, int32 index) 357 { 358 // TODO: test this function, it's not documented in the bebook. 359 int32 numItems = 0; 360 if (list != NULL) 361 numItems = list->CountItems(); 362 363 for (int32 i = 0; i < numItems; i++) { 364 BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i)); 365 if (item != NULL) 366 _AddItem(item, index + i); 367 } 368 369 // TODO: return false if needed 370 return true; 371 } 372 373 374 bool 375 BMenu::AddSeparatorItem() 376 { 377 BMenuItem *item = new BSeparatorItem(); 378 return _AddItem(item, CountItems()); 379 } 380 381 382 bool 383 BMenu::RemoveItem(BMenuItem *item) 384 { 385 // TODO: Check if item is also deleted 386 return RemoveItems(0, 0, item, false); 387 } 388 389 390 BMenuItem * 391 BMenu::RemoveItem(int32 index) 392 { 393 BMenuItem *item = ItemAt(index); 394 RemoveItems(index, 1, NULL, false); 395 return item; 396 } 397 398 399 bool 400 BMenu::RemoveItems(int32 index, int32 count, bool del) 401 { 402 return RemoveItems(index, count, NULL, del); 403 } 404 405 406 bool 407 BMenu::RemoveItem(BMenu *submenu) 408 { 409 for (int i = 0; i < fItems.CountItems(); i++) 410 if (static_cast<BMenuItem *>(fItems.ItemAt(i))->Submenu() == submenu) 411 return RemoveItems(i, 1, NULL, false); 412 413 return false; 414 } 415 416 417 int32 418 BMenu::CountItems() const 419 { 420 return fItems.CountItems(); 421 } 422 423 424 BMenuItem * 425 BMenu::ItemAt(int32 index) const 426 { 427 return static_cast<BMenuItem *>(fItems.ItemAt(index)); 428 } 429 430 431 BMenu * 432 BMenu::SubmenuAt(int32 index) const 433 { 434 BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index)); 435 return (item != NULL) ? item->Submenu() : NULL; 436 } 437 438 439 int32 440 BMenu::IndexOf(BMenuItem *item) const 441 { 442 return fItems.IndexOf(item); 443 } 444 445 446 int32 447 BMenu::IndexOf(BMenu *submenu) const 448 { 449 for (int32 i = 0; i < fItems.CountItems(); i++) 450 if (ItemAt(i)->Submenu() == submenu) 451 return i; 452 453 return -1; 454 } 455 456 457 BMenuItem * 458 BMenu::FindItem(const char *label) const 459 { 460 BMenuItem *item = NULL; 461 462 for (int32 i = 0; i < CountItems(); i++) { 463 item = ItemAt(i); 464 465 if (item->Label() && strcmp(item->Label(), label) == 0) 466 break; 467 468 if (item->Submenu()) { 469 item = item->Submenu()->FindItem(label); 470 if (item) 471 break; 472 } 473 } 474 475 return item; 476 } 477 478 479 BMenuItem * 480 BMenu::FindItem(uint32 command) const 481 { 482 BMenuItem *item = NULL; 483 484 for (int32 i = 0; i < CountItems(); i++) { 485 item = ItemAt(i); 486 487 if (item->Command() == command) 488 break; 489 490 if (item->Submenu()) { 491 item = item->Submenu()->FindItem(command); 492 if (item) 493 break; 494 } 495 } 496 497 return item; 498 } 499 500 501 status_t 502 BMenu::SetTargetForItems(BHandler *handler) 503 { 504 for (int32 i = 0; i < fItems.CountItems(); i++) 505 if (ItemAt(i)->SetTarget(handler) < B_OK) 506 return B_ERROR; 507 508 return B_OK; 509 } 510 511 512 status_t 513 BMenu::SetTargetForItems(BMessenger messenger) 514 { 515 for (int32 i = 0; i < fItems.CountItems(); i++) 516 if (ItemAt(i)->SetTarget(messenger) < B_OK) 517 return B_ERROR; 518 519 return B_OK; 520 } 521 522 523 void 524 BMenu::SetEnabled(bool enabled) 525 { 526 fEnabled = enabled; 527 528 for (int32 i = 0; i < CountItems(); i++) 529 ItemAt(i)->SetEnabled(enabled); 530 } 531 532 533 void 534 BMenu::SetRadioMode(bool flag) 535 { 536 fRadioMode = flag; 537 } 538 539 540 void 541 BMenu::SetTriggersEnabled(bool flag) 542 { 543 fTriggerEnabled = flag; 544 } 545 546 547 void 548 BMenu::SetMaxContentWidth(float width) 549 { 550 fMaxContentWidth = width; 551 } 552 553 554 void 555 BMenu::SetLabelFromMarked(bool flag) 556 { 557 fDynamicName = flag; 558 } 559 560 561 bool 562 BMenu::IsLabelFromMarked() 563 { 564 return fDynamicName; 565 } 566 567 568 bool 569 BMenu::IsEnabled() const 570 { 571 return fEnabled; 572 } 573 574 575 bool 576 BMenu::IsRadioMode() const 577 { 578 return fRadioMode; 579 } 580 581 582 bool 583 BMenu::AreTriggersEnabled() const 584 { 585 return fTriggerEnabled; 586 } 587 588 589 bool 590 BMenu::IsRedrawAfterSticky() const 591 { 592 return fRedrawAfterSticky; 593 } 594 595 596 float 597 BMenu::MaxContentWidth() const 598 { 599 return fMaxContentWidth; 600 } 601 602 603 BMenuItem * 604 BMenu::FindMarked() 605 { 606 for (int i = 0; i < fItems.CountItems(); i++) 607 if (((BMenuItem*)fItems.ItemAt(i))->IsMarked()) 608 return (BMenuItem*)fItems.ItemAt(i); 609 610 return NULL; 611 } 612 613 614 BMenu * 615 BMenu::Supermenu() const 616 { 617 return fSuper; 618 } 619 620 621 BMenuItem * 622 BMenu::Superitem() const 623 { 624 return fSuperitem; 625 } 626 627 628 void 629 BMenu::MessageReceived(BMessage *msg) 630 { 631 BView::MessageReceived(msg); 632 } 633 634 635 void 636 BMenu::KeyDown(const char *bytes, int32 numBytes) 637 { 638 switch (bytes[0]) { 639 case B_UP_ARROW: 640 { 641 if (fSelected) { 642 fSelected->fSelected = false; 643 644 if (fSelected == fItems.FirstItem()) 645 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 646 else 647 fSelected = ItemAt(IndexOf(fSelected) - 1); 648 } else 649 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 650 651 fSelected->fSelected = true; 652 653 break; 654 } 655 case B_DOWN_ARROW: 656 { 657 if (fSelected) { 658 fSelected->fSelected = false; 659 660 if (fSelected == fItems.LastItem()) 661 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 662 else 663 fSelected = ItemAt(IndexOf(fSelected) + 1); 664 } else 665 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 666 667 fSelected->fSelected = true; 668 669 break; 670 } 671 case B_HOME: 672 { 673 if (fSelected) 674 fSelected->fSelected = false; 675 676 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 677 fSelected->fSelected = true; 678 679 break; 680 } 681 case B_END: 682 { 683 if (fSelected) 684 fSelected->fSelected = false; 685 686 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 687 fSelected->fSelected = true; 688 689 break; 690 } 691 case B_ENTER: 692 case B_SPACE: 693 { 694 if (fSelected) 695 InvokeItem(fSelected); 696 697 break; 698 } 699 default: 700 BView::KeyDown(bytes, numBytes); 701 } 702 } 703 704 705 void 706 BMenu::Draw(BRect updateRect) 707 { 708 DrawBackground(updateRect); 709 DrawItems(updateRect); 710 } 711 712 713 void 714 BMenu::GetPreferredSize(float *width, float *height) 715 { 716 ComputeLayout(0, true, false, width, height); 717 } 718 719 720 void 721 BMenu::ResizeToPreferred() 722 { 723 BView::ResizeToPreferred(); 724 } 725 726 727 void 728 BMenu::FrameMoved(BPoint new_position) 729 { 730 BView::FrameMoved(new_position); 731 } 732 733 734 void 735 BMenu::FrameResized(float new_width, float new_height) 736 { 737 BView::FrameResized(new_width, new_height); 738 } 739 740 741 void 742 BMenu::InvalidateLayout() 743 { 744 CacheFontInfo(); 745 LayoutItems(0); 746 Invalidate(); 747 } 748 749 750 BHandler * 751 BMenu::ResolveSpecifier(BMessage *msg, int32 index, 752 BMessage *specifier, int32 form, 753 const char *property) 754 { 755 BPropertyInfo propInfo(sPropList); 756 BHandler *target = NULL; 757 758 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) { 759 case B_ERROR: 760 break; 761 762 case 0: 763 case 1: 764 case 2: 765 case 3: 766 case 4: 767 case 5: 768 case 6: 769 case 7: 770 target = this; 771 break; 772 case 8: 773 // TODO: redirect to menu 774 target = this; 775 break; 776 case 9: 777 case 10: 778 case 11: 779 case 12: 780 target = this; 781 break; 782 case 13: 783 // TODO: redirect to menuitem 784 target = this; 785 break; 786 } 787 788 if (!target) 789 target = BView::ResolveSpecifier(msg, index, specifier, form, 790 property); 791 792 return target; 793 } 794 795 796 status_t 797 BMenu::GetSupportedSuites(BMessage *data) 798 { 799 status_t err; 800 801 if (data == NULL) 802 return B_BAD_VALUE; 803 804 err = data->AddString("suites", "suite/vnd.Be-menu"); 805 806 if (err < B_OK) 807 return err; 808 809 BPropertyInfo prop_info(sPropList); 810 err = data->AddFlat("messages", &prop_info); 811 812 if (err < B_OK) 813 return err; 814 815 return BView::GetSupportedSuites(data); 816 } 817 818 819 status_t 820 BMenu::Perform(perform_code d, void *arg) 821 { 822 return BView::Perform(d, arg); 823 } 824 825 826 void 827 BMenu::MakeFocus(bool focused) 828 { 829 BView::MakeFocus(focused); 830 } 831 832 833 void 834 BMenu::AllAttached() 835 { 836 BView::AllAttached(); 837 } 838 839 840 void 841 BMenu::AllDetached() 842 { 843 BView::AllDetached(); 844 } 845 846 847 BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags, 848 menu_layout layout, bool resizeToFit) 849 : BView(frame, name, resizingMode, flags), 850 fChosenItem(NULL), 851 fSelected(NULL), 852 fCachedMenuWindow(NULL), 853 fSuper(NULL), 854 fSuperitem(NULL), 855 fAscent(-1.0f), 856 fDescent(-1.0f), 857 fFontHeight(-1.0f), 858 fState(0), 859 fLayout(layout), 860 fExtraRect(NULL), 861 fMaxContentWidth(0.0f), 862 fInitMatrixSize(NULL), 863 fExtraMenuData(NULL), 864 fTrigger(0), 865 fResizeToFit(resizeToFit), 866 fUseCachedMenuLayout(true), 867 fEnabled(true), 868 fDynamicName(false), 869 fRadioMode(false), 870 fTrackNewBounds(false), 871 fStickyMode(false), 872 fIgnoreHidden(true), 873 fTriggerEnabled(true), 874 fRedrawAfterSticky(false), 875 fAttachAborted(false) 876 { 877 InitData(NULL); 878 } 879 880 881 BPoint 882 BMenu::ScreenLocation() 883 { 884 BMenu *superMenu = Supermenu(); 885 BMenuItem *superItem = Superitem(); 886 887 if (superMenu == NULL && superItem == NULL) { 888 debugger("BMenu can't determine where to draw." 889 "Override BMenu::ScreenLocation() to determine location."); 890 } 891 892 BPoint point; 893 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 894 point = superItem->Frame().RightTop(); 895 else 896 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 897 898 superMenu->ConvertToScreen(&point); 899 900 return point; 901 } 902 903 904 void 905 BMenu::SetItemMargins(float left, float top, float right, float bottom) 906 { 907 fPad.Set(left, top, right, bottom); 908 } 909 910 911 void 912 BMenu::GetItemMargins(float *left, float *top, float *right, 913 float *bottom) const 914 { 915 if (left != NULL) 916 *left = fPad.left; 917 if (top != NULL) 918 *top = fPad.top; 919 if (right != NULL) 920 *right = fPad.right; 921 if (bottom != NULL) 922 *bottom = fPad.bottom; 923 } 924 925 926 menu_layout 927 BMenu::Layout() const 928 { 929 return fLayout; 930 } 931 932 933 void 934 BMenu::Show() 935 { 936 Show(false); 937 } 938 939 940 void 941 BMenu::Show(bool selectFirst) 942 { 943 _show(selectFirst); 944 } 945 946 947 void 948 BMenu::Hide() 949 { 950 _hide(); 951 } 952 953 954 BMenuItem * 955 BMenu::Track(bool openAnyway, BRect *clickToOpenRect) 956 { 957 if (IsStickyPrefOn()) 958 openAnyway = false; 959 960 SetStickyMode(openAnyway); 961 962 if (LockLooper()) { 963 RedrawAfterSticky(Bounds()); 964 UnlockLooper(); 965 } 966 967 if (clickToOpenRect != NULL && LockLooper()) { 968 fExtraRect = clickToOpenRect; 969 ConvertFromScreen(fExtraRect); 970 UnlockLooper(); 971 } 972 973 int action; 974 BMenuItem *menuItem = _track(&action, -1); 975 976 SetStickyMode(false); 977 fExtraRect = NULL; 978 979 return menuItem; 980 } 981 982 983 bool 984 BMenu::AddDynamicItem(add_state s) 985 { 986 // Implemented in subclasses 987 return false; 988 } 989 990 991 void 992 BMenu::DrawBackground(BRect update) 993 { 994 BRect rect = Bounds() & update; 995 rgb_color oldColor = HighColor(); 996 997 SetHighColor(sMenuInfo.background_color); 998 FillRect(rect, B_SOLID_HIGH); 999 1000 SetHighColor(oldColor); 1001 } 1002 1003 1004 void 1005 BMenu::SetTrackingHook(menu_tracking_hook func, void *state) 1006 { 1007 delete fExtraMenuData; 1008 fExtraMenuData = new _ExtraMenuData_(func, state); 1009 } 1010 1011 1012 void BMenu::_ReservedMenu3() {} 1013 void BMenu::_ReservedMenu4() {} 1014 void BMenu::_ReservedMenu5() {} 1015 void BMenu::_ReservedMenu6() {} 1016 1017 1018 BMenu & 1019 BMenu::operator=(const BMenu &) 1020 { 1021 return *this; 1022 } 1023 1024 1025 void 1026 BMenu::InitData(BMessage *data) 1027 { 1028 // TODO: Get _color, _fname, _fflt from the message, if present 1029 BFont font; 1030 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1031 font.SetSize(sMenuInfo.font_size); 1032 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1033 1034 SetLowColor(sMenuInfo.background_color); 1035 SetViewColor(sMenuInfo.background_color); 1036 1037 if (data != NULL) { 1038 data->FindInt32("_layout", (int32 *)&fLayout); 1039 data->FindBool("_rsize_to_fit", &fResizeToFit); 1040 data->FindBool("_disable", &fEnabled); 1041 data->FindBool("_radio", &fRadioMode); 1042 1043 bool disableTrigger = false; 1044 data->FindBool("_trig_disabled", &disableTrigger); 1045 fTriggerEnabled = !disableTrigger; 1046 1047 data->FindBool("_dyn_label", &fDynamicName); 1048 data->FindFloat("_maxwidth", &fMaxContentWidth); 1049 } 1050 } 1051 1052 1053 bool 1054 BMenu::_show(bool selectFirstItem) 1055 { 1056 BWindow *menuWindow = MenuWindow(); 1057 1058 menuWindow->Lock(); 1059 menuWindow->ChildAt(0)->AddChild(this); 1060 1061 // We're doing this here because ConvertToScreen() needs: 1062 // 1. The BView to be attached (see the above line). 1063 // 2. The looper locked or not running (the Show() call below starts the looper) 1064 if (fSuper != NULL) 1065 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1066 1067 UpdateWindowViewSize(); 1068 1069 menuWindow->Unlock(); 1070 menuWindow->Show(); 1071 1072 return true; 1073 } 1074 1075 1076 void 1077 BMenu::_hide() 1078 { 1079 if (fCachedMenuWindow != NULL) { 1080 fCachedMenuWindow->Lock(); 1081 fCachedMenuWindow->ChildAt(0)->RemoveChild(this); 1082 fCachedMenuWindow->Hide(); 1083 fCachedMenuWindow->Unlock(); 1084 } 1085 1086 } 1087 1088 1089 BMenuItem * 1090 BMenu::_track(int *action, long start) 1091 { 1092 // TODO: Take Sticky mode into account 1093 BPoint location; 1094 ulong buttons; 1095 BMenuItem *item = NULL; 1096 int localAction = 0; 1097 do { 1098 if (LockLooper()) { 1099 GetMouse(&location, &buttons); 1100 if (OverSuper(location)) { 1101 UnlockLooper(); 1102 break; 1103 } 1104 1105 item = HitTestItems(location, B_ORIGIN); 1106 1107 // TODO: Sometimes the menu flickers a bit. 1108 // try to be smarter and suggest an update area, 1109 // instead of invalidating the whole view. 1110 if (item != NULL) { 1111 if (item != fSelected) { 1112 SelectItem(item); 1113 Invalidate(); 1114 } 1115 1116 int submenuAction = 0; 1117 BMenuItem *submenuItem = NULL; 1118 // TODO: Review this as it doesn't work very well, 1119 // BMenu::_track() isn't always called when needed. 1120 if (item->Submenu() != NULL) { 1121 UnlockLooper(); 1122 1123 submenuItem = item->Submenu()->_track(&submenuAction); 1124 if (submenuAction == 5) { 1125 item = submenuItem; 1126 localAction = submenuAction; 1127 break; 1128 } 1129 1130 if (!LockLooper()) 1131 break; 1132 } 1133 } 1134 1135 UnlockLooper(); 1136 } 1137 1138 snooze(50000); 1139 } while (buttons != 0); 1140 1141 // TODO: A deeper investigation of actions 1142 // would be nice. Consider building an enum 1143 // with the possible actions, and putting it in a 1144 // private, shared header (BMenuBar needs to know about them too). 1145 if (localAction == 0) { 1146 if (buttons != 0) 1147 localAction = 0; 1148 else 1149 localAction = 5; 1150 } 1151 1152 if (action != NULL) 1153 *action = localAction; 1154 1155 if (LockLooper()) { 1156 SelectItem(NULL); 1157 UnlockLooper(); 1158 } 1159 1160 return item; 1161 } 1162 1163 1164 bool 1165 BMenu::_AddItem(BMenuItem *item, int32 index) 1166 { 1167 ASSERT(item != NULL); 1168 1169 bool err = fItems.AddItem(item, index); 1170 1171 if (!err) 1172 return err; 1173 1174 item->SetSuper(this); 1175 1176 // Make sure we update the layout in case we are already attached. 1177 if (Window() && fResizeToFit) { 1178 LayoutItems(index); 1179 Invalidate(); 1180 } 1181 1182 // Find the root menu window, so we can install this item. 1183 BMenu *root = this; 1184 while (root->Supermenu()) 1185 root = root->Supermenu(); 1186 1187 if (root->Window()) 1188 Install(root->Window()); 1189 1190 return err; 1191 } 1192 1193 1194 bool 1195 BMenu::RemoveItems(int32 index, int32 count, BMenuItem *_item, bool del) 1196 { 1197 bool result = false; 1198 1199 // The plan is simple: If we're given a BMenuItem directly, we use it 1200 // and ignore index and count. Otherwise, we use them instead. 1201 if (_item != NULL) { 1202 fItems.RemoveItem(_item); 1203 _item->SetSuper(NULL); 1204 _item->Uninstall(); 1205 if (del) 1206 delete _item; 1207 result = true; 1208 } else { 1209 BMenuItem *item = NULL; 1210 // We iterate backwards because it's simpler 1211 // TODO: We should check if index and count are in bounds. 1212 for (int32 i = index + count - 1; i >= index; i--) { 1213 item = static_cast<BMenuItem *>(fItems.ItemAt(index)); 1214 if (item != NULL) { 1215 fItems.RemoveItem(item); 1216 item->SetSuper(NULL); 1217 item->Uninstall(); 1218 if (del) 1219 delete item; 1220 if (!result) 1221 result = true; 1222 } 1223 } 1224 } 1225 1226 InvalidateLayout(); 1227 1228 return result; 1229 } 1230 1231 1232 void 1233 BMenu::LayoutItems(int32 index) 1234 { 1235 CalcTriggers(); 1236 1237 float width, height; 1238 ComputeLayout(index, true, true, &width, &height); 1239 1240 ResizeTo(width, height); 1241 1242 // Move the BMenu to 1, 1, if it's attached to a BMenuWindow, 1243 // (that means it's a BMenu, BMenuBars are attached to regular BWindows). 1244 // This is needed to be able to draw the frame around the BMenu. 1245 if (dynamic_cast<BMenuWindow *>(Window()) != NULL) 1246 MoveTo(1, 1); 1247 } 1248 1249 1250 void 1251 BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems, 1252 float* width, float* height) 1253 { 1254 // TODO: Take "bestFit", "moveItems", "index" into account. 1255 BRect frame(0, 0, 0, 0); 1256 float iWidth, iHeight; 1257 BMenuItem *item = NULL; 1258 1259 switch (fLayout) { 1260 case B_ITEMS_IN_COLUMN: 1261 { 1262 for (int32 i = 0; i < fItems.CountItems(); i++) { 1263 item = ItemAt(i); 1264 if (item != NULL) { 1265 item->GetContentSize(&iWidth, &iHeight); 1266 1267 if (item->fModifiers && item->fShortcutChar) 1268 iWidth += 25.0f; 1269 1270 item->fBounds.left = 0.0f; 1271 item->fBounds.top = frame.bottom; 1272 item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom; 1273 1274 frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right) + 20; 1275 frame.bottom = item->fBounds.bottom + 1.0f; 1276 } 1277 } 1278 1279 for (int32 i = 0; i < fItems.CountItems(); i++) 1280 ItemAt(i)->fBounds.right = frame.right; 1281 1282 frame.right = (float)ceil(frame.right); 1283 frame.bottom--; 1284 break; 1285 } 1286 1287 case B_ITEMS_IN_ROW: 1288 { 1289 font_height fh; 1290 GetFontHeight(&fh); 1291 frame = BRect(0.0f, 0.0f, 0.0f, 1292 (float)ceil(fh.ascent) + (float)ceil(fh.descent) + fPad.top + fPad.bottom); 1293 1294 for (int32 i = 0; i < fItems.CountItems(); i++) { 1295 item = ItemAt(i); 1296 if (item != NULL) { 1297 item->GetContentSize(&iWidth, &iHeight); 1298 1299 item->fBounds.left = frame.right; 1300 item->fBounds.top = 0.0f; 1301 item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right; 1302 1303 frame.right = item->fBounds.right + 1.0f; 1304 frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom); 1305 } 1306 } 1307 1308 for (int32 i = 0; i < fItems.CountItems(); i++) 1309 ItemAt(i)->fBounds.bottom = frame.bottom; 1310 1311 frame.right = (float)ceil(frame.right) + 8.0f; 1312 break; 1313 } 1314 1315 case B_ITEMS_IN_MATRIX: 1316 { 1317 for (int32 i = 0; i < CountItems(); i++) { 1318 item = ItemAt(i); 1319 if (item != NULL) { 1320 frame.left = min_c(frame.left, item->Frame().left); 1321 frame.right = max_c(frame.right, item->Frame().right); 1322 frame.top = min_c(frame.top, item->Frame().top); 1323 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 1324 } 1325 } 1326 break; 1327 } 1328 1329 default: 1330 break; 1331 } 1332 1333 // This is for BMenuBar. 1334 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 1335 if (Parent()) 1336 *width = Parent()->Frame().Width() + 1; 1337 else 1338 *width = Window()->Frame().Width() + 1; 1339 1340 *height = frame.Height(); 1341 } else { 1342 *width = frame.Width(); 1343 *height = frame.Height(); 1344 } 1345 } 1346 1347 1348 BRect 1349 BMenu::Bump(BRect current, BPoint extent, int32 index) const 1350 { 1351 return BRect(); 1352 } 1353 1354 1355 BPoint 1356 BMenu::ItemLocInRect(BRect frame) const 1357 { 1358 return BPoint(); 1359 } 1360 1361 1362 BRect 1363 BMenu::CalcFrame(BPoint where, bool *scrollOn) 1364 { 1365 return BRect(); 1366 } 1367 1368 1369 bool 1370 BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast) 1371 { 1372 return false; 1373 } 1374 1375 1376 void 1377 BMenu::ScrollIntoView(BMenuItem *item) 1378 { 1379 } 1380 1381 1382 void 1383 BMenu::DrawItems(BRect updateRect) 1384 { 1385 for (int32 i = 0; i < fItems.CountItems(); i++) { 1386 if (ItemAt(i)->Frame().Intersects(updateRect)) 1387 ItemAt(i)->Draw(); 1388 } 1389 } 1390 1391 1392 int 1393 BMenu::State(BMenuItem **item) const 1394 { 1395 return 0; 1396 } 1397 1398 1399 void 1400 BMenu::InvokeItem(BMenuItem *item, bool now) 1401 { 1402 if (item->Submenu()) 1403 item->Submenu()->Show(); 1404 else if (IsRadioMode()) 1405 item->SetMarked(true); 1406 1407 item->Invoke(); 1408 } 1409 1410 1411 bool 1412 BMenu::OverSuper(BPoint location) 1413 { 1414 if (!Supermenu()) 1415 return false; 1416 1417 ConvertToScreen(&location); 1418 1419 return fSuperbounds.Contains(location); 1420 } 1421 1422 1423 bool 1424 BMenu::OverSubmenu(BMenuItem *item, BPoint loc) 1425 { 1426 // TODO: we assume that loc is in screen coords 1427 if (!item->Submenu()) 1428 return false; 1429 1430 return item->Submenu()->Window()->Frame().Contains(loc); 1431 } 1432 1433 1434 BMenuWindow * 1435 BMenu::MenuWindow() 1436 { 1437 if (fCachedMenuWindow == NULL) { 1438 // Menu windows get the BMenu's handler name 1439 fCachedMenuWindow = new BMenuWindow(Name()); 1440 UpdateWindowViewSize(); 1441 } 1442 1443 return fCachedMenuWindow; 1444 } 1445 1446 1447 void 1448 BMenu::DeleteMenuWindow() 1449 { 1450 if (fCachedMenuWindow != NULL) { 1451 fCachedMenuWindow->Lock(); 1452 fCachedMenuWindow->Quit(); 1453 fCachedMenuWindow = NULL; 1454 } 1455 } 1456 1457 1458 BMenuItem * 1459 BMenu::HitTestItems(BPoint where, BPoint slop) const 1460 { 1461 // TODO: Take "slop" into account ? 1462 int32 itemCount = CountItems(); 1463 for (int32 i = 0; i < itemCount; i++) { 1464 BMenuItem *item = ItemAt(i); 1465 if (item->Frame().Contains(where)) 1466 return item; 1467 } 1468 1469 return NULL; 1470 } 1471 1472 1473 BRect 1474 BMenu::Superbounds() const 1475 { 1476 return fSuperbounds; 1477 } 1478 1479 1480 void 1481 BMenu::CacheFontInfo() 1482 { 1483 font_height fh; 1484 GetFontHeight(&fh); 1485 fAscent = fh.ascent; 1486 fDescent = fh.descent; 1487 fFontHeight = (float)ceil(fh.ascent + fh.descent + fh.leading); 1488 } 1489 1490 1491 void 1492 BMenu::ItemMarked(BMenuItem *item) 1493 { 1494 if (IsRadioMode()) { 1495 for (int32 i = 0; i < CountItems(); i++) 1496 if (ItemAt(i) != item) 1497 ItemAt(i)->SetMarked(false); 1498 } 1499 1500 if (IsLabelFromMarked() && Superitem()) 1501 Superitem()->SetLabel(item->Label()); 1502 } 1503 1504 1505 void 1506 BMenu::Install(BWindow *target) 1507 { 1508 for (int32 i = 0; i < CountItems(); i++) 1509 ItemAt(i)->Install(target); 1510 } 1511 1512 1513 void 1514 BMenu::Uninstall() 1515 { 1516 for (int32 i = 0; i < CountItems(); i++) 1517 ItemAt(i)->Uninstall(); 1518 } 1519 1520 1521 void 1522 BMenu::SelectItem(BMenuItem *menuItem, uint32 showSubmenu, bool selectFirstItem) 1523 { 1524 // TODO: make use of "showSubmenu" and "selectFirstItem". 1525 if (fSelected != NULL) { 1526 fSelected->Select(false); 1527 if (fSelected->Submenu() != NULL) 1528 fSelected->Submenu()->_hide(); 1529 } 1530 1531 if (menuItem != NULL) 1532 menuItem->Select(true); 1533 1534 fSelected = menuItem; 1535 if (fSelected != NULL && fSelected->Submenu() != NULL) 1536 fSelected->Submenu()->_show(); 1537 1538 } 1539 1540 1541 BMenuItem * 1542 BMenu::CurrentSelection() const 1543 { 1544 return fSelected; 1545 } 1546 1547 1548 bool 1549 BMenu::SelectNextItem(BMenuItem *item, bool forward) 1550 { 1551 return false; 1552 } 1553 1554 1555 BMenuItem * 1556 BMenu::NextItem(BMenuItem *item, bool forward) const 1557 { 1558 return NULL; 1559 } 1560 1561 1562 bool 1563 BMenu::IsItemVisible(BMenuItem *item) const 1564 { 1565 return false; 1566 } 1567 1568 1569 void 1570 BMenu::SetIgnoreHidden(bool on) 1571 { 1572 fIgnoreHidden = on; 1573 } 1574 1575 1576 void 1577 BMenu::SetStickyMode(bool on) 1578 { 1579 fStickyMode = on; 1580 } 1581 1582 1583 bool 1584 BMenu::IsStickyMode() const 1585 { 1586 return fStickyMode; 1587 } 1588 1589 1590 void 1591 BMenu::CalcTriggers() 1592 { 1593 BList triggersList; 1594 1595 // Gathers the existing triggers 1596 // TODO: Oh great, reinterpret_cast. 1597 for (int32 i = 0; i < CountItems(); i++) { 1598 char trigger = ItemAt(i)->Trigger(); 1599 if (trigger != 0) 1600 triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger)); 1601 } 1602 1603 // Set triggers for items which don't have one yet 1604 for (int32 i = 0; i < CountItems(); i++) { 1605 BMenuItem *item = ItemAt(i); 1606 if (item->Trigger() == 0) { 1607 const char *newTrigger = ChooseTrigger(item->Label(), &triggersList); 1608 if (newTrigger != NULL) { 1609 item->SetSysTrigger(*newTrigger); 1610 // TODO: This is crap. I'd prefer to have 1611 // BMenuItem::SetSysTrigger(const char *) update fTriggerIndex. 1612 // This isn't the case on beos, but it will probably be like that on haiku. 1613 item->fTriggerIndex = newTrigger - item->Label(); 1614 } 1615 } 1616 } 1617 } 1618 1619 1620 const char * 1621 BMenu::ChooseTrigger(const char *title, BList *chars) 1622 { 1623 ASSERT(chars != NULL); 1624 1625 if (title == NULL) 1626 return NULL; 1627 1628 char *titlePtr = const_cast<char *>(title); 1629 1630 char trigger; 1631 // TODO: Oh great, reinterpret_cast all around 1632 while ((trigger = *titlePtr) != '\0') { 1633 if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger))) { 1634 chars->AddItem(reinterpret_cast<void *>((uint32)trigger)); 1635 return titlePtr; 1636 } 1637 1638 titlePtr++; 1639 } 1640 1641 return NULL; 1642 } 1643 1644 1645 void 1646 BMenu::UpdateWindowViewSize(bool upWind) 1647 { 1648 ASSERT(fCachedMenuWindow != NULL); 1649 fCachedMenuWindow->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2); 1650 fCachedMenuWindow->MoveTo(ScreenLocation()); 1651 } 1652 1653 1654 bool 1655 BMenu::IsStickyPrefOn() 1656 { 1657 return sMenuInfo.click_to_open; 1658 } 1659 1660 1661 void 1662 BMenu::RedrawAfterSticky(BRect bounds) 1663 { 1664 } 1665 1666 1667 bool 1668 BMenu::OkToProceed(BMenuItem *) 1669 { 1670 return false; 1671 } 1672 1673 1674 status_t 1675 BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec, 1676 int32 *form, const char **prop, BMenu **tmenu, 1677 BMenuItem **titem, int32 *user_data, 1678 BMessage *reply) const 1679 { 1680 return B_ERROR; 1681 } 1682 1683 1684 status_t 1685 BMenu::DoMenuMsg(BMenuItem **next, BMenu *tar, BMessage *m, 1686 BMessage *r, BMessage *spec, int32 f) const 1687 { 1688 return B_ERROR; 1689 } 1690 1691 1692 status_t 1693 BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *tar, BMessage *m, 1694 BMessage *r, BMessage *spec, int32 f) const 1695 { 1696 return B_ERROR; 1697 } 1698 1699 1700 status_t 1701 BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1702 BMessage *r) const 1703 { 1704 return B_ERROR; 1705 } 1706 1707 1708 status_t 1709 BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1710 BMessage *r) const 1711 { 1712 return B_ERROR; 1713 } 1714 1715 1716 status_t 1717 BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1718 BMessage *r) const 1719 { 1720 return B_ERROR; 1721 } 1722 1723 1724 status_t 1725 BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1726 BMessage *r) const 1727 { 1728 return B_ERROR; 1729 } 1730 1731 1732 status_t 1733 BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1734 BMessage *r, bool menu) const 1735 { 1736 return B_ERROR; 1737 } 1738 1739 1740 status_t 1741 set_menu_info(menu_info *info) 1742 { 1743 if (!info) 1744 return B_BAD_VALUE; 1745 1746 BPath path; 1747 1748 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 1749 return B_OK; 1750 1751 path.Append("menu_settings"); 1752 1753 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE); 1754 1755 if (file.InitCheck() != B_OK) 1756 return B_OK; 1757 1758 file.Write(info, sizeof(menu_info)); 1759 1760 BMenu::sMenuInfo = *info; 1761 1762 return B_OK; 1763 } 1764 1765 1766 status_t 1767 get_menu_info(menu_info *info) 1768 { 1769 if (!info) 1770 return B_BAD_VALUE; 1771 1772 *info = BMenu::sMenuInfo; 1773 1774 return B_OK; 1775 } 1776