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