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