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*)(addr_t)tolower(c)); } 72 bool AddTrigger(uint32 c) 73 { return fList.AddItem((void*)(addr_t)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 1111 if (item->IsMarked()) 1112 return item; 1113 } 1114 1115 return NULL; 1116 } 1117 1118 1119 int32 1120 BMenu::FindMarkedIndex() 1121 { 1122 for (int32 i = 0; i < fItems.CountItems(); i++) { 1123 BMenuItem* item = ItemAt(i); 1124 1125 if (item->IsMarked()) 1126 return i; 1127 } 1128 1129 return -1; 1130 } 1131 1132 1133 BMenu* 1134 BMenu::Supermenu() const 1135 { 1136 return fSuper; 1137 } 1138 1139 1140 BMenuItem* 1141 BMenu::Superitem() const 1142 { 1143 return fSuperitem; 1144 } 1145 1146 1147 // #pragma mark - 1148 1149 1150 BHandler* 1151 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier, 1152 int32 form, const char* property) 1153 { 1154 BPropertyInfo propInfo(sPropList); 1155 BHandler* target = NULL; 1156 1157 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) { 1158 case B_ERROR: 1159 break; 1160 1161 case 0: 1162 case 1: 1163 case 2: 1164 case 3: 1165 case 4: 1166 case 5: 1167 case 6: 1168 case 7: 1169 target = this; 1170 break; 1171 case 8: 1172 // TODO: redirect to menu 1173 target = this; 1174 break; 1175 case 9: 1176 case 10: 1177 case 11: 1178 case 12: 1179 target = this; 1180 break; 1181 case 13: 1182 // TODO: redirect to menuitem 1183 target = this; 1184 break; 1185 } 1186 1187 if (!target) 1188 target = BView::ResolveSpecifier(msg, index, specifier, form, 1189 property); 1190 1191 return target; 1192 } 1193 1194 1195 status_t 1196 BMenu::GetSupportedSuites(BMessage* data) 1197 { 1198 if (data == NULL) 1199 return B_BAD_VALUE; 1200 1201 status_t err = data->AddString("suites", "suite/vnd.Be-menu"); 1202 1203 if (err < B_OK) 1204 return err; 1205 1206 BPropertyInfo propertyInfo(sPropList); 1207 err = data->AddFlat("messages", &propertyInfo); 1208 1209 if (err < B_OK) 1210 return err; 1211 1212 return BView::GetSupportedSuites(data); 1213 } 1214 1215 1216 status_t 1217 BMenu::Perform(perform_code code, void* _data) 1218 { 1219 switch (code) { 1220 case PERFORM_CODE_MIN_SIZE: 1221 ((perform_data_min_size*)_data)->return_value 1222 = BMenu::MinSize(); 1223 return B_OK; 1224 case PERFORM_CODE_MAX_SIZE: 1225 ((perform_data_max_size*)_data)->return_value 1226 = BMenu::MaxSize(); 1227 return B_OK; 1228 case PERFORM_CODE_PREFERRED_SIZE: 1229 ((perform_data_preferred_size*)_data)->return_value 1230 = BMenu::PreferredSize(); 1231 return B_OK; 1232 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1233 ((perform_data_layout_alignment*)_data)->return_value 1234 = BMenu::LayoutAlignment(); 1235 return B_OK; 1236 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1237 ((perform_data_has_height_for_width*)_data)->return_value 1238 = BMenu::HasHeightForWidth(); 1239 return B_OK; 1240 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1241 { 1242 perform_data_get_height_for_width* data 1243 = (perform_data_get_height_for_width*)_data; 1244 BMenu::GetHeightForWidth(data->width, &data->min, &data->max, 1245 &data->preferred); 1246 return B_OK; 1247 } 1248 case PERFORM_CODE_SET_LAYOUT: 1249 { 1250 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1251 BMenu::SetLayout(data->layout); 1252 return B_OK; 1253 } 1254 case PERFORM_CODE_LAYOUT_INVALIDATED: 1255 { 1256 perform_data_layout_invalidated* data 1257 = (perform_data_layout_invalidated*)_data; 1258 BMenu::LayoutInvalidated(data->descendants); 1259 return B_OK; 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 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags, 1273 menu_layout layout, bool resizeToFit) 1274 : 1275 BView(frame, name, resizingMode, flags), 1276 fChosenItem(NULL), 1277 fSelected(NULL), 1278 fCachedMenuWindow(NULL), 1279 fSuper(NULL), 1280 fSuperitem(NULL), 1281 fAscent(-1.0f), 1282 fDescent(-1.0f), 1283 fFontHeight(-1.0f), 1284 fState(MENU_STATE_CLOSED), 1285 fLayout(layout), 1286 fExtraRect(NULL), 1287 fMaxContentWidth(0.0f), 1288 fInitMatrixSize(NULL), 1289 fExtraMenuData(NULL), 1290 fTrigger(0), 1291 fResizeToFit(resizeToFit), 1292 fUseCachedMenuLayout(false), 1293 fEnabled(true), 1294 fDynamicName(false), 1295 fRadioMode(false), 1296 fTrackNewBounds(false), 1297 fStickyMode(false), 1298 fIgnoreHidden(true), 1299 fTriggerEnabled(true), 1300 fRedrawAfterSticky(false), 1301 fAttachAborted(false) 1302 { 1303 _InitData(NULL); 1304 } 1305 1306 1307 void 1308 BMenu::SetItemMargins(float left, float top, float right, float bottom) 1309 { 1310 fPad.Set(left, top, right, bottom); 1311 } 1312 1313 1314 void 1315 BMenu::GetItemMargins(float* left, float* top, float* right, 1316 float* bottom) const 1317 { 1318 if (left != NULL) 1319 *left = fPad.left; 1320 if (top != NULL) 1321 *top = fPad.top; 1322 if (right != NULL) 1323 *right = fPad.right; 1324 if (bottom != NULL) 1325 *bottom = fPad.bottom; 1326 } 1327 1328 1329 menu_layout 1330 BMenu::Layout() const 1331 { 1332 return fLayout; 1333 } 1334 1335 1336 void 1337 BMenu::Show() 1338 { 1339 Show(false); 1340 } 1341 1342 1343 void 1344 BMenu::Show(bool selectFirst) 1345 { 1346 _Install(NULL); 1347 _Show(selectFirst); 1348 } 1349 1350 1351 void 1352 BMenu::Hide() 1353 { 1354 _Hide(); 1355 _Uninstall(); 1356 } 1357 1358 1359 BMenuItem* 1360 BMenu::Track(bool sticky, BRect* clickToOpenRect) 1361 { 1362 if (sticky && LockLooper()) { 1363 //RedrawAfterSticky(Bounds()); 1364 // the call above didn't do anything, so I've removed it for now 1365 UnlockLooper(); 1366 } 1367 1368 if (clickToOpenRect != NULL && LockLooper()) { 1369 fExtraRect = clickToOpenRect; 1370 ConvertFromScreen(fExtraRect); 1371 UnlockLooper(); 1372 } 1373 1374 _SetStickyMode(sticky); 1375 1376 int action; 1377 BMenuItem* menuItem = _Track(&action); 1378 1379 fExtraRect = NULL; 1380 1381 return menuItem; 1382 } 1383 1384 1385 bool 1386 BMenu::AddDynamicItem(add_state state) 1387 { 1388 // Implemented in subclasses 1389 return false; 1390 } 1391 1392 1393 void 1394 BMenu::DrawBackground(BRect update) 1395 { 1396 if (be_control_look != NULL) { 1397 rgb_color base = sMenuInfo.background_color; 1398 uint32 flags = 0; 1399 if (!IsEnabled()) 1400 flags |= BControlLook::B_DISABLED; 1401 if (IsFocus()) 1402 flags |= BControlLook::B_FOCUSED; 1403 BRect rect = Bounds(); 1404 uint32 borders = BControlLook::B_LEFT_BORDER 1405 | BControlLook::B_RIGHT_BORDER; 1406 if (Window() != NULL && Parent() != NULL) { 1407 if (Parent()->Frame().top == Window()->Bounds().top) 1408 borders |= BControlLook::B_TOP_BORDER; 1409 if (Parent()->Frame().bottom == Window()->Bounds().bottom) 1410 borders |= BControlLook::B_BOTTOM_BORDER; 1411 } else { 1412 borders |= BControlLook::B_TOP_BORDER 1413 | BControlLook::B_BOTTOM_BORDER; 1414 } 1415 be_control_look->DrawMenuBackground(this, rect, update, base, 0, 1416 borders); 1417 1418 return; 1419 } 1420 1421 rgb_color oldColor = HighColor(); 1422 SetHighColor(sMenuInfo.background_color); 1423 FillRect(Bounds() & update, B_SOLID_HIGH); 1424 SetHighColor(oldColor); 1425 } 1426 1427 1428 void 1429 BMenu::SetTrackingHook(menu_tracking_hook func, void* state) 1430 { 1431 delete fExtraMenuData; 1432 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(func, state); 1433 } 1434 1435 1436 void BMenu::_ReservedMenu3() {} 1437 void BMenu::_ReservedMenu4() {} 1438 void BMenu::_ReservedMenu5() {} 1439 void BMenu::_ReservedMenu6() {} 1440 1441 1442 void 1443 BMenu::_InitData(BMessage* archive) 1444 { 1445 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>"); 1446 1447 // TODO: Get _color, _fname, _fflt from the message, if present 1448 BFont font; 1449 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1450 font.SetSize(sMenuInfo.font_size); 1451 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1452 1453 fLayoutData = new LayoutData; 1454 fLayoutData->lastResizingMode = ResizingMode(); 1455 1456 SetLowColor(sMenuInfo.background_color); 1457 SetViewColor(B_TRANSPARENT_COLOR); 1458 1459 fTriggerEnabled = sMenuInfo.triggers_always_shown; 1460 1461 if (archive != NULL) { 1462 archive->FindInt32("_layout", (int32*)&fLayout); 1463 archive->FindBool("_rsize_to_fit", &fResizeToFit); 1464 bool disabled; 1465 if (archive->FindBool("_disable", &disabled) == B_OK) 1466 fEnabled = !disabled; 1467 archive->FindBool("_radio", &fRadioMode); 1468 1469 bool disableTrigger = false; 1470 archive->FindBool("_trig_disabled", &disableTrigger); 1471 fTriggerEnabled = !disableTrigger; 1472 1473 archive->FindBool("_dyn_label", &fDynamicName); 1474 archive->FindFloat("_maxwidth", &fMaxContentWidth); 1475 1476 BMessage msg; 1477 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) { 1478 BArchivable* object = instantiate_object(&msg); 1479 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) { 1480 BRect bounds; 1481 if (fLayout == B_ITEMS_IN_MATRIX 1482 && archive->FindRect("_i_frames", i, &bounds) == B_OK) 1483 AddItem(item, bounds); 1484 else 1485 AddItem(item); 1486 } 1487 } 1488 } 1489 } 1490 1491 1492 bool 1493 BMenu::_Show(bool selectFirstItem, bool keyDown) 1494 { 1495 // See if the supermenu has a cached menuwindow, 1496 // and use that one if possible. 1497 BMenuWindow* window = NULL; 1498 bool ourWindow = false; 1499 if (fSuper != NULL) { 1500 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1501 window = fSuper->_MenuWindow(); 1502 } 1503 1504 // Otherwise, create a new one 1505 // This happens for "stand alone" BPopUpMenus 1506 // (i.e. not within a BMenuField) 1507 if (window == NULL) { 1508 // Menu windows get the BMenu's handler name 1509 window = new (nothrow) BMenuWindow(Name()); 1510 ourWindow = true; 1511 } 1512 1513 if (window == NULL) 1514 return false; 1515 1516 if (window->Lock()) { 1517 bool addAborted = false; 1518 if (keyDown) 1519 addAborted = _AddDynamicItems(keyDown); 1520 1521 if (addAborted) { 1522 if (ourWindow) 1523 window->Quit(); 1524 else 1525 window->Unlock(); 1526 return false; 1527 } 1528 fAttachAborted = false; 1529 1530 window->AttachMenu(this); 1531 1532 if (ItemAt(0) != NULL) { 1533 float width, height; 1534 ItemAt(0)->GetContentSize(&width, &height); 1535 1536 window->SetSmallStep(ceilf(height)); 1537 } 1538 1539 // Menu didn't have the time to add its items: aborting... 1540 if (fAttachAborted) { 1541 window->DetachMenu(); 1542 // TODO: Probably not needed, we can just let _hide() quit the 1543 // window. 1544 if (ourWindow) 1545 window->Quit(); 1546 else 1547 window->Unlock(); 1548 return false; 1549 } 1550 1551 _UpdateWindowViewSize(true); 1552 window->Show(); 1553 1554 if (selectFirstItem) 1555 _SelectItem(ItemAt(0), false); 1556 1557 window->Unlock(); 1558 } 1559 1560 return true; 1561 } 1562 1563 1564 void 1565 BMenu::_Hide() 1566 { 1567 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 1568 if (window == NULL || !window->Lock()) 1569 return; 1570 1571 if (fSelected != NULL) 1572 _SelectItem(NULL); 1573 1574 window->Hide(); 1575 window->DetachMenu(); 1576 // we don't want to be deleted when the window is removed 1577 1578 #if USE_CACHED_MENUWINDOW 1579 if (fSuper != NULL) 1580 window->Unlock(); 1581 else 1582 #endif 1583 window->Quit(); 1584 // it's our window, quit it 1585 1586 1587 // Delete the menu window used by our submenus 1588 _DeleteMenuWindow(); 1589 } 1590 1591 1592 // #pragma mark - mouse tracking 1593 1594 1595 const static bigtime_t kOpenSubmenuDelay = 225000; 1596 const static bigtime_t kNavigationAreaTimeout = 1000000; 1597 1598 1599 BMenuItem* 1600 BMenu::_Track(int* action, long start) 1601 { 1602 // TODO: cleanup 1603 BMenuItem* item = NULL; 1604 BRect navAreaRectAbove; 1605 BRect navAreaRectBelow; 1606 bigtime_t selectedTime = system_time(); 1607 bigtime_t navigationAreaTime = 0; 1608 1609 fState = MENU_STATE_TRACKING; 1610 // we will use this for keyboard selection: 1611 fChosenItem = NULL; 1612 1613 BPoint location; 1614 uint32 buttons = 0; 1615 if (LockLooper()) { 1616 GetMouse(&location, &buttons); 1617 UnlockLooper(); 1618 } 1619 1620 bool releasedOnce = buttons == 0; 1621 while (fState != MENU_STATE_CLOSED) { 1622 if (_CustomTrackingWantsToQuit()) 1623 break; 1624 1625 if (!LockLooper()) 1626 break; 1627 1628 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 1629 BPoint screenLocation = ConvertToScreen(location); 1630 if (window->CheckForScrolling(screenLocation)) { 1631 UnlockLooper(); 1632 continue; 1633 } 1634 1635 // The order of the checks is important 1636 // to be able to handle overlapping menus: 1637 // first we check if mouse is inside a submenu, 1638 // then if the mouse is inside this menu, 1639 // then if it's over a super menu. 1640 if (_OverSubmenu(fSelected, screenLocation) 1641 || fState == MENU_STATE_KEY_TO_SUBMENU) { 1642 if (fState == MENU_STATE_TRACKING) { 1643 // not if from R.Arrow 1644 fState = MENU_STATE_TRACKING_SUBMENU; 1645 } 1646 navAreaRectAbove = BRect(); 1647 navAreaRectBelow = BRect(); 1648 1649 // Since the submenu has its own looper, 1650 // we can unlock ours. Doing so also make sure 1651 // that our window gets any update message to 1652 // redraw itself 1653 UnlockLooper(); 1654 int submenuAction = MENU_STATE_TRACKING; 1655 BMenu* submenu = fSelected->Submenu(); 1656 submenu->_SetStickyMode(_IsStickyMode()); 1657 1658 // The following call blocks until the submenu 1659 // gives control back to us, either because the mouse 1660 // pointer goes out of the submenu's bounds, or because 1661 // the user closes the menu 1662 BMenuItem* submenuItem = submenu->_Track(&submenuAction); 1663 if (submenuAction == MENU_STATE_CLOSED) { 1664 item = submenuItem; 1665 fState = MENU_STATE_CLOSED; 1666 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) { 1667 if (LockLooper()) { 1668 BMenuItem *temp = fSelected; 1669 // close the submenu: 1670 _SelectItem(NULL); 1671 // but reselect the item itself for user: 1672 _SelectItem(temp, false); 1673 UnlockLooper(); 1674 } 1675 // cancel key-nav state 1676 fState = MENU_STATE_TRACKING; 1677 } else 1678 fState = MENU_STATE_TRACKING; 1679 if (!LockLooper()) 1680 break; 1681 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) { 1682 _UpdateStateOpenSelect(item, location, navAreaRectAbove, 1683 navAreaRectBelow, selectedTime, navigationAreaTime); 1684 if (!releasedOnce) 1685 releasedOnce = true; 1686 } else if (_OverSuper(screenLocation) && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 1687 fState = MENU_STATE_TRACKING; 1688 UnlockLooper(); 1689 break; 1690 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) { 1691 UnlockLooper(); 1692 break; 1693 } else if (fSuper == NULL || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 1694 // Mouse pointer outside menu: 1695 // If there's no other submenu opened, 1696 // deselect the current selected item 1697 if (fSelected != NULL 1698 && (fSelected->Submenu() == NULL 1699 || fSelected->Submenu()->Window() == NULL)) { 1700 _SelectItem(NULL); 1701 fState = MENU_STATE_TRACKING; 1702 } 1703 1704 if (fSuper != NULL) { 1705 // Give supermenu the chance to continue tracking 1706 *action = fState; 1707 UnlockLooper(); 1708 return NULL; 1709 } 1710 } 1711 1712 UnlockLooper(); 1713 1714 if (releasedOnce) 1715 _UpdateStateClose(item, location, buttons); 1716 1717 if (fState != MENU_STATE_CLOSED) { 1718 bigtime_t snoozeAmount = 50000; 1719 1720 BPoint newLocation = location; 1721 uint32 newButtons = buttons; 1722 1723 // If user doesn't move the mouse, loop here, 1724 // so we don't interfere with keyboard menu navigation 1725 do { 1726 snooze(snoozeAmount); 1727 if (!LockLooper()) 1728 break; 1729 GetMouse(&newLocation, &newButtons, true); 1730 UnlockLooper(); 1731 } while (newLocation == location && newButtons == buttons 1732 && !(item != NULL && item->Submenu() != NULL 1733 && item->Submenu()->Window() == NULL) 1734 && fState == MENU_STATE_TRACKING); 1735 1736 if (newLocation != location || newButtons != buttons) { 1737 if (!releasedOnce && newButtons == 0 && buttons != 0) 1738 releasedOnce = true; 1739 location = newLocation; 1740 buttons = newButtons; 1741 } 1742 1743 if (releasedOnce) 1744 _UpdateStateClose(item, location, buttons); 1745 } 1746 } 1747 1748 if (action != NULL) 1749 *action = fState; 1750 1751 // keyboard Enter will set this 1752 if (fChosenItem != NULL) 1753 item = fChosenItem; 1754 else if (fSelected == NULL) 1755 // needed to cover (rare) mouse/ESC combination 1756 item = NULL; 1757 1758 if (fSelected != NULL && LockLooper()) { 1759 _SelectItem(NULL); 1760 UnlockLooper(); 1761 } 1762 1763 // delete the menu window recycled for all the child menus 1764 _DeleteMenuWindow(); 1765 1766 return item; 1767 } 1768 1769 1770 void 1771 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove, 1772 BRect& navAreaRectBelow) 1773 { 1774 #define NAV_AREA_THRESHOLD 8 1775 1776 // The navigation area is a region in which mouse-overs won't select 1777 // the item under the cursor. This makes it easier to navigate to 1778 // submenus, as the cursor can be moved to submenu items directly instead 1779 // of having to move it horizontally into the submenu first. The concept 1780 // is illustrated below: 1781 // 1782 // +-------+----+---------+ 1783 // | | /| | 1784 // | | /*| | 1785 // |[2]--> | /**| | 1786 // | |/[4]| | 1787 // |------------| | 1788 // | [1] | [6] | 1789 // |------------| | 1790 // | |\[5]| | 1791 // |[3]--> | \**| | 1792 // | | \*| | 1793 // | | \| | 1794 // | +----|---------+ 1795 // | | 1796 // +------------+ 1797 // 1798 // [1] Selected item, cursor position ('position') 1799 // [2] Upper navigation area rectangle ('navAreaRectAbove') 1800 // [3] Lower navigation area rectangle ('navAreaRectBelow') 1801 // [4] Upper navigation area 1802 // [5] Lower navigation area 1803 // [6] Submenu 1804 // 1805 // The rectangles are used to calculate if the cursor is in the actual 1806 // navigation area (see _UpdateStateOpenSelect()). 1807 1808 if (fSelected == NULL) 1809 return; 1810 1811 BMenu* submenu = fSelected->Submenu(); 1812 1813 if (submenu != NULL) { 1814 BRect menuBounds = ConvertToScreen(Bounds()); 1815 1816 fSelected->Submenu()->LockLooper(); 1817 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1818 fSelected->Submenu()->Bounds()); 1819 fSelected->Submenu()->UnlockLooper(); 1820 1821 if (menuBounds.left < submenuBounds.left) { 1822 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD, 1823 submenuBounds.top, menuBounds.right, 1824 position.y); 1825 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD, 1826 position.y, menuBounds.right, 1827 submenuBounds.bottom); 1828 } else { 1829 navAreaRectAbove.Set(menuBounds.left, 1830 submenuBounds.top, position.x - NAV_AREA_THRESHOLD, 1831 position.y); 1832 navAreaRectBelow.Set(menuBounds.left, 1833 position.y, position.x - NAV_AREA_THRESHOLD, 1834 submenuBounds.bottom); 1835 } 1836 } else { 1837 navAreaRectAbove = BRect(); 1838 navAreaRectBelow = BRect(); 1839 } 1840 } 1841 1842 1843 void 1844 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position, 1845 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime, 1846 bigtime_t& navigationAreaTime) 1847 { 1848 if (fState == MENU_STATE_CLOSED) 1849 return; 1850 1851 if (item != fSelected) { 1852 if (navigationAreaTime == 0) 1853 navigationAreaTime = system_time(); 1854 1855 position = ConvertToScreen(position); 1856 1857 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position); 1858 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position); 1859 1860 if (fSelected == NULL 1861 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) { 1862 _SelectItem(item, false); 1863 navAreaRectAbove = BRect(); 1864 navAreaRectBelow = BRect(); 1865 selectedTime = system_time(); 1866 navigationAreaTime = 0; 1867 return; 1868 } 1869 1870 BRect menuBounds = ConvertToScreen(Bounds()); 1871 1872 fSelected->Submenu()->LockLooper(); 1873 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1874 fSelected->Submenu()->Bounds()); 1875 fSelected->Submenu()->UnlockLooper(); 1876 1877 float xOffset; 1878 1879 // navAreaRectAbove and navAreaRectBelow have the same X 1880 // position and width, so it doesn't matter which one we use to 1881 // calculate the X offset 1882 if (menuBounds.left < submenuBounds.left) 1883 xOffset = position.x - navAreaRectAbove.left; 1884 else 1885 xOffset = navAreaRectAbove.right - position.x; 1886 1887 bool inNavArea; 1888 1889 if (inNavAreaRectAbove) { 1890 float yOffset = navAreaRectAbove.bottom - position.y; 1891 float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height(); 1892 1893 inNavArea = yOffset <= xOffset / ratio; 1894 } else { 1895 float yOffset = navAreaRectBelow.bottom - position.y; 1896 float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height(); 1897 1898 inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset 1899 / ratio); 1900 } 1901 1902 bigtime_t systime = system_time(); 1903 1904 if (!inNavArea || (navigationAreaTime > 0 && systime - 1905 navigationAreaTime > kNavigationAreaTimeout)) { 1906 // Don't delay opening of submenu if the user had 1907 // to wait for the navigation area timeout anyway 1908 _SelectItem(item, inNavArea); 1909 1910 if (inNavArea) { 1911 _UpdateNavigationArea(position, navAreaRectAbove, 1912 navAreaRectBelow); 1913 } else { 1914 navAreaRectAbove = BRect(); 1915 navAreaRectBelow = BRect(); 1916 } 1917 1918 selectedTime = system_time(); 1919 navigationAreaTime = 0; 1920 } 1921 } else if (fSelected->Submenu() != NULL && 1922 system_time() - selectedTime > kOpenSubmenuDelay) { 1923 _SelectItem(fSelected, true); 1924 1925 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) { 1926 position = ConvertToScreen(position); 1927 _UpdateNavigationArea(position, navAreaRectAbove, 1928 navAreaRectBelow); 1929 } 1930 } 1931 1932 if (fState != MENU_STATE_TRACKING) 1933 fState = MENU_STATE_TRACKING; 1934 } 1935 1936 1937 void 1938 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where, 1939 const uint32& buttons) 1940 { 1941 if (fState == MENU_STATE_CLOSED) 1942 return; 1943 1944 if (buttons != 0 && _IsStickyMode()) { 1945 if (item == NULL) { 1946 if (item != fSelected) { 1947 LockLooper(); 1948 _SelectItem(item, false); 1949 UnlockLooper(); 1950 } 1951 fState = MENU_STATE_CLOSED; 1952 } else 1953 _SetStickyMode(false); 1954 } else if (buttons == 0 && !_IsStickyMode()) { 1955 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 1956 _SetStickyMode(true); 1957 fExtraRect = NULL; 1958 // Setting this to NULL will prevent this code 1959 // to be executed next time 1960 } else { 1961 if (item != fSelected) { 1962 LockLooper(); 1963 _SelectItem(item, false); 1964 UnlockLooper(); 1965 } 1966 fState = MENU_STATE_CLOSED; 1967 } 1968 } 1969 } 1970 1971 1972 // #pragma mark - 1973 1974 1975 bool 1976 BMenu::_AddItem(BMenuItem* item, int32 index) 1977 { 1978 ASSERT(item != NULL); 1979 if (index < 0 || index > fItems.CountItems()) 1980 return false; 1981 1982 if (item->IsMarked()) 1983 _ItemMarked(item); 1984 1985 if (!fItems.AddItem(item, index)) 1986 return false; 1987 1988 // install the item on the supermenu's window 1989 // or onto our window, if we are a root menu 1990 BWindow* window = NULL; 1991 if (Superitem() != NULL) 1992 window = Superitem()->fWindow; 1993 else 1994 window = Window(); 1995 if (window != NULL) 1996 item->Install(window); 1997 1998 item->SetSuper(this); 1999 return true; 2000 } 2001 2002 2003 bool 2004 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item, 2005 bool deleteItems) 2006 { 2007 bool success = false; 2008 bool invalidateLayout = false; 2009 2010 bool locked = LockLooper(); 2011 BWindow* window = Window(); 2012 2013 // The plan is simple: If we're given a BMenuItem directly, we use it 2014 // and ignore index and count. Otherwise, we use them instead. 2015 if (item != NULL) { 2016 if (fItems.RemoveItem(item)) { 2017 if (item == fSelected && window != NULL) 2018 _SelectItem(NULL); 2019 item->Uninstall(); 2020 item->SetSuper(NULL); 2021 if (deleteItems) 2022 delete item; 2023 success = invalidateLayout = true; 2024 } 2025 } else { 2026 // We iterate backwards because it's simpler 2027 int32 i = min_c(index + count - 1, fItems.CountItems() - 1); 2028 // NOTE: the range check for "index" is done after 2029 // calculating the last index to be removed, so 2030 // that the range is not "shifted" unintentionally 2031 index = max_c(0, index); 2032 for (; i >= index; i--) { 2033 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 2034 if (item != NULL) { 2035 if (fItems.RemoveItem(item)) { 2036 if (item == fSelected && window != NULL) 2037 _SelectItem(NULL); 2038 item->Uninstall(); 2039 item->SetSuper(NULL); 2040 if (deleteItems) 2041 delete item; 2042 success = true; 2043 invalidateLayout = true; 2044 } else { 2045 // operation not entirely successful 2046 success = false; 2047 break; 2048 } 2049 } 2050 } 2051 } 2052 2053 if (invalidateLayout) { 2054 InvalidateLayout(); 2055 if (locked && window != NULL) { 2056 _LayoutItems(0); 2057 _UpdateWindowViewSize(false); 2058 Invalidate(); 2059 } 2060 } 2061 2062 if (locked) 2063 UnlockLooper(); 2064 2065 return success; 2066 } 2067 2068 2069 bool 2070 BMenu::_RelayoutIfNeeded() 2071 { 2072 if (!fUseCachedMenuLayout) { 2073 fUseCachedMenuLayout = true; 2074 _CacheFontInfo(); 2075 _LayoutItems(0); 2076 return true; 2077 } 2078 return false; 2079 } 2080 2081 2082 void 2083 BMenu::_LayoutItems(int32 index) 2084 { 2085 _CalcTriggers(); 2086 2087 float width, height; 2088 _ComputeLayout(index, fResizeToFit, true, &width, &height); 2089 2090 if (fResizeToFit) 2091 ResizeTo(width, height); 2092 } 2093 2094 2095 BSize 2096 BMenu::_ValidatePreferredSize() 2097 { 2098 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode() 2099 != fLayoutData->lastResizingMode) { 2100 _ComputeLayout(0, true, false, NULL, NULL); 2101 ResetLayoutInvalidation(); 2102 } 2103 2104 return fLayoutData->preferred; 2105 } 2106 2107 2108 void 2109 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 2110 float* _width, float* _height) 2111 { 2112 // TODO: Take "bestFit", "moveItems", "index" into account, 2113 // Recalculate only the needed items, 2114 // not the whole layout every time 2115 2116 fLayoutData->lastResizingMode = ResizingMode(); 2117 2118 BRect frame; 2119 2120 switch (fLayout) { 2121 case B_ITEMS_IN_COLUMN: 2122 { 2123 BRect parentFrame; 2124 BRect* overrideFrame = NULL; 2125 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) { 2126 parentFrame = Supermenu()->Bounds(); 2127 overrideFrame = &parentFrame; 2128 } 2129 2130 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, 2131 frame); 2132 break; 2133 } 2134 case B_ITEMS_IN_ROW: 2135 _ComputeRowLayout(index, bestFit, moveItems, frame); 2136 break; 2137 2138 case B_ITEMS_IN_MATRIX: 2139 _ComputeMatrixLayout(frame); 2140 break; 2141 2142 default: 2143 break; 2144 } 2145 2146 // change width depending on resize mode 2147 BSize size; 2148 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2149 if (Parent()) 2150 size.width = Parent()->Frame().Width() + 1; 2151 else if (Window()) 2152 size.width = Window()->Frame().Width() + 1; 2153 else 2154 size.width = Bounds().Width(); 2155 } else 2156 size.width = frame.Width(); 2157 2158 size.height = frame.Height(); 2159 2160 if (_width) 2161 *_width = size.width; 2162 2163 if (_height) 2164 *_height = size.height; 2165 2166 if (bestFit) 2167 fLayoutData->preferred = size; 2168 2169 if (moveItems) 2170 fUseCachedMenuLayout = true; 2171 } 2172 2173 2174 void 2175 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2176 BRect* overrideFrame, BRect& frame) 2177 { 2178 BFont font; 2179 GetFont(&font); 2180 bool command = false; 2181 bool control = false; 2182 bool shift = false; 2183 bool option = false; 2184 if (index > 0) 2185 frame = ItemAt(index - 1)->Frame(); 2186 else if (overrideFrame != NULL) { 2187 frame.Set(0, 0, overrideFrame->right, -1); 2188 } else 2189 frame.Set(0, 0, 0, -1); 2190 2191 for (; index < fItems.CountItems(); index++) { 2192 BMenuItem* item = ItemAt(index); 2193 2194 float width, height; 2195 item->GetContentSize(&width, &height); 2196 2197 if (item->fModifiers && item->fShortcutChar) { 2198 width += font.Size(); 2199 if (item->fModifiers & B_COMMAND_KEY) 2200 command = true; 2201 if (item->fModifiers & B_CONTROL_KEY) 2202 control = true; 2203 if (item->fModifiers & B_SHIFT_KEY) 2204 shift = true; 2205 if (item->fModifiers & B_OPTION_KEY) 2206 option = true; 2207 } 2208 2209 item->fBounds.left = 0.0f; 2210 item->fBounds.top = frame.bottom + 1.0f; 2211 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2212 + fPad.bottom; 2213 2214 if (item->fSubmenu != NULL) 2215 width += item->Frame().Height(); 2216 2217 frame.right = max_c(frame.right, width + fPad.left + fPad.right); 2218 frame.bottom = item->fBounds.bottom; 2219 } 2220 2221 if (command) 2222 frame.right += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2223 if (control) 2224 frame.right += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2225 if (option) 2226 frame.right += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2227 if (shift) 2228 frame.right += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2229 2230 if (fMaxContentWidth > 0) 2231 frame.right = min_c(frame.right, fMaxContentWidth); 2232 2233 if (moveItems) { 2234 for (int32 i = 0; i < fItems.CountItems(); i++) 2235 ItemAt(i)->fBounds.right = frame.right; 2236 } 2237 2238 frame.top = 0; 2239 frame.right = ceilf(frame.right); 2240 } 2241 2242 2243 void 2244 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2245 BRect& frame) 2246 { 2247 font_height fh; 2248 GetFontHeight(&fh); 2249 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2250 + fPad.bottom)); 2251 2252 for (int32 i = 0; i < fItems.CountItems(); i++) { 2253 BMenuItem* item = ItemAt(i); 2254 2255 float width, height; 2256 item->GetContentSize(&width, &height); 2257 2258 item->fBounds.left = frame.right; 2259 item->fBounds.top = 0.0f; 2260 item->fBounds.right = item->fBounds.left + width + fPad.left 2261 + fPad.right; 2262 2263 frame.right = item->Frame().right + 1.0f; 2264 frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom); 2265 } 2266 2267 if (moveItems) { 2268 for (int32 i = 0; i < fItems.CountItems(); i++) 2269 ItemAt(i)->fBounds.bottom = frame.bottom; 2270 } 2271 2272 if (bestFit) 2273 frame.right = ceilf(frame.right); 2274 else 2275 frame.right = Bounds().right; 2276 } 2277 2278 2279 void 2280 BMenu::_ComputeMatrixLayout(BRect &frame) 2281 { 2282 frame.Set(0, 0, 0, 0); 2283 for (int32 i = 0; i < CountItems(); i++) { 2284 BMenuItem* item = ItemAt(i); 2285 if (item != NULL) { 2286 frame.left = min_c(frame.left, item->Frame().left); 2287 frame.right = max_c(frame.right, item->Frame().right); 2288 frame.top = min_c(frame.top, item->Frame().top); 2289 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 2290 } 2291 } 2292 } 2293 2294 2295 void 2296 BMenu::LayoutInvalidated(bool descendants) 2297 { 2298 fUseCachedMenuLayout = false; 2299 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2300 } 2301 2302 2303 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2304 BPoint 2305 BMenu::ScreenLocation() 2306 { 2307 BMenu* superMenu = Supermenu(); 2308 BMenuItem* superItem = Superitem(); 2309 2310 if (superMenu == NULL || superItem == NULL) { 2311 debugger("BMenu can't determine where to draw." 2312 "Override BMenu::ScreenLocation() to determine location."); 2313 } 2314 2315 BPoint point; 2316 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2317 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2318 else 2319 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2320 2321 superMenu->ConvertToScreen(&point); 2322 2323 return point; 2324 } 2325 2326 2327 BRect 2328 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2329 { 2330 // TODO: Improve me 2331 BRect bounds = Bounds(); 2332 BRect frame = bounds.OffsetToCopy(where); 2333 2334 BScreen screen(Window()); 2335 BRect screenFrame = screen.Frame(); 2336 2337 BMenu* superMenu = Supermenu(); 2338 BMenuItem* superItem = Superitem(); 2339 2340 // TODO: Horrible hack: 2341 // When added to a BMenuField, a BPopUpMenu is the child of 2342 // a _BMCMenuBar_ to "fake" the menu hierarchy 2343 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2344 bool scroll = false; 2345 if (superMenu == NULL || superItem == NULL || inMenuField) { 2346 // just move the window on screen 2347 2348 if (frame.bottom > screenFrame.bottom) 2349 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2350 else if (frame.top < screenFrame.top) 2351 frame.OffsetBy(0, -frame.top); 2352 2353 if (frame.right > screenFrame.right) 2354 frame.OffsetBy(screenFrame.right - frame.right, 0); 2355 else if (frame.left < screenFrame.left) 2356 frame.OffsetBy(-frame.left, 0); 2357 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2358 if (frame.right > screenFrame.right) 2359 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2360 2361 if (frame.left < 0) 2362 frame.OffsetBy(-frame.left + 6, 0); 2363 2364 if (frame.bottom > screenFrame.bottom) 2365 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2366 } else { 2367 if (frame.bottom > screenFrame.bottom) { 2368 if (scrollOn != NULL && superMenu != NULL 2369 && dynamic_cast<BMenuBar*>(superMenu) != NULL 2370 && frame.top < (screenFrame.bottom - 80)) { 2371 scroll = true; 2372 } else { 2373 frame.OffsetBy(0, -superItem->Frame().Height() 2374 - frame.Height() - 3); 2375 } 2376 } 2377 2378 if (frame.right > screenFrame.right) 2379 frame.OffsetBy(screenFrame.right - frame.right, 0); 2380 } 2381 2382 if (!scroll) { 2383 // basically, if this returns false, it means 2384 // that the menu frame won't fit completely inside the screen 2385 // TODO: Scrolling will currently only work up/down, 2386 // not left/right 2387 scroll = screenFrame.Height() < frame.Height(); 2388 } 2389 2390 if (scrollOn != NULL) 2391 *scrollOn = scroll; 2392 2393 return frame; 2394 } 2395 2396 2397 void 2398 BMenu::_DrawItems(BRect updateRect) 2399 { 2400 int32 itemCount = fItems.CountItems(); 2401 for (int32 i = 0; i < itemCount; i++) { 2402 BMenuItem* item = ItemAt(i); 2403 if (item->Frame().Intersects(updateRect)) 2404 item->Draw(); 2405 } 2406 } 2407 2408 2409 int 2410 BMenu::_State(BMenuItem** item) const 2411 { 2412 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2413 return fState; 2414 2415 if (fSelected != NULL && fSelected->Submenu() != NULL) 2416 return fSelected->Submenu()->_State(item); 2417 2418 return fState; 2419 } 2420 2421 2422 void 2423 BMenu::_InvokeItem(BMenuItem* item, bool now) 2424 { 2425 if (!item->IsEnabled()) 2426 return; 2427 2428 // Do the "selected" animation 2429 // TODO: Doesn't work. This is supposed to highlight 2430 // and dehighlight the item, works on beos but not on haiku. 2431 if (!item->Submenu() && LockLooper()) { 2432 snooze(50000); 2433 item->Select(true); 2434 Window()->UpdateIfNeeded(); 2435 snooze(50000); 2436 item->Select(false); 2437 Window()->UpdateIfNeeded(); 2438 snooze(50000); 2439 item->Select(true); 2440 Window()->UpdateIfNeeded(); 2441 snooze(50000); 2442 item->Select(false); 2443 Window()->UpdateIfNeeded(); 2444 UnlockLooper(); 2445 } 2446 2447 // Lock the root menu window before calling BMenuItem::Invoke() 2448 BMenu* parent = this; 2449 BMenu* rootMenu = NULL; 2450 do { 2451 rootMenu = parent; 2452 parent = rootMenu->Supermenu(); 2453 } while (parent != NULL); 2454 2455 if (rootMenu->LockLooper()) { 2456 item->Invoke(); 2457 rootMenu->UnlockLooper(); 2458 } 2459 } 2460 2461 2462 bool 2463 BMenu::_OverSuper(BPoint location) 2464 { 2465 if (!Supermenu()) 2466 return false; 2467 2468 return fSuperbounds.Contains(location); 2469 } 2470 2471 2472 bool 2473 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2474 { 2475 if (item == NULL) 2476 return false; 2477 2478 BMenu* subMenu = item->Submenu(); 2479 if (subMenu == NULL || subMenu->Window() == NULL) 2480 return false; 2481 2482 // we assume that loc is in screen coords { 2483 if (subMenu->Window()->Frame().Contains(loc)) 2484 return true; 2485 2486 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2487 } 2488 2489 2490 BMenuWindow* 2491 BMenu::_MenuWindow() 2492 { 2493 #if USE_CACHED_MENUWINDOW 2494 if (fCachedMenuWindow == NULL) { 2495 char windowName[64]; 2496 snprintf(windowName, 64, "%s cached menu", Name()); 2497 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2498 } 2499 #endif 2500 return fCachedMenuWindow; 2501 } 2502 2503 2504 void 2505 BMenu::_DeleteMenuWindow() 2506 { 2507 if (fCachedMenuWindow != NULL) { 2508 fCachedMenuWindow->Lock(); 2509 fCachedMenuWindow->Quit(); 2510 fCachedMenuWindow = NULL; 2511 } 2512 } 2513 2514 2515 BMenuItem* 2516 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2517 { 2518 // TODO: Take "slop" into account ? 2519 2520 // if the point doesn't lie within the menu's 2521 // bounds, bail out immediately 2522 if (!Bounds().Contains(where)) 2523 return NULL; 2524 2525 int32 itemCount = CountItems(); 2526 for (int32 i = 0; i < itemCount; i++) { 2527 BMenuItem* item = ItemAt(i); 2528 if (item->IsEnabled() && item->Frame().Contains(where)) 2529 return item; 2530 } 2531 2532 return NULL; 2533 } 2534 2535 2536 BRect 2537 BMenu::_Superbounds() const 2538 { 2539 return fSuperbounds; 2540 } 2541 2542 2543 void 2544 BMenu::_CacheFontInfo() 2545 { 2546 font_height fh; 2547 GetFontHeight(&fh); 2548 fAscent = fh.ascent; 2549 fDescent = fh.descent; 2550 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2551 } 2552 2553 2554 void 2555 BMenu::_ItemMarked(BMenuItem* item) 2556 { 2557 if (IsRadioMode()) { 2558 for (int32 i = 0; i < CountItems(); i++) { 2559 if (ItemAt(i) != item) 2560 ItemAt(i)->SetMarked(false); 2561 } 2562 InvalidateLayout(); 2563 } 2564 2565 if (IsLabelFromMarked() && Superitem()) 2566 Superitem()->SetLabel(item->Label()); 2567 } 2568 2569 2570 void 2571 BMenu::_Install(BWindow* target) 2572 { 2573 for (int32 i = 0; i < CountItems(); i++) 2574 ItemAt(i)->Install(target); 2575 } 2576 2577 2578 void 2579 BMenu::_Uninstall() 2580 { 2581 for (int32 i = 0; i < CountItems(); i++) 2582 ItemAt(i)->Uninstall(); 2583 } 2584 2585 2586 void 2587 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, 2588 bool selectFirstItem, bool keyDown) 2589 { 2590 // Avoid deselecting and then reselecting the same item 2591 // which would cause flickering 2592 if (menuItem != fSelected) { 2593 if (fSelected != NULL) { 2594 fSelected->Select(false); 2595 BMenu* subMenu = fSelected->Submenu(); 2596 if (subMenu != NULL && subMenu->Window() != NULL) 2597 subMenu->_Hide(); 2598 } 2599 2600 fSelected = menuItem; 2601 if (fSelected != NULL) 2602 fSelected->Select(true); 2603 } 2604 2605 if (fSelected != NULL && showSubmenu) { 2606 BMenu* subMenu = fSelected->Submenu(); 2607 if (subMenu != NULL && subMenu->Window() == NULL) { 2608 if (!subMenu->_Show(selectFirstItem, keyDown)) { 2609 // something went wrong, deselect the item 2610 fSelected->Select(false); 2611 fSelected = NULL; 2612 } 2613 } 2614 } 2615 } 2616 2617 2618 bool 2619 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 2620 { 2621 if (CountItems() == 0) // cannot select next item in an empty menu 2622 return false; 2623 2624 BMenuItem* nextItem = _NextItem(item, forward); 2625 if (nextItem == NULL) 2626 return false; 2627 2628 bool openMenu = false; 2629 if (dynamic_cast<BMenuBar*>(this) != NULL) 2630 openMenu = true; 2631 _SelectItem(nextItem, openMenu); 2632 return true; 2633 } 2634 2635 2636 BMenuItem* 2637 BMenu::_NextItem(BMenuItem* item, bool forward) const 2638 { 2639 const int32 numItems = fItems.CountItems(); 2640 if (numItems == 0) 2641 return NULL; 2642 2643 int32 index = fItems.IndexOf(item); 2644 int32 loopCount = numItems; 2645 while (--loopCount) { 2646 // Cycle through menu items in the given direction... 2647 if (forward) 2648 index++; 2649 else 2650 index--; 2651 2652 // ... wrap around... 2653 if (index < 0) 2654 index = numItems - 1; 2655 else if (index >= numItems) 2656 index = 0; 2657 2658 // ... and return the first suitable item found. 2659 BMenuItem* nextItem = ItemAt(index); 2660 if (nextItem->IsEnabled()) 2661 return nextItem; 2662 } 2663 2664 // If no other suitable item was found, return NULL. 2665 return NULL; 2666 } 2667 2668 2669 void 2670 BMenu::_SetIgnoreHidden(bool on) 2671 { 2672 fIgnoreHidden = on; 2673 } 2674 2675 2676 void 2677 BMenu::_SetStickyMode(bool on) 2678 { 2679 if (fStickyMode == on) 2680 return; 2681 2682 fStickyMode = on; 2683 2684 // If we are switching to sticky mode, propagate the status 2685 // back to the super menu 2686 if (fSuper != NULL) 2687 fSuper->_SetStickyMode(on); 2688 else { 2689 // TODO: Ugly hack, but it needs to be done right here in this method 2690 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 2691 if (on && menuBar != NULL && menuBar->LockLooper()) { 2692 // Steal the focus from the current focus view 2693 // (needed to handle keyboard navigation) 2694 menuBar->_StealFocus(); 2695 menuBar->UnlockLooper(); 2696 } 2697 } 2698 } 2699 2700 2701 bool 2702 BMenu::_IsStickyMode() const 2703 { 2704 return fStickyMode; 2705 } 2706 2707 2708 void 2709 BMenu::_GetShiftKey(uint32 &value) const 2710 { 2711 // TODO: Move into init_interface_kit(). 2712 // Currently we can't do that, as get_modifier_key() blocks forever 2713 // when called on input_server initialization, since it tries 2714 // to send a synchronous message to itself (input_server is 2715 // a BApplication) 2716 2717 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK) 2718 value = 0x4b; 2719 } 2720 2721 2722 void 2723 BMenu::_GetControlKey(uint32 &value) const 2724 { 2725 // TODO: Move into init_interface_kit(). 2726 // Currently we can't do that, as get_modifier_key() blocks forever 2727 // when called on input_server initialization, since it tries 2728 // to send a synchronous message to itself (input_server is 2729 // a BApplication) 2730 2731 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK) 2732 value = 0x5c; 2733 } 2734 2735 2736 void 2737 BMenu::_GetCommandKey(uint32 &value) const 2738 { 2739 // TODO: Move into init_interface_kit(). 2740 // Currently we can't do that, as get_modifier_key() blocks forever 2741 // when called on input_server initialization, since it tries 2742 // to send a synchronous message to itself (input_server is 2743 // a BApplication) 2744 2745 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK) 2746 value = 0x66; 2747 } 2748 2749 2750 void 2751 BMenu::_GetOptionKey(uint32 &value) const 2752 { 2753 // TODO: Move into init_interface_kit(). 2754 // Currently we can't do that, as get_modifier_key() blocks forever 2755 // when called on input_server initialization, since it tries 2756 // to send a synchronous message to itself (input_server is 2757 // a BApplication) 2758 2759 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 2760 value = 0x5d; 2761 } 2762 2763 2764 void 2765 BMenu::_GetMenuKey(uint32 &value) const 2766 { 2767 // TODO: Move into init_interface_kit(). 2768 // Currently we can't do that, as get_modifier_key() blocks forever 2769 // when called on input_server initialization, since it tries 2770 // to send a synchronous message to itself (input_server is 2771 // a BApplication) 2772 2773 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 2774 value = 0x68; 2775 } 2776 2777 2778 void 2779 BMenu::_CalcTriggers() 2780 { 2781 BPrivate::TriggerList triggerList; 2782 2783 // Gathers the existing triggers set by the user 2784 for (int32 i = 0; i < CountItems(); i++) { 2785 char trigger = ItemAt(i)->Trigger(); 2786 if (trigger != 0) 2787 triggerList.AddTrigger(trigger); 2788 } 2789 2790 // Set triggers for items which don't have one yet 2791 for (int32 i = 0; i < CountItems(); i++) { 2792 BMenuItem* item = ItemAt(i); 2793 if (item->Trigger() == 0) { 2794 uint32 trigger; 2795 int32 index; 2796 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 2797 item->SetAutomaticTrigger(index, trigger); 2798 } 2799 } 2800 } 2801 2802 2803 bool 2804 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 2805 BPrivate::TriggerList& triggers) 2806 { 2807 if (title == NULL) 2808 return false; 2809 2810 uint32 c; 2811 2812 // two runs: first we look out for uppercase letters 2813 // TODO: support Unicode characters correctly! 2814 for (uint32 i = 0; (c = title[i]) != '\0'; i++) { 2815 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) { 2816 index = i; 2817 trigger = tolower(c); 2818 return triggers.AddTrigger(c); 2819 } 2820 } 2821 2822 // then, if we still haven't found anything, we accept them all 2823 index = 0; 2824 while ((c = UTF8ToCharCode(&title)) != 0) { 2825 if (!isspace(c) && !triggers.HasTrigger(c)) { 2826 trigger = tolower(c); 2827 return triggers.AddTrigger(c); 2828 } 2829 2830 index++; 2831 } 2832 2833 return false; 2834 } 2835 2836 2837 void 2838 BMenu::_UpdateWindowViewSize(const bool &move) 2839 { 2840 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 2841 if (window == NULL) 2842 return; 2843 2844 if (dynamic_cast<BMenuBar*>(this) != NULL) 2845 return; 2846 2847 if (!fResizeToFit) 2848 return; 2849 2850 bool scroll = false; 2851 const BPoint screenLocation = move ? ScreenLocation() : window->Frame().LeftTop(); 2852 BRect frame = _CalcFrame(screenLocation, &scroll); 2853 ResizeTo(frame.Width(), frame.Height()); 2854 2855 if (fItems.CountItems() > 0) { 2856 if (!scroll) { 2857 window->ResizeTo(Bounds().Width(), Bounds().Height()); 2858 } else { 2859 BScreen screen(window); 2860 2861 // If we need scrolling, resize the window to fit the screen and 2862 // attach scrollers to our cached BMenuWindow. 2863 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) { 2864 window->ResizeTo(Bounds().Width(), screen.Frame().Height()); 2865 frame.top = 0; 2866 } else { 2867 // Or, in case our parent was a BMenuBar enable scrolling with 2868 // normal size. 2869 window->ResizeTo(Bounds().Width(), 2870 screen.Frame().bottom - frame.top); 2871 } 2872 2873 window->AttachScrollers(); 2874 } 2875 } else { 2876 _CacheFontInfo(); 2877 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 2878 + fPad.left + fPad.right, 2879 fFontHeight + fPad.top + fPad.bottom); 2880 } 2881 2882 if (move) 2883 window->MoveTo(frame.LeftTop()); 2884 } 2885 2886 2887 bool 2888 BMenu::_AddDynamicItems(bool keyDown) 2889 { 2890 bool addAborted = false; 2891 if (AddDynamicItem(B_INITIAL_ADD)) { 2892 BMenuItem* superItem = Superitem(); 2893 BMenu* superMenu = Supermenu(); 2894 do { 2895 if (superMenu != NULL 2896 && !superMenu->_OkToProceed(superItem, keyDown)) { 2897 AddDynamicItem(B_ABORT); 2898 addAborted = true; 2899 break; 2900 } 2901 } while (AddDynamicItem(B_PROCESSING)); 2902 } 2903 2904 return addAborted; 2905 } 2906 2907 2908 bool 2909 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 2910 { 2911 BPoint where; 2912 uint32 buttons; 2913 GetMouse(&where, &buttons, false); 2914 bool stickyMode = _IsStickyMode(); 2915 // Quit if user clicks the mouse button in sticky mode 2916 // or releases the mouse button in nonsticky mode 2917 // or moves the pointer over another item 2918 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2919 // BeOS seems to do something similar. This could also be a bug in 2920 // Deskbar, though. 2921 if ((buttons != 0 && stickyMode) 2922 || ((dynamic_cast<BMenuBar*>(this) == NULL 2923 && (buttons == 0 && !stickyMode)) 2924 || ((_HitTestItems(where) != item) && !keyDown))) 2925 return false; 2926 2927 return true; 2928 } 2929 2930 2931 bool 2932 BMenu::_CustomTrackingWantsToQuit() 2933 { 2934 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 2935 && fExtraMenuData->trackingState != NULL) { 2936 return fExtraMenuData->trackingHook(this, 2937 fExtraMenuData->trackingState); 2938 } 2939 2940 return false; 2941 } 2942 2943 2944 void 2945 BMenu::_QuitTracking(bool onlyThis) 2946 { 2947 _SelectItem(NULL); 2948 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 2949 menuBar->_RestoreFocus(); 2950 2951 fState = MENU_STATE_CLOSED; 2952 2953 // Close the whole menu hierarchy 2954 if (!onlyThis && _IsStickyMode()) 2955 _SetStickyMode(false); 2956 2957 _Hide(); 2958 } 2959 2960 2961 // #pragma mark - 2962 2963 2964 // TODO: Maybe the following two methods would fit better into 2965 // InterfaceDefs.cpp 2966 // In R5, they do all the work client side, we let the app_server handle the 2967 // details. 2968 status_t 2969 set_menu_info(menu_info* info) 2970 { 2971 if (!info) 2972 return B_BAD_VALUE; 2973 2974 BPrivate::AppServerLink link; 2975 link.StartMessage(AS_SET_MENU_INFO); 2976 link.Attach<menu_info>(*info); 2977 2978 status_t status = B_ERROR; 2979 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2980 BMenu::sMenuInfo = *info; 2981 // Update also the local copy, in case anyone relies on it 2982 2983 return status; 2984 } 2985 2986 2987 status_t 2988 get_menu_info(menu_info* info) 2989 { 2990 if (!info) 2991 return B_BAD_VALUE; 2992 2993 BPrivate::AppServerLink link; 2994 link.StartMessage(AS_GET_MENU_INFO); 2995 2996 status_t status = B_ERROR; 2997 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2998 link.Read<menu_info>(info); 2999 3000 return status; 3001 } 3002 3003 3004 extern "C" void 3005 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3006 BMenu* menu, bool descendants) 3007 { 3008 menu->InvalidateLayout(); 3009 } 3010 3011