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