1 /* 2 * Copyright 2001-2018 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Stefano Ceccherini, stefano.ceccherini@gmail.com 8 * Adrien Destugues, pulkomandy@pulkomandy.tk 9 * Marc Flerackers, mflerackers@androme.be 10 * Rene Gollent, anevilyak@gmail.com 11 * John Scipione, jscipione@gmail.com 12 */ 13 14 15 #include <Menu.h> 16 17 #include <algorithm> 18 #include <new> 19 #include <set> 20 21 #include <ctype.h> 22 #include <string.h> 23 24 #include <Application.h> 25 #include <Bitmap.h> 26 #include <ControlLook.h> 27 #include <Debug.h> 28 #include <File.h> 29 #include <FindDirectory.h> 30 #include <Layout.h> 31 #include <LayoutUtils.h> 32 #include <MenuBar.h> 33 #include <MenuItem.h> 34 #include <Messenger.h> 35 #include <Path.h> 36 #include <PropertyInfo.h> 37 #include <Screen.h> 38 #include <ScrollBar.h> 39 #include <SystemCatalog.h> 40 #include <UnicodeChar.h> 41 #include <Window.h> 42 43 #include <AppServerLink.h> 44 #include <AutoDeleter.h> 45 #include <binary_compatibility/Interface.h> 46 #include <BMCPrivate.h> 47 #include <MenuPrivate.h> 48 #include <MenuWindow.h> 49 #include <ServerProtocol.h> 50 51 #include "utf8_functions.h" 52 53 54 #define USE_CACHED_MENUWINDOW 1 55 56 using BPrivate::gSystemCatalog; 57 58 #undef B_TRANSLATION_CONTEXT 59 #define B_TRANSLATION_CONTEXT "Menu" 60 61 #undef B_TRANSLATE 62 #define B_TRANSLATE(str) \ 63 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Menu") 64 65 66 using std::nothrow; 67 using BPrivate::BMenuWindow; 68 69 namespace BPrivate { 70 71 class TriggerList { 72 public: 73 TriggerList() {} 74 ~TriggerList() {} 75 76 bool HasTrigger(uint32 c) 77 { return fList.find(BUnicodeChar::ToLower(c)) != fList.end(); } 78 79 bool AddTrigger(uint32 c) 80 { 81 fList.insert(BUnicodeChar::ToLower(c)); 82 return true; 83 } 84 85 private: 86 std::set<uint32> fList; 87 }; 88 89 90 class ExtraMenuData { 91 public: 92 menu_tracking_hook trackingHook; 93 void* trackingState; 94 95 // Used to track when the menu would be drawn offscreen and instead gets 96 // shifted back on the screen towards the left. This information 97 // allows us to draw submenus in the same direction as their parents. 98 bool frameShiftedLeft; 99 100 ExtraMenuData() 101 { 102 trackingHook = NULL; 103 trackingState = NULL; 104 frameShiftedLeft = false; 105 } 106 }; 107 108 109 typedef int (*compare_func)(const BMenuItem*, const BMenuItem*); 110 111 struct MenuItemComparator 112 { 113 MenuItemComparator(compare_func compareFunc) 114 : 115 fCompareFunc(compareFunc) 116 { 117 } 118 119 bool operator () (const BMenuItem* item1, const BMenuItem* item2) { 120 return fCompareFunc(item1, item2) < 0; 121 } 122 123 private: 124 compare_func fCompareFunc; 125 }; 126 127 128 } // namespace BPrivate 129 130 131 menu_info BMenu::sMenuInfo; 132 133 uint32 BMenu::sShiftKey; 134 uint32 BMenu::sControlKey; 135 uint32 BMenu::sOptionKey; 136 uint32 BMenu::sCommandKey; 137 uint32 BMenu::sMenuKey; 138 139 static property_info sPropList[] = { 140 { "Enabled", { B_GET_PROPERTY, 0 }, 141 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is " 142 "enabled; false otherwise.", 143 0, { B_BOOL_TYPE } 144 }, 145 146 { "Enabled", { B_SET_PROPERTY, 0 }, 147 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.", 148 0, { B_BOOL_TYPE } 149 }, 150 151 { "Label", { B_GET_PROPERTY, 0 }, 152 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or " 153 "menu item.", 154 0, { B_STRING_TYPE } 155 }, 156 157 { "Label", { B_SET_PROPERTY, 0 }, 158 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu " 159 "item.", 160 0, { B_STRING_TYPE } 161 }, 162 163 { "Mark", { B_GET_PROPERTY, 0 }, 164 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the " 165 "menu's superitem is marked; false otherwise.", 166 0, { B_BOOL_TYPE } 167 }, 168 169 { "Mark", { B_SET_PROPERTY, 0 }, 170 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the " 171 "menu's superitem.", 172 0, { B_BOOL_TYPE } 173 }, 174 175 { "Menu", { B_CREATE_PROPERTY, 0 }, 176 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 177 "Adds a new menu item at the specified index with the text label " 178 "found in \"data\" and the int32 command found in \"what\" (used as " 179 "the what field in the BMessage sent by the item)." , 0, {}, 180 { {{{"data", B_STRING_TYPE}}} 181 } 182 }, 183 184 { "Menu", { B_DELETE_PROPERTY, 0 }, 185 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 186 "Removes the selected menu or menus.", 0, {} 187 }, 188 189 { "Menu", { }, 190 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 191 "Directs scripting message to the specified menu, first popping the " 192 "current specifier off the stack.", 0, {} 193 }, 194 195 { "MenuItem", { B_COUNT_PROPERTIES, 0 }, 196 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the " 197 "specified menu.", 198 0, { B_INT32_TYPE } 199 }, 200 201 { "MenuItem", { B_CREATE_PROPERTY, 0 }, 202 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 203 "Adds a new menu item at the specified index with the text label " 204 "found in \"data\" and the int32 command found in \"what\" (used as " 205 "the what field in the BMessage sent by the item).", 0, {}, 206 { { {{"data", B_STRING_TYPE }, 207 {"be:invoke_message", B_MESSAGE_TYPE}, 208 {"what", B_INT32_TYPE}, 209 {"be:target", B_MESSENGER_TYPE}} } 210 } 211 }, 212 213 { "MenuItem", { B_DELETE_PROPERTY, 0 }, 214 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 215 "Removes the specified menu item from its parent menu." 216 }, 217 218 { "MenuItem", { B_EXECUTE_PROPERTY, 0 }, 219 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 220 "Invokes the specified menu item." 221 }, 222 223 { "MenuItem", { }, 224 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 225 "Directs scripting message to the specified menu, first popping the " 226 "current specifier off the stack." 227 }, 228 229 { 0 } 230 }; 231 232 233 // note: this is redefined to localized one in BMenu::_InitData 234 const char* BPrivate::kEmptyMenuLabel = "<empty>"; 235 236 237 struct BMenu::LayoutData { 238 BSize preferred; 239 uint32 lastResizingMode; 240 }; 241 242 243 // #pragma mark - BMenu 244 245 246 BMenu::BMenu(const char* name, menu_layout layout) 247 : 248 BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW), 249 fChosenItem(NULL), 250 fSelected(NULL), 251 fCachedMenuWindow(NULL), 252 fSuper(NULL), 253 fSuperitem(NULL), 254 fAscent(-1.0f), 255 fDescent(-1.0f), 256 fFontHeight(-1.0f), 257 fState(MENU_STATE_CLOSED), 258 fLayout(layout), 259 fExtraRect(NULL), 260 fMaxContentWidth(0.0f), 261 fInitMatrixSize(NULL), 262 fExtraMenuData(NULL), 263 fTrigger(0), 264 fResizeToFit(true), 265 fUseCachedMenuLayout(false), 266 fEnabled(true), 267 fDynamicName(false), 268 fRadioMode(false), 269 fTrackNewBounds(false), 270 fStickyMode(false), 271 fIgnoreHidden(true), 272 fTriggerEnabled(true), 273 fHasSubmenus(false), 274 fAttachAborted(false) 275 { 276 _InitData(NULL); 277 } 278 279 280 BMenu::BMenu(const char* name, float width, float height) 281 : 282 BView(BRect(0.0f, 0.0f, 0.0f, 0.0f), name, 0, B_WILL_DRAW), 283 fChosenItem(NULL), 284 fSelected(NULL), 285 fCachedMenuWindow(NULL), 286 fSuper(NULL), 287 fSuperitem(NULL), 288 fAscent(-1.0f), 289 fDescent(-1.0f), 290 fFontHeight(-1.0f), 291 fState(0), 292 fLayout(B_ITEMS_IN_MATRIX), 293 fExtraRect(NULL), 294 fMaxContentWidth(0.0f), 295 fInitMatrixSize(NULL), 296 fExtraMenuData(NULL), 297 fTrigger(0), 298 fResizeToFit(true), 299 fUseCachedMenuLayout(false), 300 fEnabled(true), 301 fDynamicName(false), 302 fRadioMode(false), 303 fTrackNewBounds(false), 304 fStickyMode(false), 305 fIgnoreHidden(true), 306 fTriggerEnabled(true), 307 fHasSubmenus(false), 308 fAttachAborted(false) 309 { 310 _InitData(NULL); 311 } 312 313 314 BMenu::BMenu(BMessage* archive) 315 : 316 BView(archive), 317 fChosenItem(NULL), 318 fSelected(NULL), 319 fCachedMenuWindow(NULL), 320 fSuper(NULL), 321 fSuperitem(NULL), 322 fAscent(-1.0f), 323 fDescent(-1.0f), 324 fFontHeight(-1.0f), 325 fState(MENU_STATE_CLOSED), 326 fLayout(B_ITEMS_IN_ROW), 327 fExtraRect(NULL), 328 fMaxContentWidth(0.0f), 329 fInitMatrixSize(NULL), 330 fExtraMenuData(NULL), 331 fTrigger(0), 332 fResizeToFit(true), 333 fUseCachedMenuLayout(false), 334 fEnabled(true), 335 fDynamicName(false), 336 fRadioMode(false), 337 fTrackNewBounds(false), 338 fStickyMode(false), 339 fIgnoreHidden(true), 340 fTriggerEnabled(true), 341 fHasSubmenus(false), 342 fAttachAborted(false) 343 { 344 _InitData(archive); 345 } 346 347 348 BMenu::~BMenu() 349 { 350 _DeleteMenuWindow(); 351 352 RemoveItems(0, CountItems(), true); 353 354 delete fInitMatrixSize; 355 delete fExtraMenuData; 356 delete fLayoutData; 357 } 358 359 360 BArchivable* 361 BMenu::Instantiate(BMessage* archive) 362 { 363 if (validate_instantiation(archive, "BMenu")) 364 return new (nothrow) BMenu(archive); 365 366 return NULL; 367 } 368 369 370 status_t 371 BMenu::Archive(BMessage* data, bool deep) const 372 { 373 status_t err = BView::Archive(data, deep); 374 375 if (err == B_OK && Layout() != B_ITEMS_IN_ROW) 376 err = data->AddInt32("_layout", Layout()); 377 if (err == B_OK) 378 err = data->AddBool("_rsize_to_fit", fResizeToFit); 379 if (err == B_OK) 380 err = data->AddBool("_disable", !IsEnabled()); 381 if (err == B_OK) 382 err = data->AddBool("_radio", IsRadioMode()); 383 if (err == B_OK) 384 err = data->AddBool("_trig_disabled", AreTriggersEnabled()); 385 if (err == B_OK) 386 err = data->AddBool("_dyn_label", fDynamicName); 387 if (err == B_OK) 388 err = data->AddFloat("_maxwidth", fMaxContentWidth); 389 if (err == B_OK && deep) { 390 BMenuItem* item = NULL; 391 int32 index = 0; 392 while ((item = ItemAt(index++)) != NULL) { 393 BMessage itemData; 394 item->Archive(&itemData, deep); 395 err = data->AddMessage("_items", &itemData); 396 if (err != B_OK) 397 break; 398 if (fLayout == B_ITEMS_IN_MATRIX) { 399 err = data->AddRect("_i_frames", item->fBounds); 400 } 401 } 402 } 403 404 return err; 405 } 406 407 408 void 409 BMenu::AttachedToWindow() 410 { 411 BView::AttachedToWindow(); 412 413 _GetShiftKey(sShiftKey); 414 _GetControlKey(sControlKey); 415 _GetCommandKey(sCommandKey); 416 _GetOptionKey(sOptionKey); 417 _GetMenuKey(sMenuKey); 418 419 // The menu should be added to the menu hierarchy and made visible if: 420 // * the mouse is over the menu, 421 // * the user has requested the menu via the keyboard. 422 // So if we don't pass keydown in here, keyboard navigation breaks since 423 // fAttachAborted will return false if the mouse isn't over the menu 424 bool keyDown = Supermenu() != NULL 425 ? Supermenu()->fState == MENU_STATE_KEY_TO_SUBMENU : false; 426 fAttachAborted = _AddDynamicItems(keyDown); 427 428 if (!fAttachAborted) { 429 _CacheFontInfo(); 430 _LayoutItems(0); 431 _UpdateWindowViewSize(false); 432 } 433 } 434 435 436 void 437 BMenu::DetachedFromWindow() 438 { 439 BView::DetachedFromWindow(); 440 } 441 442 443 void 444 BMenu::AllAttached() 445 { 446 BView::AllAttached(); 447 } 448 449 450 void 451 BMenu::AllDetached() 452 { 453 BView::AllDetached(); 454 } 455 456 457 void 458 BMenu::Draw(BRect updateRect) 459 { 460 if (_RelayoutIfNeeded()) { 461 Invalidate(); 462 return; 463 } 464 465 DrawBackground(updateRect); 466 DrawItems(updateRect); 467 } 468 469 470 void 471 BMenu::MessageReceived(BMessage* message) 472 { 473 if (message->HasSpecifiers()) 474 return _ScriptReceived(message); 475 476 switch (message->what) { 477 case B_MOUSE_WHEEL_CHANGED: 478 { 479 float deltaY = 0; 480 message->FindFloat("be:wheel_delta_y", &deltaY); 481 if (deltaY == 0) 482 return; 483 484 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 485 if (window == NULL) 486 return; 487 488 float largeStep; 489 float smallStep; 490 window->GetSteps(&smallStep, &largeStep); 491 492 // pressing the shift key scrolls faster 493 if ((modifiers() & B_SHIFT_KEY) != 0) 494 deltaY *= largeStep; 495 else 496 deltaY *= smallStep; 497 498 window->TryScrollBy(deltaY); 499 break; 500 } 501 502 default: 503 BView::MessageReceived(message); 504 break; 505 } 506 } 507 508 509 void 510 BMenu::KeyDown(const char* bytes, int32 numBytes) 511 { 512 // TODO: Test how it works on BeOS R5 and implement this correctly 513 switch (bytes[0]) { 514 case B_UP_ARROW: 515 case B_DOWN_ARROW: 516 { 517 BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu()); 518 if (bar != NULL && fState == MENU_STATE_CLOSED) { 519 // tell MenuBar's _Track: 520 bar->fState = MENU_STATE_KEY_TO_SUBMENU; 521 } 522 if (fLayout == B_ITEMS_IN_COLUMN) 523 _SelectNextItem(fSelected, bytes[0] == B_DOWN_ARROW); 524 break; 525 } 526 527 case B_LEFT_ARROW: 528 if (fLayout == B_ITEMS_IN_ROW) 529 _SelectNextItem(fSelected, false); 530 else { 531 // this case has to be handled a bit specially. 532 BMenuItem* item = Superitem(); 533 if (item) { 534 if (dynamic_cast<BMenuBar*>(Supermenu())) { 535 // If we're at the top menu below the menu bar, pass 536 // the keypress to the menu bar so we can move to 537 // another top level menu. 538 BMessenger messenger(Supermenu()); 539 messenger.SendMessage(Window()->CurrentMessage()); 540 } else { 541 // tell _Track 542 fState = MENU_STATE_KEY_LEAVE_SUBMENU; 543 } 544 } 545 } 546 break; 547 548 case B_RIGHT_ARROW: 549 if (fLayout == B_ITEMS_IN_ROW) 550 _SelectNextItem(fSelected, true); 551 else { 552 if (fSelected != NULL && fSelected->Submenu() != NULL) { 553 fSelected->Submenu()->_SetStickyMode(true); 554 // fix me: this shouldn't be needed but dynamic menus 555 // aren't getting it set correctly when keyboard 556 // navigating, which aborts the attach 557 fState = MENU_STATE_KEY_TO_SUBMENU; 558 _SelectItem(fSelected, true, true, true); 559 } else if (dynamic_cast<BMenuBar*>(Supermenu())) { 560 // if we have no submenu and we're an 561 // item in the top menu below the menubar, 562 // pass the keypress to the menubar 563 // so you can use the keypress to switch menus. 564 BMessenger messenger(Supermenu()); 565 messenger.SendMessage(Window()->CurrentMessage()); 566 } 567 } 568 break; 569 570 case B_PAGE_UP: 571 case B_PAGE_DOWN: 572 { 573 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 574 if (window == NULL || !window->HasScrollers()) 575 break; 576 577 int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1; 578 579 float largeStep; 580 window->GetSteps(NULL, &largeStep); 581 window->TryScrollBy(deltaY * largeStep); 582 break; 583 } 584 585 case B_ENTER: 586 case B_SPACE: 587 if (fSelected != NULL) { 588 fChosenItem = fSelected; 589 // preserve for exit handling 590 _QuitTracking(false); 591 } 592 break; 593 594 case B_ESCAPE: 595 _SelectItem(NULL); 596 if (fState == MENU_STATE_CLOSED 597 && dynamic_cast<BMenuBar*>(Supermenu())) { 598 // Keyboard may show menu without tracking it 599 BMessenger messenger(Supermenu()); 600 messenger.SendMessage(Window()->CurrentMessage()); 601 } else 602 _QuitTracking(false); 603 break; 604 605 default: 606 { 607 if (AreTriggersEnabled()) { 608 uint32 trigger = BUnicodeChar::FromUTF8(&bytes); 609 610 for (uint32 i = CountItems(); i-- > 0;) { 611 BMenuItem* item = ItemAt(i); 612 if (item->fTriggerIndex < 0 || item->fTrigger != trigger) 613 continue; 614 615 _InvokeItem(item); 616 _QuitTracking(false); 617 break; 618 } 619 } 620 break; 621 } 622 } 623 } 624 625 626 BSize 627 BMenu::MinSize() 628 { 629 _ValidatePreferredSize(); 630 631 BSize size = (GetLayout() != NULL ? GetLayout()->MinSize() 632 : fLayoutData->preferred); 633 634 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 635 } 636 637 638 BSize 639 BMenu::MaxSize() 640 { 641 _ValidatePreferredSize(); 642 643 BSize size = (GetLayout() != NULL ? GetLayout()->MaxSize() 644 : fLayoutData->preferred); 645 646 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 647 } 648 649 650 BSize 651 BMenu::PreferredSize() 652 { 653 _ValidatePreferredSize(); 654 655 BSize size = (GetLayout() != NULL ? GetLayout()->PreferredSize() 656 : fLayoutData->preferred); 657 658 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 659 } 660 661 662 void 663 BMenu::GetPreferredSize(float* _width, float* _height) 664 { 665 _ValidatePreferredSize(); 666 667 if (_width) 668 *_width = fLayoutData->preferred.width; 669 670 if (_height) 671 *_height = fLayoutData->preferred.height; 672 } 673 674 675 void 676 BMenu::ResizeToPreferred() 677 { 678 BView::ResizeToPreferred(); 679 } 680 681 682 void 683 BMenu::DoLayout() 684 { 685 // If the user set a layout, we let the base class version call its 686 // hook. 687 if (GetLayout() != NULL) { 688 BView::DoLayout(); 689 return; 690 } 691 692 if (_RelayoutIfNeeded()) 693 Invalidate(); 694 } 695 696 697 void 698 BMenu::FrameMoved(BPoint where) 699 { 700 BView::FrameMoved(where); 701 } 702 703 704 void 705 BMenu::FrameResized(float width, float height) 706 { 707 BView::FrameResized(width, height); 708 } 709 710 711 void 712 BMenu::InvalidateLayout() 713 { 714 fUseCachedMenuLayout = false; 715 // This method exits for backwards compatibility reasons, it is used to 716 // invalidate the menu layout, but we also use call 717 // BView::InvalidateLayout() for good measure. Don't delete this method! 718 BView::InvalidateLayout(false); 719 } 720 721 722 void 723 BMenu::MakeFocus(bool focused) 724 { 725 BView::MakeFocus(focused); 726 } 727 728 729 bool 730 BMenu::AddItem(BMenuItem* item) 731 { 732 return AddItem(item, CountItems()); 733 } 734 735 736 bool 737 BMenu::AddItem(BMenuItem* item, int32 index) 738 { 739 if (fLayout == B_ITEMS_IN_MATRIX) { 740 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only " 741 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 742 } 743 744 if (item == NULL) 745 return false; 746 747 const bool locked = LockLooper(); 748 749 if (!_AddItem(item, index)) { 750 if (locked) 751 UnlockLooper(); 752 return false; 753 } 754 755 InvalidateLayout(); 756 if (locked) { 757 if (!Window()->IsHidden()) { 758 _LayoutItems(index); 759 _UpdateWindowViewSize(false); 760 Invalidate(); 761 } 762 UnlockLooper(); 763 } 764 765 return true; 766 } 767 768 769 bool 770 BMenu::AddItem(BMenuItem* item, BRect frame) 771 { 772 if (fLayout != B_ITEMS_IN_MATRIX) { 773 debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only " 774 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 775 } 776 777 if (item == NULL) 778 return false; 779 780 const bool locked = LockLooper(); 781 782 item->fBounds = frame; 783 784 int32 index = CountItems(); 785 if (!_AddItem(item, index)) { 786 if (locked) 787 UnlockLooper(); 788 return false; 789 } 790 791 if (locked) { 792 if (!Window()->IsHidden()) { 793 _LayoutItems(index); 794 Invalidate(); 795 } 796 UnlockLooper(); 797 } 798 799 return true; 800 } 801 802 803 bool 804 BMenu::AddItem(BMenu* submenu) 805 { 806 BMenuItem* item = new (nothrow) BMenuItem(submenu); 807 if (item == NULL) 808 return false; 809 810 if (!AddItem(item, CountItems())) { 811 item->fSubmenu = NULL; 812 delete item; 813 return false; 814 } 815 816 return true; 817 } 818 819 820 bool 821 BMenu::AddItem(BMenu* submenu, int32 index) 822 { 823 if (fLayout == B_ITEMS_IN_MATRIX) { 824 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only " 825 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 826 } 827 828 BMenuItem* item = new (nothrow) BMenuItem(submenu); 829 if (item == NULL) 830 return false; 831 832 if (!AddItem(item, index)) { 833 item->fSubmenu = NULL; 834 delete item; 835 return false; 836 } 837 838 return true; 839 } 840 841 842 bool 843 BMenu::AddItem(BMenu* submenu, BRect frame) 844 { 845 if (fLayout != B_ITEMS_IN_MATRIX) { 846 debugger("BMenu::AddItem(BMenu*, BRect) this method can only " 847 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 848 } 849 850 BMenuItem* item = new (nothrow) BMenuItem(submenu); 851 if (item == NULL) 852 return false; 853 854 if (!AddItem(item, frame)) { 855 item->fSubmenu = NULL; 856 delete item; 857 return false; 858 } 859 860 return true; 861 } 862 863 864 bool 865 BMenu::AddList(BList* list, int32 index) 866 { 867 // TODO: test this function, it's not documented in the bebook. 868 if (list == NULL) 869 return false; 870 871 bool locked = LockLooper(); 872 873 int32 numItems = list->CountItems(); 874 for (int32 i = 0; i < numItems; i++) { 875 BMenuItem* item = static_cast<BMenuItem*>(list->ItemAt(i)); 876 if (item != NULL) { 877 if (!_AddItem(item, index + i)) 878 break; 879 } 880 } 881 882 InvalidateLayout(); 883 if (locked && Window() != NULL && !Window()->IsHidden()) { 884 // Make sure we update the layout if needed. 885 _LayoutItems(index); 886 _UpdateWindowViewSize(false); 887 Invalidate(); 888 } 889 890 if (locked) 891 UnlockLooper(); 892 893 return true; 894 } 895 896 897 bool 898 BMenu::AddSeparatorItem() 899 { 900 BMenuItem* item = new (nothrow) BSeparatorItem(); 901 if (!item || !AddItem(item, CountItems())) { 902 delete item; 903 return false; 904 } 905 906 return true; 907 } 908 909 910 bool 911 BMenu::RemoveItem(BMenuItem* item) 912 { 913 return _RemoveItems(0, 0, item, false); 914 } 915 916 917 BMenuItem* 918 BMenu::RemoveItem(int32 index) 919 { 920 BMenuItem* item = ItemAt(index); 921 if (item != NULL) 922 _RemoveItems(index, 1, NULL, false); 923 return item; 924 } 925 926 927 bool 928 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems) 929 { 930 return _RemoveItems(index, count, NULL, deleteItems); 931 } 932 933 934 bool 935 BMenu::RemoveItem(BMenu* submenu) 936 { 937 for (int32 i = 0; i < fItems.CountItems(); i++) { 938 if (static_cast<BMenuItem*>(fItems.ItemAtFast(i))->Submenu() 939 == submenu) { 940 return _RemoveItems(i, 1, NULL, false); 941 } 942 } 943 944 return false; 945 } 946 947 948 int32 949 BMenu::CountItems() const 950 { 951 return fItems.CountItems(); 952 } 953 954 955 BMenuItem* 956 BMenu::ItemAt(int32 index) const 957 { 958 return static_cast<BMenuItem*>(fItems.ItemAt(index)); 959 } 960 961 962 BMenu* 963 BMenu::SubmenuAt(int32 index) const 964 { 965 BMenuItem* item = static_cast<BMenuItem*>(fItems.ItemAt(index)); 966 return item != NULL ? item->Submenu() : NULL; 967 } 968 969 970 int32 971 BMenu::IndexOf(BMenuItem* item) const 972 { 973 return fItems.IndexOf(item); 974 } 975 976 977 int32 978 BMenu::IndexOf(BMenu* submenu) const 979 { 980 for (int32 i = 0; i < fItems.CountItems(); i++) { 981 if (ItemAt(i)->Submenu() == submenu) 982 return i; 983 } 984 985 return -1; 986 } 987 988 989 BMenuItem* 990 BMenu::FindItem(const char* label) const 991 { 992 BMenuItem* item = NULL; 993 994 for (int32 i = 0; i < CountItems(); i++) { 995 item = ItemAt(i); 996 997 if (item->Label() && strcmp(item->Label(), label) == 0) 998 return item; 999 1000 if (item->Submenu() != NULL) { 1001 item = item->Submenu()->FindItem(label); 1002 if (item != NULL) 1003 return item; 1004 } 1005 } 1006 1007 return NULL; 1008 } 1009 1010 1011 BMenuItem* 1012 BMenu::FindItem(uint32 command) const 1013 { 1014 BMenuItem* item = NULL; 1015 1016 for (int32 i = 0; i < CountItems(); i++) { 1017 item = ItemAt(i); 1018 1019 if (item->Command() == command) 1020 return item; 1021 1022 if (item->Submenu() != NULL) { 1023 item = item->Submenu()->FindItem(command); 1024 if (item != NULL) 1025 return item; 1026 } 1027 } 1028 1029 return NULL; 1030 } 1031 1032 1033 status_t 1034 BMenu::SetTargetForItems(BHandler* handler) 1035 { 1036 status_t status = B_OK; 1037 for (int32 i = 0; i < fItems.CountItems(); i++) { 1038 status = ItemAt(i)->SetTarget(handler); 1039 if (status < B_OK) 1040 break; 1041 } 1042 1043 return status; 1044 } 1045 1046 1047 status_t 1048 BMenu::SetTargetForItems(BMessenger messenger) 1049 { 1050 status_t status = B_OK; 1051 for (int32 i = 0; i < fItems.CountItems(); i++) { 1052 status = ItemAt(i)->SetTarget(messenger); 1053 if (status < B_OK) 1054 break; 1055 } 1056 1057 return status; 1058 } 1059 1060 1061 void 1062 BMenu::SetEnabled(bool enable) 1063 { 1064 if (fEnabled == enable) 1065 return; 1066 1067 fEnabled = enable; 1068 1069 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) 1070 Supermenu()->SetEnabled(enable); 1071 1072 if (fSuperitem) 1073 fSuperitem->SetEnabled(enable); 1074 } 1075 1076 1077 void 1078 BMenu::SetRadioMode(bool on) 1079 { 1080 fRadioMode = on; 1081 if (!on) 1082 SetLabelFromMarked(false); 1083 } 1084 1085 1086 void 1087 BMenu::SetTriggersEnabled(bool enable) 1088 { 1089 fTriggerEnabled = enable; 1090 } 1091 1092 1093 void 1094 BMenu::SetMaxContentWidth(float width) 1095 { 1096 fMaxContentWidth = width; 1097 } 1098 1099 1100 void 1101 BMenu::SetLabelFromMarked(bool on) 1102 { 1103 fDynamicName = on; 1104 if (on) 1105 SetRadioMode(true); 1106 } 1107 1108 1109 bool 1110 BMenu::IsLabelFromMarked() 1111 { 1112 return fDynamicName; 1113 } 1114 1115 1116 bool 1117 BMenu::IsEnabled() const 1118 { 1119 if (!fEnabled) 1120 return false; 1121 1122 return fSuper ? fSuper->IsEnabled() : true ; 1123 } 1124 1125 1126 bool 1127 BMenu::IsRadioMode() const 1128 { 1129 return fRadioMode; 1130 } 1131 1132 1133 bool 1134 BMenu::AreTriggersEnabled() const 1135 { 1136 return fTriggerEnabled; 1137 } 1138 1139 1140 bool 1141 BMenu::IsRedrawAfterSticky() const 1142 { 1143 return false; 1144 } 1145 1146 1147 float 1148 BMenu::MaxContentWidth() const 1149 { 1150 return fMaxContentWidth; 1151 } 1152 1153 1154 BMenuItem* 1155 BMenu::FindMarked() 1156 { 1157 for (int32 i = 0; i < fItems.CountItems(); i++) { 1158 BMenuItem* item = ItemAt(i); 1159 1160 if (item->IsMarked()) 1161 return item; 1162 } 1163 1164 return NULL; 1165 } 1166 1167 1168 int32 1169 BMenu::FindMarkedIndex() 1170 { 1171 for (int32 i = 0; i < fItems.CountItems(); i++) { 1172 BMenuItem* item = ItemAt(i); 1173 1174 if (item->IsMarked()) 1175 return i; 1176 } 1177 1178 return -1; 1179 } 1180 1181 1182 BMenu* 1183 BMenu::Supermenu() const 1184 { 1185 return fSuper; 1186 } 1187 1188 1189 BMenuItem* 1190 BMenu::Superitem() const 1191 { 1192 return fSuperitem; 1193 } 1194 1195 1196 BHandler* 1197 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier, 1198 int32 form, const char* property) 1199 { 1200 BPropertyInfo propInfo(sPropList); 1201 BHandler* target = NULL; 1202 1203 if (propInfo.FindMatch(msg, index, specifier, form, property) >= B_OK) { 1204 target = this; 1205 } 1206 1207 if (!target) 1208 target = BView::ResolveSpecifier(msg, index, specifier, form, 1209 property); 1210 1211 return target; 1212 } 1213 1214 1215 status_t 1216 BMenu::GetSupportedSuites(BMessage* data) 1217 { 1218 if (data == NULL) 1219 return B_BAD_VALUE; 1220 1221 status_t err = data->AddString("suites", "suite/vnd.Be-menu"); 1222 1223 if (err < B_OK) 1224 return err; 1225 1226 BPropertyInfo propertyInfo(sPropList); 1227 err = data->AddFlat("messages", &propertyInfo); 1228 1229 if (err < B_OK) 1230 return err; 1231 1232 return BView::GetSupportedSuites(data); 1233 } 1234 1235 1236 status_t 1237 BMenu::Perform(perform_code code, void* _data) 1238 { 1239 switch (code) { 1240 case PERFORM_CODE_MIN_SIZE: 1241 ((perform_data_min_size*)_data)->return_value 1242 = BMenu::MinSize(); 1243 return B_OK; 1244 1245 case PERFORM_CODE_MAX_SIZE: 1246 ((perform_data_max_size*)_data)->return_value 1247 = BMenu::MaxSize(); 1248 return B_OK; 1249 1250 case PERFORM_CODE_PREFERRED_SIZE: 1251 ((perform_data_preferred_size*)_data)->return_value 1252 = BMenu::PreferredSize(); 1253 return B_OK; 1254 1255 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1256 ((perform_data_layout_alignment*)_data)->return_value 1257 = BMenu::LayoutAlignment(); 1258 return B_OK; 1259 1260 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1261 ((perform_data_has_height_for_width*)_data)->return_value 1262 = BMenu::HasHeightForWidth(); 1263 return B_OK; 1264 1265 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1266 { 1267 perform_data_get_height_for_width* data 1268 = (perform_data_get_height_for_width*)_data; 1269 BMenu::GetHeightForWidth(data->width, &data->min, &data->max, 1270 &data->preferred); 1271 return B_OK; 1272 } 1273 1274 case PERFORM_CODE_SET_LAYOUT: 1275 { 1276 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1277 BMenu::SetLayout(data->layout); 1278 return B_OK; 1279 } 1280 1281 case PERFORM_CODE_LAYOUT_INVALIDATED: 1282 { 1283 perform_data_layout_invalidated* data 1284 = (perform_data_layout_invalidated*)_data; 1285 BMenu::LayoutInvalidated(data->descendants); 1286 return B_OK; 1287 } 1288 1289 case PERFORM_CODE_DO_LAYOUT: 1290 { 1291 BMenu::DoLayout(); 1292 return B_OK; 1293 } 1294 } 1295 1296 return BView::Perform(code, _data); 1297 } 1298 1299 1300 // #pragma mark - BMenu protected methods 1301 1302 1303 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags, 1304 menu_layout layout, bool resizeToFit) 1305 : 1306 BView(frame, name, resizingMode, flags), 1307 fChosenItem(NULL), 1308 fSelected(NULL), 1309 fCachedMenuWindow(NULL), 1310 fSuper(NULL), 1311 fSuperitem(NULL), 1312 fAscent(-1.0f), 1313 fDescent(-1.0f), 1314 fFontHeight(-1.0f), 1315 fState(MENU_STATE_CLOSED), 1316 fLayout(layout), 1317 fExtraRect(NULL), 1318 fMaxContentWidth(0.0f), 1319 fInitMatrixSize(NULL), 1320 fExtraMenuData(NULL), 1321 fTrigger(0), 1322 fResizeToFit(resizeToFit), 1323 fUseCachedMenuLayout(false), 1324 fEnabled(true), 1325 fDynamicName(false), 1326 fRadioMode(false), 1327 fTrackNewBounds(false), 1328 fStickyMode(false), 1329 fIgnoreHidden(true), 1330 fTriggerEnabled(true), 1331 fHasSubmenus(false), 1332 fAttachAborted(false) 1333 { 1334 _InitData(NULL); 1335 } 1336 1337 1338 void 1339 BMenu::SetItemMargins(float left, float top, float right, float bottom) 1340 { 1341 fPad.Set(left, top, right, bottom); 1342 } 1343 1344 1345 void 1346 BMenu::GetItemMargins(float* _left, float* _top, float* _right, 1347 float* _bottom) const 1348 { 1349 if (_left != NULL) 1350 *_left = fPad.left; 1351 1352 if (_top != NULL) 1353 *_top = fPad.top; 1354 1355 if (_right != NULL) 1356 *_right = fPad.right; 1357 1358 if (_bottom != NULL) 1359 *_bottom = fPad.bottom; 1360 } 1361 1362 1363 menu_layout 1364 BMenu::Layout() const 1365 { 1366 return fLayout; 1367 } 1368 1369 1370 void 1371 BMenu::Show() 1372 { 1373 Show(false); 1374 } 1375 1376 1377 void 1378 BMenu::Show(bool selectFirst) 1379 { 1380 _Install(NULL); 1381 _Show(selectFirst); 1382 } 1383 1384 1385 void 1386 BMenu::Hide() 1387 { 1388 _Hide(); 1389 _Uninstall(); 1390 } 1391 1392 1393 BMenuItem* 1394 BMenu::Track(bool sticky, BRect* clickToOpenRect) 1395 { 1396 if (sticky && LockLooper()) { 1397 //RedrawAfterSticky(Bounds()); 1398 // the call above didn't do anything, so I've removed it for now 1399 UnlockLooper(); 1400 } 1401 1402 if (clickToOpenRect != NULL && LockLooper()) { 1403 fExtraRect = clickToOpenRect; 1404 ConvertFromScreen(fExtraRect); 1405 UnlockLooper(); 1406 } 1407 1408 _SetStickyMode(sticky); 1409 1410 int action; 1411 BMenuItem* menuItem = _Track(&action); 1412 1413 fExtraRect = NULL; 1414 1415 return menuItem; 1416 } 1417 1418 1419 // #pragma mark - BMenu private methods 1420 1421 1422 bool 1423 BMenu::AddDynamicItem(add_state state) 1424 { 1425 // Implemented in subclasses 1426 return false; 1427 } 1428 1429 1430 void 1431 BMenu::DrawBackground(BRect updateRect) 1432 { 1433 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR); 1434 uint32 flags = 0; 1435 if (!IsEnabled()) 1436 flags |= BControlLook::B_DISABLED; 1437 1438 if (IsFocus()) 1439 flags |= BControlLook::B_FOCUSED; 1440 1441 BRect rect = Bounds(); 1442 uint32 borders = BControlLook::B_LEFT_BORDER 1443 | BControlLook::B_RIGHT_BORDER; 1444 if (Window() != NULL && Parent() != NULL) { 1445 if (Parent()->Frame().top == Window()->Bounds().top) 1446 borders |= BControlLook::B_TOP_BORDER; 1447 1448 if (Parent()->Frame().bottom == Window()->Bounds().bottom) 1449 borders |= BControlLook::B_BOTTOM_BORDER; 1450 } else { 1451 borders |= BControlLook::B_TOP_BORDER 1452 | BControlLook::B_BOTTOM_BORDER; 1453 } 1454 be_control_look->DrawMenuBackground(this, rect, updateRect, base, flags, 1455 borders); 1456 } 1457 1458 1459 void 1460 BMenu::SetTrackingHook(menu_tracking_hook func, void* state) 1461 { 1462 fExtraMenuData->trackingHook = func; 1463 fExtraMenuData->trackingState = state; 1464 } 1465 1466 1467 // #pragma mark - Reorder item methods 1468 1469 1470 void 1471 BMenu::SortItems(int (*compare)(const BMenuItem*, const BMenuItem*)) 1472 { 1473 BMenuItem** begin = (BMenuItem**)fItems.Items(); 1474 BMenuItem** end = begin + fItems.CountItems(); 1475 1476 std::stable_sort(begin, end, BPrivate::MenuItemComparator(compare)); 1477 1478 InvalidateLayout(); 1479 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1480 _LayoutItems(0); 1481 Invalidate(); 1482 UnlockLooper(); 1483 } 1484 } 1485 1486 1487 bool 1488 BMenu::SwapItems(int32 indexA, int32 indexB) 1489 { 1490 bool swapped = fItems.SwapItems(indexA, indexB); 1491 if (swapped) { 1492 InvalidateLayout(); 1493 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1494 _LayoutItems(std::min(indexA, indexB)); 1495 Invalidate(); 1496 UnlockLooper(); 1497 } 1498 } 1499 1500 return swapped; 1501 } 1502 1503 1504 bool 1505 BMenu::MoveItem(int32 indexFrom, int32 indexTo) 1506 { 1507 bool moved = fItems.MoveItem(indexFrom, indexTo); 1508 if (moved) { 1509 InvalidateLayout(); 1510 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1511 _LayoutItems(std::min(indexFrom, indexTo)); 1512 Invalidate(); 1513 UnlockLooper(); 1514 } 1515 } 1516 1517 return moved; 1518 } 1519 1520 1521 void BMenu::_ReservedMenu3() {} 1522 void BMenu::_ReservedMenu4() {} 1523 void BMenu::_ReservedMenu5() {} 1524 void BMenu::_ReservedMenu6() {} 1525 1526 1527 void 1528 BMenu::_InitData(BMessage* archive) 1529 { 1530 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>"); 1531 1532 // TODO: Get _color, _fname, _fflt from the message, if present 1533 BFont font; 1534 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1535 font.SetSize(sMenuInfo.font_size); 1536 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1537 1538 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(); 1539 1540 const float labelSpacing = be_control_look->DefaultLabelSpacing(); 1541 fPad = BRect(ceilf(labelSpacing * 2.3f), ceilf(labelSpacing / 3.0f), 1542 ceilf((labelSpacing / 3.0f) * 10.0f), 0.0f); 1543 1544 fLayoutData = new LayoutData; 1545 fLayoutData->lastResizingMode = ResizingMode(); 1546 1547 SetLowUIColor(B_MENU_BACKGROUND_COLOR); 1548 SetViewColor(B_TRANSPARENT_COLOR); 1549 1550 fTriggerEnabled = sMenuInfo.triggers_always_shown; 1551 1552 if (archive != NULL) { 1553 archive->FindInt32("_layout", (int32*)&fLayout); 1554 archive->FindBool("_rsize_to_fit", &fResizeToFit); 1555 bool disabled; 1556 if (archive->FindBool("_disable", &disabled) == B_OK) 1557 fEnabled = !disabled; 1558 archive->FindBool("_radio", &fRadioMode); 1559 1560 bool disableTrigger = false; 1561 archive->FindBool("_trig_disabled", &disableTrigger); 1562 fTriggerEnabled = !disableTrigger; 1563 1564 archive->FindBool("_dyn_label", &fDynamicName); 1565 archive->FindFloat("_maxwidth", &fMaxContentWidth); 1566 1567 BMessage msg; 1568 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) { 1569 BArchivable* object = instantiate_object(&msg); 1570 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) { 1571 BRect bounds; 1572 if (fLayout == B_ITEMS_IN_MATRIX 1573 && archive->FindRect("_i_frames", i, &bounds) == B_OK) 1574 AddItem(item, bounds); 1575 else 1576 AddItem(item); 1577 } 1578 } 1579 } 1580 } 1581 1582 1583 bool 1584 BMenu::_Show(bool selectFirstItem, bool keyDown) 1585 { 1586 if (Window() != NULL) 1587 return false; 1588 1589 // See if the supermenu has a cached menuwindow, 1590 // and use that one if possible. 1591 BMenuWindow* window = NULL; 1592 bool ourWindow = false; 1593 if (fSuper != NULL) { 1594 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1595 window = fSuper->_MenuWindow(); 1596 } 1597 1598 // Otherwise, create a new one 1599 // This happens for "stand alone" BPopUpMenus 1600 // (i.e. not within a BMenuField) 1601 if (window == NULL) { 1602 // Menu windows get the BMenu's handler name 1603 window = new (nothrow) BMenuWindow(Name()); 1604 ourWindow = true; 1605 } 1606 1607 if (window == NULL) 1608 return false; 1609 1610 if (window->Lock()) { 1611 bool addAborted = false; 1612 if (keyDown) 1613 addAborted = _AddDynamicItems(keyDown); 1614 1615 if (addAborted) { 1616 if (ourWindow) 1617 window->Quit(); 1618 else 1619 window->Unlock(); 1620 return false; 1621 } 1622 fAttachAborted = false; 1623 1624 window->AttachMenu(this); 1625 1626 if (ItemAt(0) != NULL) { 1627 float width, height; 1628 ItemAt(0)->GetContentSize(&width, &height); 1629 1630 window->SetSmallStep(ceilf(height)); 1631 } 1632 1633 // Menu didn't have the time to add its items: aborting... 1634 if (fAttachAborted) { 1635 window->DetachMenu(); 1636 // TODO: Probably not needed, we can just let _hide() quit the 1637 // window. 1638 if (ourWindow) 1639 window->Quit(); 1640 else 1641 window->Unlock(); 1642 return false; 1643 } 1644 1645 _UpdateWindowViewSize(true); 1646 window->Show(); 1647 1648 if (selectFirstItem) 1649 _SelectItem(ItemAt(0), false); 1650 1651 window->Unlock(); 1652 } 1653 1654 return true; 1655 } 1656 1657 1658 void 1659 BMenu::_Hide() 1660 { 1661 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 1662 if (window == NULL || !window->Lock()) 1663 return; 1664 1665 if (fSelected != NULL) 1666 _SelectItem(NULL); 1667 1668 window->Hide(); 1669 window->DetachMenu(); 1670 // we don't want to be deleted when the window is removed 1671 1672 #if USE_CACHED_MENUWINDOW 1673 if (fSuper != NULL) 1674 window->Unlock(); 1675 else 1676 #endif 1677 window->Quit(); 1678 // it's our window, quit it 1679 1680 _DeleteMenuWindow(); 1681 // Delete the menu window used by our submenus 1682 } 1683 1684 1685 void BMenu::_ScriptReceived(BMessage* message) 1686 { 1687 BMessage replyMsg(B_REPLY); 1688 status_t err = B_BAD_SCRIPT_SYNTAX; 1689 int32 index; 1690 BMessage specifier; 1691 int32 what; 1692 const char* property; 1693 1694 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property) 1695 != B_OK) { 1696 return BView::MessageReceived(message); 1697 } 1698 1699 BPropertyInfo propertyInfo(sPropList); 1700 switch (propertyInfo.FindMatch(message, index, &specifier, what, 1701 property)) { 1702 case 0: // Enabled: GET 1703 if (message->what == B_GET_PROPERTY) 1704 err = replyMsg.AddBool("result", IsEnabled()); 1705 break; 1706 case 1: // Enabled: SET 1707 if (message->what == B_SET_PROPERTY) { 1708 bool isEnabled; 1709 err = message->FindBool("data", &isEnabled); 1710 if (err >= B_OK) 1711 SetEnabled(isEnabled); 1712 } 1713 break; 1714 case 2: // Label: GET 1715 case 3: // Label: SET 1716 case 4: // Mark: GET 1717 case 5: { // Mark: SET 1718 BMenuItem *item = Superitem(); 1719 if (item != NULL) 1720 return Supermenu()->_ItemScriptReceived(message, item); 1721 1722 break; 1723 } 1724 case 6: // Menu: CREATE 1725 if (message->what == B_CREATE_PROPERTY) { 1726 const char *label; 1727 ObjectDeleter<BMessage> invokeMessage(new BMessage()); 1728 BMessenger target; 1729 ObjectDeleter<BMenuItem> item; 1730 err = message->FindString("data", &label); 1731 if (err >= B_OK) { 1732 invokeMessage.SetTo(new BMessage()); 1733 err = message->FindInt32("what", 1734 (int32*)&invokeMessage->what); 1735 if (err == B_NAME_NOT_FOUND) { 1736 invokeMessage.Unset(); 1737 err = B_OK; 1738 } 1739 } 1740 if (err >= B_OK) { 1741 item.SetTo(new BMenuItem(new BMenu(label), 1742 invokeMessage.Detach())); 1743 } 1744 if (err >= B_OK) { 1745 err = _InsertItemAtSpecifier(specifier, what, item.Get()); 1746 } 1747 if (err >= B_OK) 1748 item.Detach(); 1749 } 1750 break; 1751 case 7: { // Menu: DELETE 1752 if (message->what == B_DELETE_PROPERTY) { 1753 BMenuItem *item = NULL; 1754 int32 index; 1755 err = _ResolveItemSpecifier(specifier, what, item, &index); 1756 if (err >= B_OK) { 1757 if (item->Submenu() == NULL) 1758 err = B_BAD_VALUE; 1759 else { 1760 if (index >= 0) 1761 RemoveItem(index); 1762 else 1763 RemoveItem(item); 1764 } 1765 } 1766 } 1767 break; 1768 } 1769 case 8: { // Menu: * 1770 // TODO: check that submenu looper is running and handle it 1771 // correctly 1772 BMenu *submenu = NULL; 1773 BMenuItem *item; 1774 err = _ResolveItemSpecifier(specifier, what, item); 1775 if (err >= B_OK) 1776 submenu = item->Submenu(); 1777 if (submenu != NULL) { 1778 message->PopSpecifier(); 1779 return submenu->_ScriptReceived(message); 1780 } 1781 break; 1782 } 1783 case 9: // MenuItem: COUNT 1784 if (message->what == B_COUNT_PROPERTIES) 1785 err = replyMsg.AddInt32("result", CountItems()); 1786 break; 1787 case 10: // MenuItem: CREATE 1788 if (message->what == B_CREATE_PROPERTY) { 1789 const char *label; 1790 ObjectDeleter<BMessage> invokeMessage(new BMessage()); 1791 bool targetPresent = true; 1792 BMessenger target; 1793 ObjectDeleter<BMenuItem> item; 1794 err = message->FindString("data", &label); 1795 if (err >= B_OK) { 1796 err = message->FindMessage("be:invoke_message", 1797 invokeMessage.Get()); 1798 if (err == B_NAME_NOT_FOUND) { 1799 err = message->FindInt32("what", 1800 (int32*)&invokeMessage->what); 1801 if (err == B_NAME_NOT_FOUND) { 1802 invokeMessage.Unset(); 1803 err = B_OK; 1804 } 1805 } 1806 } 1807 if (err >= B_OK) { 1808 err = message->FindMessenger("be:target", &target); 1809 if (err == B_NAME_NOT_FOUND) { 1810 targetPresent = false; 1811 err = B_OK; 1812 } 1813 } 1814 if (err >= B_OK) { 1815 item.SetTo(new BMenuItem(label, invokeMessage.Detach())); 1816 if (targetPresent) 1817 err = item->SetTarget(target); 1818 } 1819 if (err >= B_OK) { 1820 err = _InsertItemAtSpecifier(specifier, what, item.Get()); 1821 } 1822 if (err >= B_OK) 1823 item.Detach(); 1824 } 1825 break; 1826 case 11: // MenuItem: DELETE 1827 if (message->what == B_DELETE_PROPERTY) { 1828 BMenuItem *item = NULL; 1829 int32 index; 1830 err = _ResolveItemSpecifier(specifier, what, item, &index); 1831 if (err >= B_OK) { 1832 if (index >= 0) 1833 RemoveItem(index); 1834 else 1835 RemoveItem(item); 1836 } 1837 } 1838 break; 1839 case 12: { // MenuItem: EXECUTE 1840 if (message->what == B_EXECUTE_PROPERTY) { 1841 BMenuItem *item = NULL; 1842 err = _ResolveItemSpecifier(specifier, what, item); 1843 if (err >= B_OK) { 1844 if (!item->IsEnabled()) 1845 err = B_NOT_ALLOWED; 1846 else 1847 err = item->Invoke(); 1848 } 1849 } 1850 break; 1851 } 1852 case 13: { // MenuItem: * 1853 BMenuItem *item = NULL; 1854 err = _ResolveItemSpecifier(specifier, what, item); 1855 if (err >= B_OK) { 1856 message->PopSpecifier(); 1857 return _ItemScriptReceived(message, item); 1858 } 1859 break; 1860 } 1861 default: 1862 return BView::MessageReceived(message); 1863 } 1864 1865 if (err != B_OK) { 1866 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD; 1867 1868 if (err == B_BAD_SCRIPT_SYNTAX) 1869 replyMsg.AddString("message", "Didn't understand the specifier(s)"); 1870 else 1871 replyMsg.AddString("message", strerror(err)); 1872 } 1873 1874 replyMsg.AddInt32("error", err); 1875 message->SendReply(&replyMsg); 1876 } 1877 1878 1879 void BMenu::_ItemScriptReceived(BMessage* message, BMenuItem* item) 1880 { 1881 BMessage replyMsg(B_REPLY); 1882 status_t err = B_BAD_SCRIPT_SYNTAX; 1883 int32 index; 1884 BMessage specifier; 1885 int32 what; 1886 const char* property; 1887 1888 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property) 1889 != B_OK) { 1890 return BView::MessageReceived(message); 1891 } 1892 1893 BPropertyInfo propertyInfo(sPropList); 1894 switch (propertyInfo.FindMatch(message, index, &specifier, what, 1895 property)) { 1896 case 0: // Enabled: GET 1897 if (message->what == B_GET_PROPERTY) 1898 err = replyMsg.AddBool("result", item->IsEnabled()); 1899 break; 1900 case 1: // Enabled: SET 1901 if (message->what == B_SET_PROPERTY) { 1902 bool isEnabled; 1903 err = message->FindBool("data", &isEnabled); 1904 if (err >= B_OK) 1905 item->SetEnabled(isEnabled); 1906 } 1907 break; 1908 case 2: // Label: GET 1909 if (message->what == B_GET_PROPERTY) 1910 err = replyMsg.AddString("result", item->Label()); 1911 break; 1912 case 3: // Label: SET 1913 if (message->what == B_SET_PROPERTY) { 1914 const char *label; 1915 err = message->FindString("data", &label); 1916 if (err >= B_OK) 1917 item->SetLabel(label); 1918 } 1919 case 4: // Mark: GET 1920 if (message->what == B_GET_PROPERTY) 1921 err = replyMsg.AddBool("result", item->IsMarked()); 1922 break; 1923 case 5: // Mark: SET 1924 if (message->what == B_SET_PROPERTY) { 1925 bool isMarked; 1926 err = message->FindBool("data", &isMarked); 1927 if (err >= B_OK) 1928 item->SetMarked(isMarked); 1929 } 1930 break; 1931 case 6: // Menu: CREATE 1932 case 7: // Menu: DELETE 1933 case 8: // Menu: * 1934 case 9: // MenuItem: COUNT 1935 case 10: // MenuItem: CREATE 1936 case 11: // MenuItem: DELETE 1937 case 12: // MenuItem: EXECUTE 1938 case 13: // MenuItem: * 1939 break; 1940 default: 1941 return BView::MessageReceived(message); 1942 } 1943 1944 if (err != B_OK) { 1945 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD; 1946 replyMsg.AddString("message", strerror(err)); 1947 } 1948 1949 replyMsg.AddInt32("error", err); 1950 message->SendReply(&replyMsg); 1951 } 1952 1953 1954 status_t BMenu::_ResolveItemSpecifier(const BMessage& specifier, int32 what, 1955 BMenuItem*& item, int32 *_index) 1956 { 1957 status_t err; 1958 item = NULL; 1959 int32 index = -1; 1960 switch (what) { 1961 case B_INDEX_SPECIFIER: 1962 case B_REVERSE_INDEX_SPECIFIER: { 1963 err = specifier.FindInt32("index", &index); 1964 if (err < B_OK) 1965 return err; 1966 if (what == B_REVERSE_INDEX_SPECIFIER) 1967 index = CountItems() - index; 1968 item = ItemAt(index); 1969 break; 1970 } 1971 case B_NAME_SPECIFIER: { 1972 const char* name; 1973 err = specifier.FindString("name", &name); 1974 if (err < B_OK) 1975 return err; 1976 item = FindItem(name); 1977 break; 1978 } 1979 } 1980 if (item == NULL) 1981 return B_BAD_INDEX; 1982 1983 if (_index != NULL) 1984 *_index = index; 1985 1986 return B_OK; 1987 } 1988 1989 1990 status_t BMenu::_InsertItemAtSpecifier(const BMessage& specifier, int32 what, 1991 BMenuItem* item) 1992 { 1993 status_t err; 1994 switch (what) { 1995 case B_INDEX_SPECIFIER: 1996 case B_REVERSE_INDEX_SPECIFIER: { 1997 int32 index; 1998 err = specifier.FindInt32("index", &index); 1999 if (err < B_OK) return err; 2000 if (what == B_REVERSE_INDEX_SPECIFIER) 2001 index = CountItems() - index; 2002 if (!AddItem(item, index)) 2003 return B_BAD_INDEX; 2004 break; 2005 } 2006 case B_NAME_SPECIFIER: 2007 return B_NOT_SUPPORTED; 2008 break; 2009 } 2010 2011 return B_OK; 2012 } 2013 2014 2015 // #pragma mark - mouse tracking 2016 2017 2018 const static bigtime_t kOpenSubmenuDelay = 0; 2019 const static bigtime_t kNavigationAreaTimeout = 1000000; 2020 2021 2022 BMenuItem* 2023 BMenu::_Track(int* action, long start) 2024 { 2025 // TODO: cleanup 2026 BMenuItem* item = NULL; 2027 BRect navAreaRectAbove; 2028 BRect navAreaRectBelow; 2029 bigtime_t selectedTime = system_time(); 2030 bigtime_t navigationAreaTime = 0; 2031 2032 fState = MENU_STATE_TRACKING; 2033 fChosenItem = NULL; 2034 // we will use this for keyboard selection 2035 2036 BPoint location; 2037 uint32 buttons = 0; 2038 if (LockLooper()) { 2039 GetMouse(&location, &buttons); 2040 UnlockLooper(); 2041 } 2042 2043 bool releasedOnce = buttons == 0; 2044 while (fState != MENU_STATE_CLOSED) { 2045 if (_CustomTrackingWantsToQuit()) 2046 break; 2047 2048 if (!LockLooper()) 2049 break; 2050 2051 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 2052 BPoint screenLocation = ConvertToScreen(location); 2053 if (window->CheckForScrolling(screenLocation)) { 2054 UnlockLooper(); 2055 continue; 2056 } 2057 2058 // The order of the checks is important 2059 // to be able to handle overlapping menus: 2060 // first we check if mouse is inside a submenu, 2061 // then if the mouse is inside this menu, 2062 // then if it's over a super menu. 2063 if (_OverSubmenu(fSelected, screenLocation) 2064 || fState == MENU_STATE_KEY_TO_SUBMENU) { 2065 if (fState == MENU_STATE_TRACKING) { 2066 // not if from R.Arrow 2067 fState = MENU_STATE_TRACKING_SUBMENU; 2068 } 2069 navAreaRectAbove = BRect(); 2070 navAreaRectBelow = BRect(); 2071 2072 // Since the submenu has its own looper, 2073 // we can unlock ours. Doing so also make sure 2074 // that our window gets any update message to 2075 // redraw itself 2076 UnlockLooper(); 2077 2078 // To prevent NULL access violation, ensure a menu has actually 2079 // been selected and that it has a submenu. Because keyboard and 2080 // mouse interactions set selected items differently, the menu 2081 // tracking thread needs to be careful in triggering the navigation 2082 // to the submenu. 2083 if (fSelected != NULL) { 2084 BMenu* submenu = fSelected->Submenu(); 2085 int submenuAction = MENU_STATE_TRACKING; 2086 if (submenu != NULL) { 2087 submenu->_SetStickyMode(_IsStickyMode()); 2088 2089 // The following call blocks until the submenu 2090 // gives control back to us, either because the mouse 2091 // pointer goes out of the submenu's bounds, or because 2092 // the user closes the menu 2093 BMenuItem* submenuItem = submenu->_Track(&submenuAction); 2094 if (submenuAction == MENU_STATE_CLOSED) { 2095 item = submenuItem; 2096 fState = MENU_STATE_CLOSED; 2097 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) { 2098 if (LockLooper()) { 2099 BMenuItem* temp = fSelected; 2100 // close the submenu: 2101 _SelectItem(NULL); 2102 // but reselect the item itself for user: 2103 _SelectItem(temp, false); 2104 UnlockLooper(); 2105 } 2106 // cancel key-nav state 2107 fState = MENU_STATE_TRACKING; 2108 } else 2109 fState = MENU_STATE_TRACKING; 2110 } 2111 } 2112 if (!LockLooper()) 2113 break; 2114 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) { 2115 _UpdateStateOpenSelect(item, location, navAreaRectAbove, 2116 navAreaRectBelow, selectedTime, navigationAreaTime); 2117 releasedOnce = true; 2118 } else if (_OverSuper(screenLocation) 2119 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 2120 fState = MENU_STATE_TRACKING; 2121 UnlockLooper(); 2122 break; 2123 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) { 2124 UnlockLooper(); 2125 break; 2126 } else if (fSuper == NULL 2127 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 2128 // Mouse pointer outside menu: 2129 // If there's no other submenu opened, 2130 // deselect the current selected item 2131 if (fSelected != NULL 2132 && (fSelected->Submenu() == NULL 2133 || fSelected->Submenu()->Window() == NULL)) { 2134 _SelectItem(NULL); 2135 fState = MENU_STATE_TRACKING; 2136 } 2137 2138 if (fSuper != NULL) { 2139 // Give supermenu the chance to continue tracking 2140 *action = fState; 2141 UnlockLooper(); 2142 return NULL; 2143 } 2144 } 2145 2146 UnlockLooper(); 2147 2148 if (releasedOnce) 2149 _UpdateStateClose(item, location, buttons); 2150 2151 if (fState != MENU_STATE_CLOSED) { 2152 bigtime_t snoozeAmount = 50000; 2153 2154 BPoint newLocation = location; 2155 uint32 newButtons = buttons; 2156 2157 // If user doesn't move the mouse, loop here, 2158 // so we don't interfere with keyboard menu navigation 2159 do { 2160 snooze(snoozeAmount); 2161 if (!LockLooper()) 2162 break; 2163 GetMouse(&newLocation, &newButtons, true); 2164 UnlockLooper(); 2165 } while (newLocation == location && newButtons == buttons 2166 && !(item != NULL && item->Submenu() != NULL 2167 && item->Submenu()->Window() == NULL) 2168 && fState == MENU_STATE_TRACKING); 2169 2170 if (newLocation != location || newButtons != buttons) { 2171 if (!releasedOnce && newButtons == 0 && buttons != 0) 2172 releasedOnce = true; 2173 location = newLocation; 2174 buttons = newButtons; 2175 } 2176 2177 if (releasedOnce) 2178 _UpdateStateClose(item, location, buttons); 2179 } 2180 } 2181 2182 if (action != NULL) 2183 *action = fState; 2184 2185 // keyboard Enter will set this 2186 if (fChosenItem != NULL) 2187 item = fChosenItem; 2188 else if (fSelected == NULL) { 2189 // needed to cover (rare) mouse/ESC combination 2190 item = NULL; 2191 } 2192 2193 if (fSelected != NULL && LockLooper()) { 2194 _SelectItem(NULL); 2195 UnlockLooper(); 2196 } 2197 2198 // delete the menu window recycled for all the child menus 2199 _DeleteMenuWindow(); 2200 2201 return item; 2202 } 2203 2204 2205 void 2206 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove, 2207 BRect& navAreaRectBelow) 2208 { 2209 #define NAV_AREA_THRESHOLD 8 2210 2211 // The navigation area is a region in which mouse-overs won't select 2212 // the item under the cursor. This makes it easier to navigate to 2213 // submenus, as the cursor can be moved to submenu items directly instead 2214 // of having to move it horizontally into the submenu first. The concept 2215 // is illustrated below: 2216 // 2217 // +-------+----+---------+ 2218 // | | /| | 2219 // | | /*| | 2220 // |[2]--> | /**| | 2221 // | |/[4]| | 2222 // |------------| | 2223 // | [1] | [6] | 2224 // |------------| | 2225 // | |\[5]| | 2226 // |[3]--> | \**| | 2227 // | | \*| | 2228 // | | \| | 2229 // | +----|---------+ 2230 // | | 2231 // +------------+ 2232 // 2233 // [1] Selected item, cursor position ('position') 2234 // [2] Upper navigation area rectangle ('navAreaRectAbove') 2235 // [3] Lower navigation area rectangle ('navAreaRectBelow') 2236 // [4] Upper navigation area 2237 // [5] Lower navigation area 2238 // [6] Submenu 2239 // 2240 // The rectangles are used to calculate if the cursor is in the actual 2241 // navigation area (see _UpdateStateOpenSelect()). 2242 2243 if (fSelected == NULL) 2244 return; 2245 2246 BMenu* submenu = fSelected->Submenu(); 2247 2248 if (submenu != NULL) { 2249 BRect menuBounds = ConvertToScreen(Bounds()); 2250 2251 BRect submenuBounds; 2252 if (fSelected->Submenu()->LockLooper()) { 2253 submenuBounds = fSelected->Submenu()->ConvertToScreen( 2254 fSelected->Submenu()->Bounds()); 2255 fSelected->Submenu()->UnlockLooper(); 2256 } 2257 2258 if (menuBounds.left < submenuBounds.left) { 2259 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD, 2260 submenuBounds.top, menuBounds.right, 2261 position.y); 2262 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD, 2263 position.y, menuBounds.right, 2264 submenuBounds.bottom); 2265 } else { 2266 navAreaRectAbove.Set(menuBounds.left, 2267 submenuBounds.top, position.x - NAV_AREA_THRESHOLD, 2268 position.y); 2269 navAreaRectBelow.Set(menuBounds.left, 2270 position.y, position.x - NAV_AREA_THRESHOLD, 2271 submenuBounds.bottom); 2272 } 2273 } else { 2274 navAreaRectAbove = BRect(); 2275 navAreaRectBelow = BRect(); 2276 } 2277 } 2278 2279 2280 void 2281 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position, 2282 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime, 2283 bigtime_t& navigationAreaTime) 2284 { 2285 if (fState == MENU_STATE_CLOSED) 2286 return; 2287 2288 if (item != fSelected) { 2289 if (navigationAreaTime == 0) 2290 navigationAreaTime = system_time(); 2291 2292 position = ConvertToScreen(position); 2293 2294 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position); 2295 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position); 2296 2297 if (fSelected == NULL 2298 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) { 2299 _SelectItem(item, false); 2300 navAreaRectAbove = BRect(); 2301 navAreaRectBelow = BRect(); 2302 selectedTime = system_time(); 2303 navigationAreaTime = 0; 2304 return; 2305 } 2306 2307 bool isLeft = ConvertFromScreen(navAreaRectAbove).left == 0; 2308 BPoint p1, p2; 2309 2310 if (inNavAreaRectAbove) { 2311 if (!isLeft) { 2312 p1 = navAreaRectAbove.LeftBottom(); 2313 p2 = navAreaRectAbove.RightTop(); 2314 } else { 2315 p2 = navAreaRectAbove.RightBottom(); 2316 p1 = navAreaRectAbove.LeftTop(); 2317 } 2318 } else { 2319 if (!isLeft) { 2320 p2 = navAreaRectBelow.LeftTop(); 2321 p1 = navAreaRectBelow.RightBottom(); 2322 } else { 2323 p1 = navAreaRectBelow.RightTop(); 2324 p2 = navAreaRectBelow.LeftBottom(); 2325 } 2326 } 2327 bool inNavArea = 2328 (p1.y - p2.y) * position.x + (p2.x - p1.x) * position.y 2329 + (p1.x - p2.x) * p1.y + (p2.y - p1.y) * p1.x >= 0; 2330 2331 bigtime_t systime = system_time(); 2332 2333 if (!inNavArea || (navigationAreaTime > 0 && systime - 2334 navigationAreaTime > kNavigationAreaTimeout)) { 2335 // Don't delay opening of submenu if the user had 2336 // to wait for the navigation area timeout anyway 2337 _SelectItem(item, inNavArea); 2338 2339 if (inNavArea) { 2340 _UpdateNavigationArea(position, navAreaRectAbove, 2341 navAreaRectBelow); 2342 } else { 2343 navAreaRectAbove = BRect(); 2344 navAreaRectBelow = BRect(); 2345 } 2346 2347 selectedTime = system_time(); 2348 navigationAreaTime = 0; 2349 } 2350 } else if (fSelected->Submenu() != NULL && 2351 system_time() - selectedTime > kOpenSubmenuDelay) { 2352 _SelectItem(fSelected, true); 2353 2354 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) { 2355 position = ConvertToScreen(position); 2356 _UpdateNavigationArea(position, navAreaRectAbove, 2357 navAreaRectBelow); 2358 } 2359 } 2360 2361 if (fState != MENU_STATE_TRACKING) 2362 fState = MENU_STATE_TRACKING; 2363 } 2364 2365 2366 void 2367 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where, 2368 const uint32& buttons) 2369 { 2370 if (fState == MENU_STATE_CLOSED) 2371 return; 2372 2373 if (buttons != 0 && _IsStickyMode()) { 2374 if (item == NULL) { 2375 if (item != fSelected && LockLooper()) { 2376 _SelectItem(item, false); 2377 UnlockLooper(); 2378 } 2379 fState = MENU_STATE_CLOSED; 2380 } else 2381 _SetStickyMode(false); 2382 } else if (buttons == 0 && !_IsStickyMode()) { 2383 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 2384 _SetStickyMode(true); 2385 fExtraRect = NULL; 2386 // Setting this to NULL will prevent this code 2387 // to be executed next time 2388 } else { 2389 if (item != fSelected && LockLooper()) { 2390 _SelectItem(item, false); 2391 UnlockLooper(); 2392 } 2393 fState = MENU_STATE_CLOSED; 2394 } 2395 } 2396 } 2397 2398 2399 bool 2400 BMenu::_AddItem(BMenuItem* item, int32 index) 2401 { 2402 ASSERT(item != NULL); 2403 if (index < 0 || index > fItems.CountItems()) 2404 return false; 2405 2406 if (item->IsMarked()) 2407 _ItemMarked(item); 2408 2409 if (!fItems.AddItem(item, index)) 2410 return false; 2411 2412 // install the item on the supermenu's window 2413 // or onto our window, if we are a root menu 2414 BWindow* window = NULL; 2415 if (Superitem() != NULL) 2416 window = Superitem()->fWindow; 2417 else 2418 window = Window(); 2419 if (window != NULL) 2420 item->Install(window); 2421 2422 item->SetSuper(this); 2423 return true; 2424 } 2425 2426 2427 bool 2428 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item, 2429 bool deleteItems) 2430 { 2431 bool success = false; 2432 bool invalidateLayout = false; 2433 2434 bool locked = LockLooper(); 2435 BWindow* window = Window(); 2436 2437 // The plan is simple: If we're given a BMenuItem directly, we use it 2438 // and ignore index and count. Otherwise, we use them instead. 2439 if (item != NULL) { 2440 if (fItems.RemoveItem(item)) { 2441 if (item == fSelected && window != NULL) 2442 _SelectItem(NULL); 2443 item->Uninstall(); 2444 item->SetSuper(NULL); 2445 if (deleteItems) 2446 delete item; 2447 success = invalidateLayout = true; 2448 } 2449 } else { 2450 // We iterate backwards because it's simpler 2451 int32 i = std::min(index + count - 1, fItems.CountItems() - 1); 2452 // NOTE: the range check for "index" is done after 2453 // calculating the last index to be removed, so 2454 // that the range is not "shifted" unintentionally 2455 index = std::max((int32)0, index); 2456 for (; i >= index; i--) { 2457 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 2458 if (item != NULL) { 2459 if (fItems.RemoveItem(i)) { 2460 if (item == fSelected && window != NULL) 2461 _SelectItem(NULL); 2462 item->Uninstall(); 2463 item->SetSuper(NULL); 2464 if (deleteItems) 2465 delete item; 2466 success = true; 2467 invalidateLayout = true; 2468 } else { 2469 // operation not entirely successful 2470 success = false; 2471 break; 2472 } 2473 } 2474 } 2475 } 2476 2477 if (invalidateLayout) { 2478 InvalidateLayout(); 2479 if (locked && window != NULL) { 2480 _LayoutItems(0); 2481 _UpdateWindowViewSize(false); 2482 Invalidate(); 2483 } 2484 } 2485 2486 if (locked) 2487 UnlockLooper(); 2488 2489 return success; 2490 } 2491 2492 2493 bool 2494 BMenu::_RelayoutIfNeeded() 2495 { 2496 if (!fUseCachedMenuLayout) { 2497 fUseCachedMenuLayout = true; 2498 _CacheFontInfo(); 2499 _LayoutItems(0); 2500 _UpdateWindowViewSize(false); 2501 return true; 2502 } 2503 return false; 2504 } 2505 2506 2507 void 2508 BMenu::_LayoutItems(int32 index) 2509 { 2510 _CalcTriggers(); 2511 2512 float width; 2513 float height; 2514 _ComputeLayout(index, fResizeToFit, true, &width, &height); 2515 2516 if (fResizeToFit) 2517 ResizeTo(width, height); 2518 } 2519 2520 2521 BSize 2522 BMenu::_ValidatePreferredSize() 2523 { 2524 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode() 2525 != fLayoutData->lastResizingMode) { 2526 _ComputeLayout(0, true, false, NULL, NULL); 2527 ResetLayoutInvalidation(); 2528 } 2529 2530 return fLayoutData->preferred; 2531 } 2532 2533 2534 void 2535 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 2536 float* _width, float* _height) 2537 { 2538 // TODO: Take "bestFit", "moveItems", "index" into account, 2539 // Recalculate only the needed items, 2540 // not the whole layout every time 2541 2542 fLayoutData->lastResizingMode = ResizingMode(); 2543 2544 BRect frame; 2545 switch (fLayout) { 2546 case B_ITEMS_IN_COLUMN: 2547 { 2548 BRect parentFrame; 2549 BRect* overrideFrame = NULL; 2550 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) { 2551 // When the menu is modified while it's open, we get here in a 2552 // situation where trying to lock the looper would deadlock 2553 // (the window is locked waiting for the menu to terminate). 2554 // In that case, just give up on getting the supermenu bounds 2555 // and keep the menu at the current width and position. 2556 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) { 2557 parentFrame = Supermenu()->Bounds(); 2558 Supermenu()->UnlockLooper(); 2559 overrideFrame = &parentFrame; 2560 } 2561 } 2562 2563 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, 2564 frame); 2565 break; 2566 } 2567 2568 case B_ITEMS_IN_ROW: 2569 _ComputeRowLayout(index, bestFit, moveItems, frame); 2570 break; 2571 2572 case B_ITEMS_IN_MATRIX: 2573 _ComputeMatrixLayout(frame); 2574 break; 2575 } 2576 2577 // change width depending on resize mode 2578 BSize size; 2579 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2580 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL) 2581 size.width = Bounds().Width() - fPad.right; 2582 else if (Parent() != NULL) 2583 size.width = Parent()->Frame().Width(); 2584 else if (Window() != NULL) 2585 size.width = Window()->Frame().Width(); 2586 else 2587 size.width = Bounds().Width(); 2588 } else 2589 size.width = frame.Width(); 2590 2591 size.height = frame.Height(); 2592 2593 if (_width) 2594 *_width = size.width; 2595 2596 if (_height) 2597 *_height = size.height; 2598 2599 if (bestFit) 2600 fLayoutData->preferred = size; 2601 2602 if (moveItems) 2603 fUseCachedMenuLayout = true; 2604 } 2605 2606 2607 void 2608 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2609 BRect* overrideFrame, BRect& frame) 2610 { 2611 bool command = false; 2612 bool control = false; 2613 bool shift = false; 2614 bool option = false; 2615 bool submenu = false; 2616 2617 if (index > 0) 2618 frame = ItemAt(index - 1)->Frame(); 2619 else if (overrideFrame != NULL) 2620 frame.Set(0, 0, overrideFrame->right, -1); 2621 else 2622 frame.Set(0, 0, 0, -1); 2623 2624 BFont font; 2625 GetFont(&font); 2626 2627 // Loop over all items to set their top, bottom and left coordinates, 2628 // all while computing the width of the menu 2629 for (; index < fItems.CountItems(); index++) { 2630 BMenuItem* item = ItemAt(index); 2631 2632 float width; 2633 float height; 2634 item->GetContentSize(&width, &height); 2635 2636 if (item->fModifiers && item->fShortcutChar) { 2637 width += font.Size(); 2638 if ((item->fModifiers & B_COMMAND_KEY) != 0) 2639 command = true; 2640 2641 if ((item->fModifiers & B_CONTROL_KEY) != 0) 2642 control = true; 2643 2644 if ((item->fModifiers & B_SHIFT_KEY) != 0) 2645 shift = true; 2646 2647 if ((item->fModifiers & B_OPTION_KEY) != 0) 2648 option = true; 2649 } 2650 2651 item->fBounds.left = 0.0f; 2652 item->fBounds.top = frame.bottom + 1.0f; 2653 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2654 + fPad.bottom; 2655 2656 if (item->fSubmenu != NULL) 2657 submenu = true; 2658 2659 frame.right = std::max(frame.right, width + fPad.left + fPad.right); 2660 frame.bottom = item->fBounds.bottom; 2661 } 2662 2663 // Compute the extra space needed for shortcuts and submenus 2664 if (command) { 2665 frame.right 2666 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2667 } 2668 if (control) { 2669 frame.right 2670 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2671 } 2672 if (option) { 2673 frame.right 2674 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2675 } 2676 if (shift) { 2677 frame.right 2678 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2679 } 2680 if (submenu) { 2681 frame.right += ItemAt(0)->Frame().Height() / 2; 2682 fHasSubmenus = true; 2683 } else { 2684 fHasSubmenus = false; 2685 } 2686 2687 if (fMaxContentWidth > 0) 2688 frame.right = std::min(frame.right, fMaxContentWidth); 2689 2690 frame.top = 0; 2691 frame.right = ceilf(frame.right); 2692 2693 // Finally update the "right" coordinate of all items 2694 if (moveItems) { 2695 for (int32 i = 0; i < fItems.CountItems(); i++) 2696 ItemAt(i)->fBounds.right = frame.right; 2697 } 2698 } 2699 2700 2701 void 2702 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2703 BRect& frame) 2704 { 2705 font_height fh; 2706 GetFontHeight(&fh); 2707 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2708 + fPad.bottom)); 2709 2710 for (int32 i = 0; i < fItems.CountItems(); i++) { 2711 BMenuItem* item = ItemAt(i); 2712 2713 float width, height; 2714 item->GetContentSize(&width, &height); 2715 2716 item->fBounds.left = frame.right; 2717 item->fBounds.top = 0.0f; 2718 item->fBounds.right = item->fBounds.left + width + fPad.left 2719 + fPad.right; 2720 2721 frame.right = item->Frame().right + 1.0f; 2722 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom); 2723 } 2724 2725 if (moveItems) { 2726 for (int32 i = 0; i < fItems.CountItems(); i++) 2727 ItemAt(i)->fBounds.bottom = frame.bottom; 2728 } 2729 2730 if (bestFit) 2731 frame.right = ceilf(frame.right); 2732 else 2733 frame.right = Bounds().right; 2734 } 2735 2736 2737 void 2738 BMenu::_ComputeMatrixLayout(BRect &frame) 2739 { 2740 frame.Set(0, 0, 0, 0); 2741 for (int32 i = 0; i < CountItems(); i++) { 2742 BMenuItem* item = ItemAt(i); 2743 if (item != NULL) { 2744 frame.left = std::min(frame.left, item->Frame().left); 2745 frame.right = std::max(frame.right, item->Frame().right); 2746 frame.top = std::min(frame.top, item->Frame().top); 2747 frame.bottom = std::max(frame.bottom, item->Frame().bottom); 2748 } 2749 } 2750 } 2751 2752 2753 void 2754 BMenu::LayoutInvalidated(bool descendants) 2755 { 2756 fUseCachedMenuLayout = false; 2757 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2758 } 2759 2760 2761 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2762 BPoint 2763 BMenu::ScreenLocation() 2764 { 2765 BMenu* superMenu = Supermenu(); 2766 BMenuItem* superItem = Superitem(); 2767 2768 if (superMenu == NULL || superItem == NULL) { 2769 debugger("BMenu can't determine where to draw." 2770 "Override BMenu::ScreenLocation() to determine location."); 2771 } 2772 2773 BPoint point; 2774 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2775 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2776 else 2777 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2778 2779 superMenu->ConvertToScreen(&point); 2780 2781 return point; 2782 } 2783 2784 2785 BRect 2786 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2787 { 2788 // TODO: Improve me 2789 BRect bounds = Bounds(); 2790 BRect frame = bounds.OffsetToCopy(where); 2791 2792 BScreen screen(Window()); 2793 BRect screenFrame = screen.Frame(); 2794 2795 BMenu* superMenu = Supermenu(); 2796 BMenuItem* superItem = Superitem(); 2797 2798 // Reset frame shifted state since this menu is being redrawn 2799 fExtraMenuData->frameShiftedLeft = false; 2800 2801 // TODO: Horrible hack: 2802 // When added to a BMenuField, a BPopUpMenu is the child of 2803 // a _BMCMenuBar_ to "fake" the menu hierarchy 2804 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2805 2806 // Offset the menu field menu window left by the width of the checkmark 2807 // so that the text when the menu is closed lines up with the text when 2808 // the menu is open. 2809 if (inMenuField) 2810 frame.OffsetBy(-8.0f, 0.0f); 2811 2812 if (superMenu == NULL || superItem == NULL || inMenuField) { 2813 // just move the window on screen 2814 if (frame.bottom > screenFrame.bottom) 2815 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2816 else if (frame.top < screenFrame.top) 2817 frame.OffsetBy(0, -frame.top); 2818 2819 if (frame.right > screenFrame.right) { 2820 frame.OffsetBy(screenFrame.right - frame.right, 0); 2821 fExtraMenuData->frameShiftedLeft = true; 2822 } 2823 else if (frame.left < screenFrame.left) 2824 frame.OffsetBy(-frame.left, 0); 2825 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2826 if (frame.right > screenFrame.right 2827 || superMenu->fExtraMenuData->frameShiftedLeft) { 2828 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2829 fExtraMenuData->frameShiftedLeft = true; 2830 } 2831 2832 if (frame.left < 0) 2833 frame.OffsetBy(-frame.left + 6, 0); 2834 2835 if (frame.bottom > screenFrame.bottom) 2836 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2837 } else { 2838 if (frame.bottom > screenFrame.bottom) { 2839 float spaceBelow = screenFrame.bottom - frame.top; 2840 float spaceOver = frame.top - screenFrame.top 2841 - superItem->Frame().Height(); 2842 if (spaceOver > spaceBelow) { 2843 frame.OffsetBy(0, -superItem->Frame().Height() 2844 - frame.Height() - 3); 2845 } 2846 } 2847 2848 if (frame.right > screenFrame.right) 2849 frame.OffsetBy(screenFrame.right - frame.right, 0); 2850 } 2851 2852 if (scrollOn != NULL) { 2853 // basically, if this returns false, it means 2854 // that the menu frame won't fit completely inside the screen 2855 // TODO: Scrolling will currently only work up/down, 2856 // not left/right 2857 *scrollOn = screenFrame.top > frame.top 2858 || screenFrame.bottom < frame.bottom; 2859 } 2860 2861 return frame; 2862 } 2863 2864 2865 void 2866 BMenu::DrawItems(BRect updateRect) 2867 { 2868 int32 itemCount = fItems.CountItems(); 2869 for (int32 i = 0; i < itemCount; i++) { 2870 BMenuItem* item = ItemAt(i); 2871 if (item->Frame().Intersects(updateRect)) 2872 item->Draw(); 2873 } 2874 } 2875 2876 2877 int 2878 BMenu::_State(BMenuItem** item) const 2879 { 2880 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2881 return fState; 2882 2883 if (fSelected != NULL && fSelected->Submenu() != NULL) 2884 return fSelected->Submenu()->_State(item); 2885 2886 return fState; 2887 } 2888 2889 2890 void 2891 BMenu::_InvokeItem(BMenuItem* item, bool now) 2892 { 2893 if (!item->IsEnabled()) 2894 return; 2895 2896 // Do the "selected" animation 2897 // TODO: Doesn't work. This is supposed to highlight 2898 // and dehighlight the item, works on beos but not on haiku. 2899 if (!item->Submenu() && LockLooper()) { 2900 snooze(50000); 2901 item->Select(true); 2902 Window()->UpdateIfNeeded(); 2903 snooze(50000); 2904 item->Select(false); 2905 Window()->UpdateIfNeeded(); 2906 snooze(50000); 2907 item->Select(true); 2908 Window()->UpdateIfNeeded(); 2909 snooze(50000); 2910 item->Select(false); 2911 Window()->UpdateIfNeeded(); 2912 UnlockLooper(); 2913 } 2914 2915 // Lock the root menu window before calling BMenuItem::Invoke() 2916 BMenu* parent = this; 2917 BMenu* rootMenu = NULL; 2918 do { 2919 rootMenu = parent; 2920 parent = rootMenu->Supermenu(); 2921 } while (parent != NULL); 2922 2923 if (rootMenu->LockLooper()) { 2924 item->Invoke(); 2925 rootMenu->UnlockLooper(); 2926 } 2927 } 2928 2929 2930 bool 2931 BMenu::_OverSuper(BPoint location) 2932 { 2933 if (!Supermenu()) 2934 return false; 2935 2936 return fSuperbounds.Contains(location); 2937 } 2938 2939 2940 bool 2941 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2942 { 2943 if (item == NULL) 2944 return false; 2945 2946 BMenu* subMenu = item->Submenu(); 2947 if (subMenu == NULL || subMenu->Window() == NULL) 2948 return false; 2949 2950 // assume that loc is in screen coordinates 2951 if (subMenu->Window()->Frame().Contains(loc)) 2952 return true; 2953 2954 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2955 } 2956 2957 2958 BMenuWindow* 2959 BMenu::_MenuWindow() 2960 { 2961 #if USE_CACHED_MENUWINDOW 2962 if (fCachedMenuWindow == NULL) { 2963 char windowName[64]; 2964 snprintf(windowName, 64, "%s cached menu", Name()); 2965 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2966 } 2967 #endif 2968 return fCachedMenuWindow; 2969 } 2970 2971 2972 void 2973 BMenu::_DeleteMenuWindow() 2974 { 2975 if (fCachedMenuWindow != NULL) { 2976 fCachedMenuWindow->Lock(); 2977 fCachedMenuWindow->Quit(); 2978 fCachedMenuWindow = NULL; 2979 } 2980 } 2981 2982 2983 BMenuItem* 2984 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2985 { 2986 // TODO: Take "slop" into account ? 2987 2988 // if the point doesn't lie within the menu's 2989 // bounds, bail out immediately 2990 if (!Bounds().Contains(where)) 2991 return NULL; 2992 2993 int32 itemCount = CountItems(); 2994 for (int32 i = 0; i < itemCount; i++) { 2995 BMenuItem* item = ItemAt(i); 2996 if (item->Frame().Contains(where) 2997 && dynamic_cast<BSeparatorItem*>(item) == NULL) { 2998 return item; 2999 } 3000 } 3001 3002 return NULL; 3003 } 3004 3005 3006 BRect 3007 BMenu::_Superbounds() const 3008 { 3009 return fSuperbounds; 3010 } 3011 3012 3013 void 3014 BMenu::_CacheFontInfo() 3015 { 3016 font_height fh; 3017 GetFontHeight(&fh); 3018 fAscent = fh.ascent; 3019 fDescent = fh.descent; 3020 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 3021 } 3022 3023 3024 void 3025 BMenu::_ItemMarked(BMenuItem* item) 3026 { 3027 if (IsRadioMode()) { 3028 for (int32 i = 0; i < CountItems(); i++) { 3029 if (ItemAt(i) != item) 3030 ItemAt(i)->SetMarked(false); 3031 } 3032 } 3033 3034 if (IsLabelFromMarked() && Superitem() != NULL) 3035 Superitem()->SetLabel(item->Label()); 3036 } 3037 3038 3039 void 3040 BMenu::_Install(BWindow* target) 3041 { 3042 for (int32 i = 0; i < CountItems(); i++) 3043 ItemAt(i)->Install(target); 3044 } 3045 3046 3047 void 3048 BMenu::_Uninstall() 3049 { 3050 for (int32 i = 0; i < CountItems(); i++) 3051 ItemAt(i)->Uninstall(); 3052 } 3053 3054 3055 void 3056 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem, 3057 bool keyDown) 3058 { 3059 // Avoid deselecting and then reselecting the same item 3060 // which would cause flickering 3061 if (item != fSelected) { 3062 if (fSelected != NULL) { 3063 fSelected->Select(false); 3064 BMenu* subMenu = fSelected->Submenu(); 3065 if (subMenu != NULL && subMenu->Window() != NULL) 3066 subMenu->_Hide(); 3067 } 3068 3069 fSelected = item; 3070 if (fSelected != NULL) 3071 fSelected->Select(true); 3072 } 3073 3074 if (fSelected != NULL && showSubmenu) { 3075 BMenu* subMenu = fSelected->Submenu(); 3076 if (subMenu != NULL && subMenu->Window() == NULL) { 3077 if (!subMenu->_Show(selectFirstItem, keyDown)) { 3078 // something went wrong, deselect the item 3079 fSelected->Select(false); 3080 fSelected = NULL; 3081 } 3082 } 3083 } 3084 } 3085 3086 3087 bool 3088 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 3089 { 3090 if (CountItems() == 0) // cannot select next item in an empty menu 3091 return false; 3092 3093 BMenuItem* nextItem = _NextItem(item, forward); 3094 if (nextItem == NULL) 3095 return false; 3096 3097 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL); 3098 3099 if (LockLooper()) { 3100 be_app->ObscureCursor(); 3101 UnlockLooper(); 3102 } 3103 3104 return true; 3105 } 3106 3107 3108 BMenuItem* 3109 BMenu::_NextItem(BMenuItem* item, bool forward) const 3110 { 3111 const int32 numItems = fItems.CountItems(); 3112 if (numItems == 0) 3113 return NULL; 3114 3115 int32 index = fItems.IndexOf(item); 3116 int32 loopCount = numItems; 3117 while (--loopCount) { 3118 // Cycle through menu items in the given direction... 3119 if (forward) 3120 index++; 3121 else 3122 index--; 3123 3124 // ... wrap around... 3125 if (index < 0) 3126 index = numItems - 1; 3127 else if (index >= numItems) 3128 index = 0; 3129 3130 // ... and return the first suitable item found. 3131 BMenuItem* nextItem = ItemAt(index); 3132 if (nextItem->IsEnabled()) 3133 return nextItem; 3134 } 3135 3136 // If no other suitable item was found, return NULL. 3137 return NULL; 3138 } 3139 3140 3141 void 3142 BMenu::_SetStickyMode(bool sticky) 3143 { 3144 if (fStickyMode == sticky) 3145 return; 3146 3147 fStickyMode = sticky; 3148 3149 if (fSuper != NULL) { 3150 // propagate the status to the super menu 3151 fSuper->_SetStickyMode(sticky); 3152 } else { 3153 // TODO: Ugly hack, but it needs to be done in this method 3154 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 3155 if (sticky && menuBar != NULL && menuBar->LockLooper()) { 3156 // If we are switching to sticky mode, 3157 // steal the focus from the current focus view 3158 // (needed to handle keyboard navigation) 3159 menuBar->_StealFocus(); 3160 menuBar->UnlockLooper(); 3161 } 3162 } 3163 } 3164 3165 3166 bool 3167 BMenu::_IsStickyMode() const 3168 { 3169 return fStickyMode; 3170 } 3171 3172 3173 void 3174 BMenu::_GetShiftKey(uint32 &value) const 3175 { 3176 // TODO: Move into init_interface_kit(). 3177 // Currently we can't do that, as get_modifier_key() blocks forever 3178 // when called on input_server initialization, since it tries 3179 // to send a synchronous message to itself (input_server is 3180 // a BApplication) 3181 3182 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK) 3183 value = 0x4b; 3184 } 3185 3186 3187 void 3188 BMenu::_GetControlKey(uint32 &value) const 3189 { 3190 // TODO: Move into init_interface_kit(). 3191 // Currently we can't do that, as get_modifier_key() blocks forever 3192 // when called on input_server initialization, since it tries 3193 // to send a synchronous message to itself (input_server is 3194 // a BApplication) 3195 3196 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK) 3197 value = 0x5c; 3198 } 3199 3200 3201 void 3202 BMenu::_GetCommandKey(uint32 &value) const 3203 { 3204 // TODO: Move into init_interface_kit(). 3205 // Currently we can't do that, as get_modifier_key() blocks forever 3206 // when called on input_server initialization, since it tries 3207 // to send a synchronous message to itself (input_server is 3208 // a BApplication) 3209 3210 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK) 3211 value = 0x66; 3212 } 3213 3214 3215 void 3216 BMenu::_GetOptionKey(uint32 &value) const 3217 { 3218 // TODO: Move into init_interface_kit(). 3219 // Currently we can't do that, as get_modifier_key() blocks forever 3220 // when called on input_server initialization, since it tries 3221 // to send a synchronous message to itself (input_server is 3222 // a BApplication) 3223 3224 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 3225 value = 0x5d; 3226 } 3227 3228 3229 void 3230 BMenu::_GetMenuKey(uint32 &value) const 3231 { 3232 // TODO: Move into init_interface_kit(). 3233 // Currently we can't do that, as get_modifier_key() blocks forever 3234 // when called on input_server initialization, since it tries 3235 // to send a synchronous message to itself (input_server is 3236 // a BApplication) 3237 3238 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 3239 value = 0x68; 3240 } 3241 3242 3243 void 3244 BMenu::_CalcTriggers() 3245 { 3246 BPrivate::TriggerList triggerList; 3247 3248 // Gathers the existing triggers set by the user 3249 for (int32 i = 0; i < CountItems(); i++) { 3250 char trigger = ItemAt(i)->Trigger(); 3251 if (trigger != 0) 3252 triggerList.AddTrigger(trigger); 3253 } 3254 3255 // Set triggers for items which don't have one yet 3256 for (int32 i = 0; i < CountItems(); i++) { 3257 BMenuItem* item = ItemAt(i); 3258 if (item->Trigger() == 0) { 3259 uint32 trigger; 3260 int32 index; 3261 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 3262 item->SetAutomaticTrigger(index, trigger); 3263 } 3264 } 3265 } 3266 3267 3268 bool 3269 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 3270 BPrivate::TriggerList& triggers) 3271 { 3272 if (title == NULL) 3273 return false; 3274 3275 index = 0; 3276 uint32 c; 3277 const char* nextCharacter, *character; 3278 3279 // two runs: first we look out for alphanumeric ASCII characters 3280 nextCharacter = title; 3281 character = nextCharacter; 3282 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3283 if (!(c < 128 && BUnicodeChar::IsAlNum(c)) || triggers.HasTrigger(c)) { 3284 character = nextCharacter; 3285 continue; 3286 } 3287 trigger = BUnicodeChar::ToLower(c); 3288 index = (int32)(character - title); 3289 return triggers.AddTrigger(c); 3290 } 3291 3292 // then, if we still haven't found something, we accept anything 3293 nextCharacter = title; 3294 character = nextCharacter; 3295 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3296 if (BUnicodeChar::IsSpace(c) || triggers.HasTrigger(c)) { 3297 character = nextCharacter; 3298 continue; 3299 } 3300 trigger = BUnicodeChar::ToLower(c); 3301 index = (int32)(character - title); 3302 return triggers.AddTrigger(c); 3303 } 3304 3305 return false; 3306 } 3307 3308 3309 void 3310 BMenu::_UpdateWindowViewSize(const bool &move) 3311 { 3312 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 3313 if (window == NULL) 3314 return; 3315 3316 if (dynamic_cast<BMenuBar*>(this) != NULL) 3317 return; 3318 3319 if (!fResizeToFit) 3320 return; 3321 3322 bool scroll = false; 3323 const BPoint screenLocation = move ? ScreenLocation() 3324 : window->Frame().LeftTop(); 3325 BRect frame = _CalcFrame(screenLocation, &scroll); 3326 ResizeTo(frame.Width(), frame.Height()); 3327 3328 if (fItems.CountItems() > 0) { 3329 if (!scroll) { 3330 if (fLayout == B_ITEMS_IN_COLUMN) 3331 window->DetachScrollers(); 3332 3333 window->ResizeTo(Bounds().Width(), Bounds().Height()); 3334 } else { 3335 3336 // Resize the window to fit the screen without overflowing the 3337 // frame, and attach scrollers to our cached BMenuWindow. 3338 BScreen screen(window); 3339 frame = frame & screen.Frame(); 3340 window->ResizeTo(Bounds().Width(), frame.Height()); 3341 3342 // we currently only support scrolling for B_ITEMS_IN_COLUMN 3343 if (fLayout == B_ITEMS_IN_COLUMN) { 3344 window->AttachScrollers(); 3345 3346 BMenuItem* selectedItem = FindMarked(); 3347 if (selectedItem != NULL) { 3348 // scroll to the selected item 3349 if (Supermenu() == NULL) { 3350 window->TryScrollTo(selectedItem->Frame().top); 3351 } else { 3352 BPoint point = selectedItem->Frame().LeftTop(); 3353 BPoint superPoint = Superitem()->Frame().LeftTop(); 3354 Supermenu()->ConvertToScreen(&superPoint); 3355 ConvertToScreen(&point); 3356 window->TryScrollTo(point.y - superPoint.y); 3357 } 3358 } 3359 } 3360 } 3361 } else { 3362 _CacheFontInfo(); 3363 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 3364 + fPad.left + fPad.right, 3365 fFontHeight + fPad.top + fPad.bottom); 3366 } 3367 3368 if (move) 3369 window->MoveTo(frame.LeftTop()); 3370 } 3371 3372 3373 bool 3374 BMenu::_AddDynamicItems(bool keyDown) 3375 { 3376 bool addAborted = false; 3377 if (AddDynamicItem(B_INITIAL_ADD)) { 3378 BMenuItem* superItem = Superitem(); 3379 BMenu* superMenu = Supermenu(); 3380 do { 3381 if (superMenu != NULL 3382 && !superMenu->_OkToProceed(superItem, keyDown)) { 3383 AddDynamicItem(B_ABORT); 3384 addAborted = true; 3385 break; 3386 } 3387 } while (AddDynamicItem(B_PROCESSING)); 3388 } 3389 3390 return addAborted; 3391 } 3392 3393 3394 bool 3395 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 3396 { 3397 BPoint where; 3398 uint32 buttons; 3399 GetMouse(&where, &buttons, false); 3400 bool stickyMode = _IsStickyMode(); 3401 // Quit if user clicks the mouse button in sticky mode 3402 // or releases the mouse button in nonsticky mode 3403 // or moves the pointer over another item 3404 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 3405 // BeOS seems to do something similar. This could also be a bug in 3406 // Deskbar, though. 3407 if ((buttons != 0 && stickyMode) 3408 || ((dynamic_cast<BMenuBar*>(this) == NULL 3409 && (buttons == 0 && !stickyMode)) 3410 || ((_HitTestItems(where) != item) && !keyDown))) { 3411 return false; 3412 } 3413 3414 return true; 3415 } 3416 3417 3418 bool 3419 BMenu::_CustomTrackingWantsToQuit() 3420 { 3421 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 3422 && fExtraMenuData->trackingState != NULL) { 3423 return fExtraMenuData->trackingHook(this, 3424 fExtraMenuData->trackingState); 3425 } 3426 3427 return false; 3428 } 3429 3430 3431 void 3432 BMenu::_QuitTracking(bool onlyThis) 3433 { 3434 _SelectItem(NULL); 3435 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 3436 menuBar->_RestoreFocus(); 3437 3438 fState = MENU_STATE_CLOSED; 3439 3440 if (!onlyThis) { 3441 // Close the whole menu hierarchy 3442 if (Supermenu() != NULL) 3443 Supermenu()->fState = MENU_STATE_CLOSED; 3444 3445 if (_IsStickyMode()) 3446 _SetStickyMode(false); 3447 3448 if (LockLooper()) { 3449 be_app->ShowCursor(); 3450 UnlockLooper(); 3451 } 3452 } 3453 3454 _Hide(); 3455 } 3456 3457 3458 // #pragma mark - menu_info functions 3459 3460 3461 // TODO: Maybe the following two methods would fit better into 3462 // InterfaceDefs.cpp 3463 // In R5, they do all the work client side, we let the app_server handle the 3464 // details. 3465 status_t 3466 set_menu_info(menu_info* info) 3467 { 3468 if (!info) 3469 return B_BAD_VALUE; 3470 3471 BPrivate::AppServerLink link; 3472 link.StartMessage(AS_SET_MENU_INFO); 3473 link.Attach<menu_info>(*info); 3474 3475 status_t status = B_ERROR; 3476 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3477 BMenu::sMenuInfo = *info; 3478 // Update also the local copy, in case anyone relies on it 3479 3480 return status; 3481 } 3482 3483 3484 status_t 3485 get_menu_info(menu_info* info) 3486 { 3487 if (!info) 3488 return B_BAD_VALUE; 3489 3490 BPrivate::AppServerLink link; 3491 link.StartMessage(AS_GET_MENU_INFO); 3492 3493 status_t status = B_ERROR; 3494 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3495 link.Read<menu_info>(info); 3496 3497 return status; 3498 } 3499 3500 3501 extern "C" void 3502 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3503 BMenu* menu, bool descendants) 3504 { 3505 menu->InvalidateLayout(); 3506 } 3507