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