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