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