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