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 // See if the supermenu has a cached menuwindow, 1503 // and use that one if possible. 1504 BMenuWindow* window = NULL; 1505 bool ourWindow = false; 1506 if (fSuper != NULL) { 1507 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1508 window = fSuper->_MenuWindow(); 1509 } 1510 1511 // Otherwise, create a new one 1512 // This happens for "stand alone" BPopUpMenus 1513 // (i.e. not within a BMenuField) 1514 if (window == NULL) { 1515 // Menu windows get the BMenu's handler name 1516 window = new (nothrow) BMenuWindow(Name()); 1517 ourWindow = true; 1518 } 1519 1520 if (window == NULL) 1521 return false; 1522 1523 if (window->Lock()) { 1524 bool addAborted = false; 1525 if (keyDown) 1526 addAborted = _AddDynamicItems(keyDown); 1527 1528 if (addAborted) { 1529 if (ourWindow) 1530 window->Quit(); 1531 else 1532 window->Unlock(); 1533 return false; 1534 } 1535 fAttachAborted = false; 1536 1537 window->AttachMenu(this); 1538 1539 if (ItemAt(0) != NULL) { 1540 float width, height; 1541 ItemAt(0)->GetContentSize(&width, &height); 1542 1543 window->SetSmallStep(ceilf(height)); 1544 } 1545 1546 // Menu didn't have the time to add its items: aborting... 1547 if (fAttachAborted) { 1548 window->DetachMenu(); 1549 // TODO: Probably not needed, we can just let _hide() quit the 1550 // window. 1551 if (ourWindow) 1552 window->Quit(); 1553 else 1554 window->Unlock(); 1555 return false; 1556 } 1557 1558 _UpdateWindowViewSize(true); 1559 window->Show(); 1560 1561 if (selectFirstItem) 1562 _SelectItem(ItemAt(0), false); 1563 1564 window->Unlock(); 1565 } 1566 1567 return true; 1568 } 1569 1570 1571 void 1572 BMenu::_Hide() 1573 { 1574 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 1575 if (window == NULL || !window->Lock()) 1576 return; 1577 1578 if (fSelected != NULL) 1579 _SelectItem(NULL); 1580 1581 window->Hide(); 1582 window->DetachMenu(); 1583 // we don't want to be deleted when the window is removed 1584 1585 #if USE_CACHED_MENUWINDOW 1586 if (fSuper != NULL) 1587 window->Unlock(); 1588 else 1589 #endif 1590 window->Quit(); 1591 // it's our window, quit it 1592 1593 _DeleteMenuWindow(); 1594 // Delete the menu window used by our submenus 1595 } 1596 1597 1598 // #pragma mark - mouse tracking 1599 1600 1601 const static bigtime_t kOpenSubmenuDelay = 225000; 1602 const static bigtime_t kNavigationAreaTimeout = 1000000; 1603 1604 1605 BMenuItem* 1606 BMenu::_Track(int* action, long start) 1607 { 1608 // TODO: cleanup 1609 BMenuItem* item = NULL; 1610 BRect navAreaRectAbove; 1611 BRect navAreaRectBelow; 1612 bigtime_t selectedTime = system_time(); 1613 bigtime_t navigationAreaTime = 0; 1614 1615 fState = MENU_STATE_TRACKING; 1616 fChosenItem = NULL; 1617 // we will use this for keyboard selection 1618 1619 BPoint location; 1620 uint32 buttons = 0; 1621 if (LockLooper()) { 1622 GetMouse(&location, &buttons); 1623 UnlockLooper(); 1624 } 1625 1626 bool releasedOnce = buttons == 0; 1627 while (fState != MENU_STATE_CLOSED) { 1628 if (_CustomTrackingWantsToQuit()) 1629 break; 1630 1631 if (!LockLooper()) 1632 break; 1633 1634 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 1635 BPoint screenLocation = ConvertToScreen(location); 1636 if (window->CheckForScrolling(screenLocation)) { 1637 UnlockLooper(); 1638 continue; 1639 } 1640 1641 // The order of the checks is important 1642 // to be able to handle overlapping menus: 1643 // first we check if mouse is inside a submenu, 1644 // then if the mouse is inside this menu, 1645 // then if it's over a super menu. 1646 if (_OverSubmenu(fSelected, screenLocation) 1647 || fState == MENU_STATE_KEY_TO_SUBMENU) { 1648 if (fState == MENU_STATE_TRACKING) { 1649 // not if from R.Arrow 1650 fState = MENU_STATE_TRACKING_SUBMENU; 1651 } 1652 navAreaRectAbove = BRect(); 1653 navAreaRectBelow = BRect(); 1654 1655 // Since the submenu has its own looper, 1656 // we can unlock ours. Doing so also make sure 1657 // that our window gets any update message to 1658 // redraw itself 1659 UnlockLooper(); 1660 int submenuAction = MENU_STATE_TRACKING; 1661 BMenu* submenu = fSelected->Submenu(); 1662 submenu->_SetStickyMode(_IsStickyMode()); 1663 1664 // The following call blocks until the submenu 1665 // gives control back to us, either because the mouse 1666 // pointer goes out of the submenu's bounds, or because 1667 // the user closes the menu 1668 BMenuItem* submenuItem = submenu->_Track(&submenuAction); 1669 if (submenuAction == MENU_STATE_CLOSED) { 1670 item = submenuItem; 1671 fState = MENU_STATE_CLOSED; 1672 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) { 1673 if (LockLooper()) { 1674 BMenuItem* temp = fSelected; 1675 // close the submenu: 1676 _SelectItem(NULL); 1677 // but reselect the item itself for user: 1678 _SelectItem(temp, false); 1679 UnlockLooper(); 1680 } 1681 // cancel key-nav state 1682 fState = MENU_STATE_TRACKING; 1683 } else 1684 fState = MENU_STATE_TRACKING; 1685 if (!LockLooper()) 1686 break; 1687 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) { 1688 _UpdateStateOpenSelect(item, location, navAreaRectAbove, 1689 navAreaRectBelow, selectedTime, navigationAreaTime); 1690 releasedOnce = true; 1691 } else if (_OverSuper(screenLocation) 1692 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 1693 fState = MENU_STATE_TRACKING; 1694 UnlockLooper(); 1695 break; 1696 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) { 1697 UnlockLooper(); 1698 break; 1699 } else if (fSuper == NULL 1700 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 1701 // Mouse pointer outside menu: 1702 // If there's no other submenu opened, 1703 // deselect the current selected item 1704 if (fSelected != NULL 1705 && (fSelected->Submenu() == NULL 1706 || fSelected->Submenu()->Window() == NULL)) { 1707 _SelectItem(NULL); 1708 fState = MENU_STATE_TRACKING; 1709 } 1710 1711 if (fSuper != NULL) { 1712 // Give supermenu the chance to continue tracking 1713 *action = fState; 1714 UnlockLooper(); 1715 return NULL; 1716 } 1717 } 1718 1719 UnlockLooper(); 1720 1721 if (releasedOnce) 1722 _UpdateStateClose(item, location, buttons); 1723 1724 if (fState != MENU_STATE_CLOSED) { 1725 bigtime_t snoozeAmount = 50000; 1726 1727 BPoint newLocation = location; 1728 uint32 newButtons = buttons; 1729 1730 // If user doesn't move the mouse, loop here, 1731 // so we don't interfere with keyboard menu navigation 1732 do { 1733 snooze(snoozeAmount); 1734 if (!LockLooper()) 1735 break; 1736 GetMouse(&newLocation, &newButtons, true); 1737 UnlockLooper(); 1738 } while (newLocation == location && newButtons == buttons 1739 && !(item != NULL && item->Submenu() != NULL 1740 && item->Submenu()->Window() == NULL) 1741 && fState == MENU_STATE_TRACKING); 1742 1743 if (newLocation != location || newButtons != buttons) { 1744 if (!releasedOnce && newButtons == 0 && buttons != 0) 1745 releasedOnce = true; 1746 location = newLocation; 1747 buttons = newButtons; 1748 } 1749 1750 if (releasedOnce) 1751 _UpdateStateClose(item, location, buttons); 1752 } 1753 } 1754 1755 if (action != NULL) 1756 *action = fState; 1757 1758 // keyboard Enter will set this 1759 if (fChosenItem != NULL) 1760 item = fChosenItem; 1761 else if (fSelected == NULL) { 1762 // needed to cover (rare) mouse/ESC combination 1763 item = NULL; 1764 } 1765 1766 if (fSelected != NULL && LockLooper()) { 1767 _SelectItem(NULL); 1768 UnlockLooper(); 1769 } 1770 1771 // delete the menu window recycled for all the child menus 1772 _DeleteMenuWindow(); 1773 1774 return item; 1775 } 1776 1777 1778 void 1779 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove, 1780 BRect& navAreaRectBelow) 1781 { 1782 #define NAV_AREA_THRESHOLD 8 1783 1784 // The navigation area is a region in which mouse-overs won't select 1785 // the item under the cursor. This makes it easier to navigate to 1786 // submenus, as the cursor can be moved to submenu items directly instead 1787 // of having to move it horizontally into the submenu first. The concept 1788 // is illustrated below: 1789 // 1790 // +-------+----+---------+ 1791 // | | /| | 1792 // | | /*| | 1793 // |[2]--> | /**| | 1794 // | |/[4]| | 1795 // |------------| | 1796 // | [1] | [6] | 1797 // |------------| | 1798 // | |\[5]| | 1799 // |[3]--> | \**| | 1800 // | | \*| | 1801 // | | \| | 1802 // | +----|---------+ 1803 // | | 1804 // +------------+ 1805 // 1806 // [1] Selected item, cursor position ('position') 1807 // [2] Upper navigation area rectangle ('navAreaRectAbove') 1808 // [3] Lower navigation area rectangle ('navAreaRectBelow') 1809 // [4] Upper navigation area 1810 // [5] Lower navigation area 1811 // [6] Submenu 1812 // 1813 // The rectangles are used to calculate if the cursor is in the actual 1814 // navigation area (see _UpdateStateOpenSelect()). 1815 1816 if (fSelected == NULL) 1817 return; 1818 1819 BMenu* submenu = fSelected->Submenu(); 1820 1821 if (submenu != NULL) { 1822 BRect menuBounds = ConvertToScreen(Bounds()); 1823 1824 fSelected->Submenu()->LockLooper(); 1825 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1826 fSelected->Submenu()->Bounds()); 1827 fSelected->Submenu()->UnlockLooper(); 1828 1829 if (menuBounds.left < submenuBounds.left) { 1830 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD, 1831 submenuBounds.top, menuBounds.right, 1832 position.y); 1833 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD, 1834 position.y, menuBounds.right, 1835 submenuBounds.bottom); 1836 } else { 1837 navAreaRectAbove.Set(menuBounds.left, 1838 submenuBounds.top, position.x - NAV_AREA_THRESHOLD, 1839 position.y); 1840 navAreaRectBelow.Set(menuBounds.left, 1841 position.y, position.x - NAV_AREA_THRESHOLD, 1842 submenuBounds.bottom); 1843 } 1844 } else { 1845 navAreaRectAbove = BRect(); 1846 navAreaRectBelow = BRect(); 1847 } 1848 } 1849 1850 1851 void 1852 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position, 1853 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime, 1854 bigtime_t& navigationAreaTime) 1855 { 1856 if (fState == MENU_STATE_CLOSED) 1857 return; 1858 1859 if (item != fSelected) { 1860 if (navigationAreaTime == 0) 1861 navigationAreaTime = system_time(); 1862 1863 position = ConvertToScreen(position); 1864 1865 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position); 1866 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position); 1867 1868 if (fSelected == NULL 1869 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) { 1870 _SelectItem(item, false); 1871 navAreaRectAbove = BRect(); 1872 navAreaRectBelow = BRect(); 1873 selectedTime = system_time(); 1874 navigationAreaTime = 0; 1875 return; 1876 } 1877 1878 BRect menuBounds = ConvertToScreen(Bounds()); 1879 1880 fSelected->Submenu()->LockLooper(); 1881 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1882 fSelected->Submenu()->Bounds()); 1883 fSelected->Submenu()->UnlockLooper(); 1884 1885 float xOffset; 1886 1887 // navAreaRectAbove and navAreaRectBelow have the same X 1888 // position and width, so it doesn't matter which one we use to 1889 // calculate the X offset 1890 if (menuBounds.left < submenuBounds.left) 1891 xOffset = position.x - navAreaRectAbove.left; 1892 else 1893 xOffset = navAreaRectAbove.right - position.x; 1894 1895 bool inNavArea; 1896 1897 if (inNavAreaRectAbove) { 1898 float yOffset = navAreaRectAbove.bottom - position.y; 1899 float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height(); 1900 1901 inNavArea = yOffset <= xOffset / ratio; 1902 } else { 1903 float yOffset = navAreaRectBelow.bottom - position.y; 1904 float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height(); 1905 1906 inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset 1907 / ratio); 1908 } 1909 1910 bigtime_t systime = system_time(); 1911 1912 if (!inNavArea || (navigationAreaTime > 0 && systime - 1913 navigationAreaTime > kNavigationAreaTimeout)) { 1914 // Don't delay opening of submenu if the user had 1915 // to wait for the navigation area timeout anyway 1916 _SelectItem(item, inNavArea); 1917 1918 if (inNavArea) { 1919 _UpdateNavigationArea(position, navAreaRectAbove, 1920 navAreaRectBelow); 1921 } else { 1922 navAreaRectAbove = BRect(); 1923 navAreaRectBelow = BRect(); 1924 } 1925 1926 selectedTime = system_time(); 1927 navigationAreaTime = 0; 1928 } 1929 } else if (fSelected->Submenu() != NULL && 1930 system_time() - selectedTime > kOpenSubmenuDelay) { 1931 _SelectItem(fSelected, true); 1932 1933 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) { 1934 position = ConvertToScreen(position); 1935 _UpdateNavigationArea(position, navAreaRectAbove, 1936 navAreaRectBelow); 1937 } 1938 } 1939 1940 if (fState != MENU_STATE_TRACKING) 1941 fState = MENU_STATE_TRACKING; 1942 } 1943 1944 1945 void 1946 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where, 1947 const uint32& buttons) 1948 { 1949 if (fState == MENU_STATE_CLOSED) 1950 return; 1951 1952 if (buttons != 0 && _IsStickyMode()) { 1953 if (item == NULL) { 1954 if (item != fSelected) { 1955 LockLooper(); 1956 _SelectItem(item, false); 1957 UnlockLooper(); 1958 } 1959 fState = MENU_STATE_CLOSED; 1960 } else 1961 _SetStickyMode(false); 1962 } else if (buttons == 0 && !_IsStickyMode()) { 1963 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 1964 _SetStickyMode(true); 1965 fExtraRect = NULL; 1966 // Setting this to NULL will prevent this code 1967 // to be executed next time 1968 } else { 1969 if (item != fSelected) { 1970 LockLooper(); 1971 _SelectItem(item, false); 1972 UnlockLooper(); 1973 } 1974 fState = MENU_STATE_CLOSED; 1975 } 1976 } 1977 } 1978 1979 1980 // #pragma mark - 1981 1982 1983 bool 1984 BMenu::_AddItem(BMenuItem* item, int32 index) 1985 { 1986 ASSERT(item != NULL); 1987 if (index < 0 || index > fItems.CountItems()) 1988 return false; 1989 1990 if (item->IsMarked()) 1991 _ItemMarked(item); 1992 1993 if (!fItems.AddItem(item, index)) 1994 return false; 1995 1996 // install the item on the supermenu's window 1997 // or onto our window, if we are a root menu 1998 BWindow* window = NULL; 1999 if (Superitem() != NULL) 2000 window = Superitem()->fWindow; 2001 else 2002 window = Window(); 2003 if (window != NULL) 2004 item->Install(window); 2005 2006 item->SetSuper(this); 2007 return true; 2008 } 2009 2010 2011 bool 2012 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item, 2013 bool deleteItems) 2014 { 2015 bool success = false; 2016 bool invalidateLayout = false; 2017 2018 bool locked = LockLooper(); 2019 BWindow* window = Window(); 2020 2021 // The plan is simple: If we're given a BMenuItem directly, we use it 2022 // and ignore index and count. Otherwise, we use them instead. 2023 if (item != NULL) { 2024 if (fItems.RemoveItem(item)) { 2025 if (item == fSelected && window != NULL) 2026 _SelectItem(NULL); 2027 item->Uninstall(); 2028 item->SetSuper(NULL); 2029 if (deleteItems) 2030 delete item; 2031 success = invalidateLayout = true; 2032 } 2033 } else { 2034 // We iterate backwards because it's simpler 2035 int32 i = min_c(index + count - 1, fItems.CountItems() - 1); 2036 // NOTE: the range check for "index" is done after 2037 // calculating the last index to be removed, so 2038 // that the range is not "shifted" unintentionally 2039 index = max_c(0, index); 2040 for (; i >= index; i--) { 2041 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 2042 if (item != NULL) { 2043 if (fItems.RemoveItem(item)) { 2044 if (item == fSelected && window != NULL) 2045 _SelectItem(NULL); 2046 item->Uninstall(); 2047 item->SetSuper(NULL); 2048 if (deleteItems) 2049 delete item; 2050 success = true; 2051 invalidateLayout = true; 2052 } else { 2053 // operation not entirely successful 2054 success = false; 2055 break; 2056 } 2057 } 2058 } 2059 } 2060 2061 if (invalidateLayout) { 2062 InvalidateLayout(); 2063 if (locked && window != NULL) { 2064 _LayoutItems(0); 2065 _UpdateWindowViewSize(false); 2066 Invalidate(); 2067 } 2068 } 2069 2070 if (locked) 2071 UnlockLooper(); 2072 2073 return success; 2074 } 2075 2076 2077 bool 2078 BMenu::_RelayoutIfNeeded() 2079 { 2080 if (!fUseCachedMenuLayout) { 2081 fUseCachedMenuLayout = true; 2082 _CacheFontInfo(); 2083 _LayoutItems(0); 2084 return true; 2085 } 2086 return false; 2087 } 2088 2089 2090 void 2091 BMenu::_LayoutItems(int32 index) 2092 { 2093 _CalcTriggers(); 2094 2095 float width; 2096 float height; 2097 _ComputeLayout(index, fResizeToFit, true, &width, &height); 2098 2099 if (fResizeToFit) 2100 ResizeTo(width, height); 2101 } 2102 2103 2104 BSize 2105 BMenu::_ValidatePreferredSize() 2106 { 2107 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode() 2108 != fLayoutData->lastResizingMode) { 2109 _ComputeLayout(0, true, false, NULL, NULL); 2110 ResetLayoutInvalidation(); 2111 } 2112 2113 return fLayoutData->preferred; 2114 } 2115 2116 2117 void 2118 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 2119 float* _width, float* _height) 2120 { 2121 // TODO: Take "bestFit", "moveItems", "index" into account, 2122 // Recalculate only the needed items, 2123 // not the whole layout every time 2124 2125 fLayoutData->lastResizingMode = ResizingMode(); 2126 2127 BRect frame; 2128 2129 switch (fLayout) { 2130 case B_ITEMS_IN_COLUMN: 2131 { 2132 BRect parentFrame; 2133 BRect* overrideFrame = NULL; 2134 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) { 2135 parentFrame = Supermenu()->Bounds(); 2136 overrideFrame = &parentFrame; 2137 } 2138 2139 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, frame); 2140 break; 2141 } 2142 case B_ITEMS_IN_ROW: 2143 _ComputeRowLayout(index, bestFit, moveItems, frame); 2144 break; 2145 2146 case B_ITEMS_IN_MATRIX: 2147 _ComputeMatrixLayout(frame); 2148 break; 2149 2150 default: 2151 break; 2152 } 2153 2154 // change width depending on resize mode 2155 BSize size; 2156 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2157 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL) 2158 size.width = Bounds().Width() - fPad.right; 2159 else if (Parent() != NULL) 2160 size.width = Parent()->Frame().Width() + 1; 2161 else if (Window() != NULL) 2162 size.width = Window()->Frame().Width() + 1; 2163 else 2164 size.width = Bounds().Width(); 2165 } else 2166 size.width = frame.Width(); 2167 2168 size.height = frame.Height(); 2169 2170 if (_width) 2171 *_width = size.width; 2172 2173 if (_height) 2174 *_height = size.height; 2175 2176 if (bestFit) 2177 fLayoutData->preferred = size; 2178 2179 if (moveItems) 2180 fUseCachedMenuLayout = true; 2181 } 2182 2183 2184 void 2185 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2186 BRect* overrideFrame, BRect& frame) 2187 { 2188 BFont font; 2189 GetFont(&font); 2190 bool command = false; 2191 bool control = false; 2192 bool shift = false; 2193 bool option = false; 2194 if (index > 0) 2195 frame = ItemAt(index - 1)->Frame(); 2196 else if (overrideFrame != NULL) { 2197 frame.Set(0, 0, overrideFrame->right, -1); 2198 } else 2199 frame.Set(0, 0, 0, -1); 2200 2201 for (; index < fItems.CountItems(); index++) { 2202 BMenuItem* item = ItemAt(index); 2203 2204 float width, height; 2205 item->GetContentSize(&width, &height); 2206 2207 if (item->fModifiers && item->fShortcutChar) { 2208 width += font.Size(); 2209 if (item->fModifiers & B_COMMAND_KEY) 2210 command = true; 2211 if (item->fModifiers & B_CONTROL_KEY) 2212 control = true; 2213 if (item->fModifiers & B_SHIFT_KEY) 2214 shift = true; 2215 if (item->fModifiers & B_OPTION_KEY) 2216 option = true; 2217 } 2218 2219 item->fBounds.left = 0.0f; 2220 item->fBounds.top = frame.bottom + 1.0f; 2221 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2222 + fPad.bottom; 2223 2224 if (item->fSubmenu != NULL) 2225 width += item->Frame().Height(); 2226 2227 frame.right = max_c(frame.right, width + fPad.left + fPad.right); 2228 frame.bottom = item->fBounds.bottom; 2229 } 2230 2231 if (command) 2232 frame.right += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2233 if (control) 2234 frame.right += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2235 if (option) 2236 frame.right += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2237 if (shift) 2238 frame.right += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2239 2240 if (fMaxContentWidth > 0) 2241 frame.right = min_c(frame.right, fMaxContentWidth); 2242 2243 if (moveItems) { 2244 for (int32 i = 0; i < fItems.CountItems(); i++) 2245 ItemAt(i)->fBounds.right = frame.right; 2246 } 2247 2248 frame.top = 0; 2249 frame.right = ceilf(frame.right); 2250 } 2251 2252 2253 void 2254 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2255 BRect& frame) 2256 { 2257 font_height fh; 2258 GetFontHeight(&fh); 2259 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2260 + fPad.bottom)); 2261 2262 for (int32 i = 0; i < fItems.CountItems(); i++) { 2263 BMenuItem* item = ItemAt(i); 2264 2265 float width, height; 2266 item->GetContentSize(&width, &height); 2267 2268 item->fBounds.left = frame.right; 2269 item->fBounds.top = 0.0f; 2270 item->fBounds.right = item->fBounds.left + width + fPad.left 2271 + fPad.right; 2272 2273 frame.right = item->Frame().right + 1.0f; 2274 frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom); 2275 } 2276 2277 if (moveItems) { 2278 for (int32 i = 0; i < fItems.CountItems(); i++) 2279 ItemAt(i)->fBounds.bottom = frame.bottom; 2280 } 2281 2282 if (bestFit) 2283 frame.right = ceilf(frame.right); 2284 else 2285 frame.right = Bounds().right; 2286 } 2287 2288 2289 void 2290 BMenu::_ComputeMatrixLayout(BRect &frame) 2291 { 2292 frame.Set(0, 0, 0, 0); 2293 for (int32 i = 0; i < CountItems(); i++) { 2294 BMenuItem* item = ItemAt(i); 2295 if (item != NULL) { 2296 frame.left = min_c(frame.left, item->Frame().left); 2297 frame.right = max_c(frame.right, item->Frame().right); 2298 frame.top = min_c(frame.top, item->Frame().top); 2299 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 2300 } 2301 } 2302 } 2303 2304 2305 void 2306 BMenu::LayoutInvalidated(bool descendants) 2307 { 2308 fUseCachedMenuLayout = false; 2309 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2310 } 2311 2312 2313 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2314 BPoint 2315 BMenu::ScreenLocation() 2316 { 2317 BMenu* superMenu = Supermenu(); 2318 BMenuItem* superItem = Superitem(); 2319 2320 if (superMenu == NULL || superItem == NULL) { 2321 debugger("BMenu can't determine where to draw." 2322 "Override BMenu::ScreenLocation() to determine location."); 2323 } 2324 2325 BPoint point; 2326 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2327 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2328 else 2329 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2330 2331 superMenu->ConvertToScreen(&point); 2332 2333 return point; 2334 } 2335 2336 2337 BRect 2338 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2339 { 2340 // TODO: Improve me 2341 BRect bounds = Bounds(); 2342 BRect frame = bounds.OffsetToCopy(where); 2343 2344 BScreen screen(Window()); 2345 BRect screenFrame = screen.Frame(); 2346 2347 BMenu* superMenu = Supermenu(); 2348 BMenuItem* superItem = Superitem(); 2349 2350 // TODO: Horrible hack: 2351 // When added to a BMenuField, a BPopUpMenu is the child of 2352 // a _BMCMenuBar_ to "fake" the menu hierarchy 2353 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2354 bool scroll = false; 2355 if (superMenu == NULL || superItem == NULL || inMenuField) { 2356 // just move the window on screen 2357 2358 if (frame.bottom > screenFrame.bottom) 2359 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2360 else if (frame.top < screenFrame.top) 2361 frame.OffsetBy(0, -frame.top); 2362 2363 if (frame.right > screenFrame.right) 2364 frame.OffsetBy(screenFrame.right - frame.right, 0); 2365 else if (frame.left < screenFrame.left) 2366 frame.OffsetBy(-frame.left, 0); 2367 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2368 if (frame.right > screenFrame.right) 2369 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2370 2371 if (frame.left < 0) 2372 frame.OffsetBy(-frame.left + 6, 0); 2373 2374 if (frame.bottom > screenFrame.bottom) 2375 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2376 } else { 2377 if (frame.bottom > screenFrame.bottom) { 2378 if (scrollOn != NULL && superMenu != NULL 2379 && dynamic_cast<BMenuBar*>(superMenu) != NULL 2380 && frame.top < (screenFrame.bottom - 80)) { 2381 scroll = true; 2382 } else { 2383 frame.OffsetBy(0, -superItem->Frame().Height() 2384 - frame.Height() - 3); 2385 } 2386 } 2387 2388 if (frame.right > screenFrame.right) 2389 frame.OffsetBy(screenFrame.right - frame.right, 0); 2390 } 2391 2392 if (!scroll) { 2393 // basically, if this returns false, it means 2394 // that the menu frame won't fit completely inside the screen 2395 // TODO: Scrolling will currently only work up/down, 2396 // not left/right 2397 scroll = screenFrame.Height() < frame.Height(); 2398 } 2399 2400 if (scrollOn != NULL) 2401 *scrollOn = scroll; 2402 2403 return frame; 2404 } 2405 2406 2407 void 2408 BMenu::_DrawItems(BRect updateRect) 2409 { 2410 int32 itemCount = fItems.CountItems(); 2411 for (int32 i = 0; i < itemCount; i++) { 2412 BMenuItem* item = ItemAt(i); 2413 if (item->Frame().Intersects(updateRect)) 2414 item->Draw(); 2415 } 2416 } 2417 2418 2419 int 2420 BMenu::_State(BMenuItem** item) const 2421 { 2422 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2423 return fState; 2424 2425 if (fSelected != NULL && fSelected->Submenu() != NULL) 2426 return fSelected->Submenu()->_State(item); 2427 2428 return fState; 2429 } 2430 2431 2432 void 2433 BMenu::_InvokeItem(BMenuItem* item, bool now) 2434 { 2435 if (!item->IsEnabled()) 2436 return; 2437 2438 // Do the "selected" animation 2439 // TODO: Doesn't work. This is supposed to highlight 2440 // and dehighlight the item, works on beos but not on haiku. 2441 if (!item->Submenu() && LockLooper()) { 2442 snooze(50000); 2443 item->Select(true); 2444 Window()->UpdateIfNeeded(); 2445 snooze(50000); 2446 item->Select(false); 2447 Window()->UpdateIfNeeded(); 2448 snooze(50000); 2449 item->Select(true); 2450 Window()->UpdateIfNeeded(); 2451 snooze(50000); 2452 item->Select(false); 2453 Window()->UpdateIfNeeded(); 2454 UnlockLooper(); 2455 } 2456 2457 // Lock the root menu window before calling BMenuItem::Invoke() 2458 BMenu* parent = this; 2459 BMenu* rootMenu = NULL; 2460 do { 2461 rootMenu = parent; 2462 parent = rootMenu->Supermenu(); 2463 } while (parent != NULL); 2464 2465 if (rootMenu->LockLooper()) { 2466 item->Invoke(); 2467 rootMenu->UnlockLooper(); 2468 } 2469 } 2470 2471 2472 bool 2473 BMenu::_OverSuper(BPoint location) 2474 { 2475 if (!Supermenu()) 2476 return false; 2477 2478 return fSuperbounds.Contains(location); 2479 } 2480 2481 2482 bool 2483 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2484 { 2485 if (item == NULL) 2486 return false; 2487 2488 BMenu* subMenu = item->Submenu(); 2489 if (subMenu == NULL || subMenu->Window() == NULL) 2490 return false; 2491 2492 // assume that loc is in screen coordinates 2493 if (subMenu->Window()->Frame().Contains(loc)) 2494 return true; 2495 2496 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2497 } 2498 2499 2500 BMenuWindow* 2501 BMenu::_MenuWindow() 2502 { 2503 #if USE_CACHED_MENUWINDOW 2504 if (fCachedMenuWindow == NULL) { 2505 char windowName[64]; 2506 snprintf(windowName, 64, "%s cached menu", Name()); 2507 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2508 } 2509 #endif 2510 return fCachedMenuWindow; 2511 } 2512 2513 2514 void 2515 BMenu::_DeleteMenuWindow() 2516 { 2517 if (fCachedMenuWindow != NULL) { 2518 fCachedMenuWindow->Lock(); 2519 fCachedMenuWindow->Quit(); 2520 fCachedMenuWindow = NULL; 2521 } 2522 } 2523 2524 2525 BMenuItem* 2526 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2527 { 2528 // TODO: Take "slop" into account ? 2529 2530 // if the point doesn't lie within the menu's 2531 // bounds, bail out immediately 2532 if (!Bounds().Contains(where)) 2533 return NULL; 2534 2535 int32 itemCount = CountItems(); 2536 for (int32 i = 0; i < itemCount; i++) { 2537 BMenuItem* item = ItemAt(i); 2538 if (item->Frame().Contains(where) 2539 && dynamic_cast<BSeparatorItem*>(item) == NULL) { 2540 return item; 2541 } 2542 } 2543 2544 return NULL; 2545 } 2546 2547 2548 BRect 2549 BMenu::_Superbounds() const 2550 { 2551 return fSuperbounds; 2552 } 2553 2554 2555 void 2556 BMenu::_CacheFontInfo() 2557 { 2558 font_height fh; 2559 GetFontHeight(&fh); 2560 fAscent = fh.ascent; 2561 fDescent = fh.descent; 2562 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2563 } 2564 2565 2566 void 2567 BMenu::_ItemMarked(BMenuItem* item) 2568 { 2569 if (IsRadioMode()) { 2570 for (int32 i = 0; i < CountItems(); i++) { 2571 if (ItemAt(i) != item) 2572 ItemAt(i)->SetMarked(false); 2573 } 2574 InvalidateLayout(); 2575 } 2576 2577 if (IsLabelFromMarked() && Superitem()) 2578 Superitem()->SetLabel(item->Label()); 2579 } 2580 2581 2582 void 2583 BMenu::_Install(BWindow* target) 2584 { 2585 for (int32 i = 0; i < CountItems(); i++) 2586 ItemAt(i)->Install(target); 2587 } 2588 2589 2590 void 2591 BMenu::_Uninstall() 2592 { 2593 for (int32 i = 0; i < CountItems(); i++) 2594 ItemAt(i)->Uninstall(); 2595 } 2596 2597 2598 void 2599 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem, 2600 bool keyDown) 2601 { 2602 // Avoid deselecting and then reselecting the same item 2603 // which would cause flickering 2604 if (item != fSelected) { 2605 if (fSelected != NULL) { 2606 fSelected->Select(false); 2607 BMenu* subMenu = fSelected->Submenu(); 2608 if (subMenu != NULL && subMenu->Window() != NULL) 2609 subMenu->_Hide(); 2610 } 2611 2612 fSelected = item; 2613 if (fSelected != NULL) 2614 fSelected->Select(true); 2615 } 2616 2617 if (fSelected != NULL && showSubmenu) { 2618 BMenu* subMenu = fSelected->Submenu(); 2619 if (subMenu != NULL && subMenu->Window() == NULL) { 2620 if (!subMenu->_Show(selectFirstItem, keyDown)) { 2621 // something went wrong, deselect the item 2622 fSelected->Select(false); 2623 fSelected = NULL; 2624 } 2625 } 2626 } 2627 } 2628 2629 2630 bool 2631 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 2632 { 2633 if (CountItems() == 0) // cannot select next item in an empty menu 2634 return false; 2635 2636 BMenuItem* nextItem = _NextItem(item, forward); 2637 if (nextItem == NULL) 2638 return false; 2639 2640 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL); 2641 2642 if (LockLooper()) { 2643 be_app->ObscureCursor(); 2644 UnlockLooper(); 2645 } 2646 2647 return true; 2648 } 2649 2650 2651 BMenuItem* 2652 BMenu::_NextItem(BMenuItem* item, bool forward) const 2653 { 2654 const int32 numItems = fItems.CountItems(); 2655 if (numItems == 0) 2656 return NULL; 2657 2658 int32 index = fItems.IndexOf(item); 2659 int32 loopCount = numItems; 2660 while (--loopCount) { 2661 // Cycle through menu items in the given direction... 2662 if (forward) 2663 index++; 2664 else 2665 index--; 2666 2667 // ... wrap around... 2668 if (index < 0) 2669 index = numItems - 1; 2670 else if (index >= numItems) 2671 index = 0; 2672 2673 // ... and return the first suitable item found. 2674 BMenuItem* nextItem = ItemAt(index); 2675 if (nextItem->IsEnabled()) 2676 return nextItem; 2677 } 2678 2679 // If no other suitable item was found, return NULL. 2680 return NULL; 2681 } 2682 2683 2684 void 2685 BMenu::_SetIgnoreHidden(bool on) 2686 { 2687 fIgnoreHidden = on; 2688 } 2689 2690 2691 void 2692 BMenu::_SetStickyMode(bool on) 2693 { 2694 if (fStickyMode == on) 2695 return; 2696 2697 fStickyMode = on; 2698 2699 if (fSuper != NULL) { 2700 // propagate the status to the super menu 2701 fSuper->_SetStickyMode(on); 2702 } else { 2703 // TODO: Ugly hack, but it needs to be done in this method 2704 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 2705 if (on && menuBar != NULL && menuBar->LockLooper()) { 2706 // If we are switching to sticky mode, 2707 // steal the focus from the current focus view 2708 // (needed to handle keyboard navigation) 2709 menuBar->_StealFocus(); 2710 menuBar->UnlockLooper(); 2711 } 2712 } 2713 } 2714 2715 2716 bool 2717 BMenu::_IsStickyMode() const 2718 { 2719 return fStickyMode; 2720 } 2721 2722 2723 void 2724 BMenu::_GetShiftKey(uint32 &value) const 2725 { 2726 // TODO: Move into init_interface_kit(). 2727 // Currently we can't do that, as get_modifier_key() blocks forever 2728 // when called on input_server initialization, since it tries 2729 // to send a synchronous message to itself (input_server is 2730 // a BApplication) 2731 2732 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK) 2733 value = 0x4b; 2734 } 2735 2736 2737 void 2738 BMenu::_GetControlKey(uint32 &value) const 2739 { 2740 // TODO: Move into init_interface_kit(). 2741 // Currently we can't do that, as get_modifier_key() blocks forever 2742 // when called on input_server initialization, since it tries 2743 // to send a synchronous message to itself (input_server is 2744 // a BApplication) 2745 2746 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK) 2747 value = 0x5c; 2748 } 2749 2750 2751 void 2752 BMenu::_GetCommandKey(uint32 &value) const 2753 { 2754 // TODO: Move into init_interface_kit(). 2755 // Currently we can't do that, as get_modifier_key() blocks forever 2756 // when called on input_server initialization, since it tries 2757 // to send a synchronous message to itself (input_server is 2758 // a BApplication) 2759 2760 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK) 2761 value = 0x66; 2762 } 2763 2764 2765 void 2766 BMenu::_GetOptionKey(uint32 &value) const 2767 { 2768 // TODO: Move into init_interface_kit(). 2769 // Currently we can't do that, as get_modifier_key() blocks forever 2770 // when called on input_server initialization, since it tries 2771 // to send a synchronous message to itself (input_server is 2772 // a BApplication) 2773 2774 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 2775 value = 0x5d; 2776 } 2777 2778 2779 void 2780 BMenu::_GetMenuKey(uint32 &value) const 2781 { 2782 // TODO: Move into init_interface_kit(). 2783 // Currently we can't do that, as get_modifier_key() blocks forever 2784 // when called on input_server initialization, since it tries 2785 // to send a synchronous message to itself (input_server is 2786 // a BApplication) 2787 2788 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 2789 value = 0x68; 2790 } 2791 2792 2793 void 2794 BMenu::_CalcTriggers() 2795 { 2796 BPrivate::TriggerList triggerList; 2797 2798 // Gathers the existing triggers set by the user 2799 for (int32 i = 0; i < CountItems(); i++) { 2800 char trigger = ItemAt(i)->Trigger(); 2801 if (trigger != 0) 2802 triggerList.AddTrigger(trigger); 2803 } 2804 2805 // Set triggers for items which don't have one yet 2806 for (int32 i = 0; i < CountItems(); i++) { 2807 BMenuItem* item = ItemAt(i); 2808 if (item->Trigger() == 0) { 2809 uint32 trigger; 2810 int32 index; 2811 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 2812 item->SetAutomaticTrigger(index, trigger); 2813 } 2814 } 2815 } 2816 2817 2818 bool 2819 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 2820 BPrivate::TriggerList& triggers) 2821 { 2822 if (title == NULL) 2823 return false; 2824 2825 uint32 c; 2826 2827 // two runs: first we look out for uppercase letters 2828 // TODO: support Unicode characters correctly! 2829 for (uint32 i = 0; (c = title[i]) != '\0'; i++) { 2830 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) { 2831 index = i; 2832 trigger = tolower(c); 2833 return triggers.AddTrigger(c); 2834 } 2835 } 2836 2837 // then, if we still haven't found anything, we accept them all 2838 index = 0; 2839 while ((c = UTF8ToCharCode(&title)) != 0) { 2840 if (!isspace(c) && !triggers.HasTrigger(c)) { 2841 trigger = tolower(c); 2842 return triggers.AddTrigger(c); 2843 } 2844 2845 index++; 2846 } 2847 2848 return false; 2849 } 2850 2851 2852 void 2853 BMenu::_UpdateWindowViewSize(const bool &move) 2854 { 2855 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 2856 if (window == NULL) 2857 return; 2858 2859 if (dynamic_cast<BMenuBar*>(this) != NULL) 2860 return; 2861 2862 if (!fResizeToFit) 2863 return; 2864 2865 bool scroll = false; 2866 const BPoint screenLocation = move ? ScreenLocation() 2867 : window->Frame().LeftTop(); 2868 BRect frame = _CalcFrame(screenLocation, &scroll); 2869 ResizeTo(frame.Width(), frame.Height()); 2870 2871 if (fItems.CountItems() > 0) { 2872 if (!scroll) { 2873 window->ResizeTo(Bounds().Width(), Bounds().Height()); 2874 } else { 2875 BScreen screen(window); 2876 2877 // If we need scrolling, resize the window to fit the screen and 2878 // attach scrollers to our cached BMenuWindow. 2879 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) { 2880 window->ResizeTo(Bounds().Width(), screen.Frame().Height()); 2881 frame.top = 0; 2882 } else { 2883 // Or, in case our parent was a BMenuBar enable scrolling with 2884 // normal size. 2885 window->ResizeTo(Bounds().Width(), 2886 screen.Frame().bottom - frame.top); 2887 } 2888 2889 if (fLayout == B_ITEMS_IN_COLUMN) { 2890 // we currently only support scrolling for B_ITEMS_IN_COLUMN 2891 window->AttachScrollers(); 2892 2893 BMenuItem* selectedItem = FindMarked(); 2894 if (selectedItem != NULL) { 2895 // scroll to the selected item 2896 if (Supermenu() == NULL) { 2897 window->TryScrollTo(selectedItem->Frame().top); 2898 } else { 2899 BPoint point = selectedItem->Frame().LeftTop(); 2900 BPoint superPoint = Superitem()->Frame().LeftTop(); 2901 Supermenu()->ConvertToScreen(&superPoint); 2902 ConvertToScreen(&point); 2903 window->TryScrollTo(point.y - superPoint.y); 2904 } 2905 } 2906 } 2907 } 2908 } else { 2909 _CacheFontInfo(); 2910 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 2911 + fPad.left + fPad.right, 2912 fFontHeight + fPad.top + fPad.bottom); 2913 } 2914 2915 if (move) 2916 window->MoveTo(frame.LeftTop()); 2917 } 2918 2919 2920 bool 2921 BMenu::_AddDynamicItems(bool keyDown) 2922 { 2923 bool addAborted = false; 2924 if (AddDynamicItem(B_INITIAL_ADD)) { 2925 BMenuItem* superItem = Superitem(); 2926 BMenu* superMenu = Supermenu(); 2927 do { 2928 if (superMenu != NULL 2929 && !superMenu->_OkToProceed(superItem, keyDown)) { 2930 AddDynamicItem(B_ABORT); 2931 addAborted = true; 2932 break; 2933 } 2934 } while (AddDynamicItem(B_PROCESSING)); 2935 } 2936 2937 return addAborted; 2938 } 2939 2940 2941 bool 2942 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 2943 { 2944 BPoint where; 2945 uint32 buttons; 2946 GetMouse(&where, &buttons, false); 2947 bool stickyMode = _IsStickyMode(); 2948 // Quit if user clicks the mouse button in sticky mode 2949 // or releases the mouse button in nonsticky mode 2950 // or moves the pointer over another item 2951 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2952 // BeOS seems to do something similar. This could also be a bug in 2953 // Deskbar, though. 2954 if ((buttons != 0 && stickyMode) 2955 || ((dynamic_cast<BMenuBar*>(this) == NULL 2956 && (buttons == 0 && !stickyMode)) 2957 || ((_HitTestItems(where) != item) && !keyDown))) { 2958 return false; 2959 } 2960 2961 return true; 2962 } 2963 2964 2965 bool 2966 BMenu::_CustomTrackingWantsToQuit() 2967 { 2968 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 2969 && fExtraMenuData->trackingState != NULL) { 2970 return fExtraMenuData->trackingHook(this, 2971 fExtraMenuData->trackingState); 2972 } 2973 2974 return false; 2975 } 2976 2977 2978 void 2979 BMenu::_QuitTracking(bool onlyThis) 2980 { 2981 _SelectItem(NULL); 2982 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 2983 menuBar->_RestoreFocus(); 2984 2985 fState = MENU_STATE_CLOSED; 2986 2987 if (!onlyThis) { 2988 // Close the whole menu hierarchy 2989 if (Supermenu() != NULL) 2990 Supermenu()->fState = MENU_STATE_CLOSED; 2991 2992 if (_IsStickyMode()) 2993 _SetStickyMode(false); 2994 2995 if (LockLooper()) { 2996 be_app->ShowCursor(); 2997 UnlockLooper(); 2998 } 2999 } 3000 3001 _Hide(); 3002 } 3003 3004 3005 // #pragma mark - 3006 3007 3008 // TODO: Maybe the following two methods would fit better into 3009 // InterfaceDefs.cpp 3010 // In R5, they do all the work client side, we let the app_server handle the 3011 // details. 3012 status_t 3013 set_menu_info(menu_info* info) 3014 { 3015 if (!info) 3016 return B_BAD_VALUE; 3017 3018 BPrivate::AppServerLink link; 3019 link.StartMessage(AS_SET_MENU_INFO); 3020 link.Attach<menu_info>(*info); 3021 3022 status_t status = B_ERROR; 3023 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3024 BMenu::sMenuInfo = *info; 3025 // Update also the local copy, in case anyone relies on it 3026 3027 return status; 3028 } 3029 3030 3031 status_t 3032 get_menu_info(menu_info* info) 3033 { 3034 if (!info) 3035 return B_BAD_VALUE; 3036 3037 BPrivate::AppServerLink link; 3038 link.StartMessage(AS_GET_MENU_INFO); 3039 3040 status_t status = B_ERROR; 3041 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3042 link.Read<menu_info>(info); 3043 3044 return status; 3045 } 3046 3047 3048 extern "C" void 3049 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3050 BMenu* menu, bool descendants) 3051 { 3052 menu->InvalidateLayout(); 3053 } 3054