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