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 // When the menu is modified while it's open, we get here in a 2144 // situation where trying to lock the looper would deadlock 2145 // (the window is locked waiting for the menu to terminate). 2146 // In that case, just give up on getting the supermenu bounds 2147 // and keep the menu at the current width and position. 2148 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) { 2149 parentFrame = Supermenu()->Bounds(); 2150 Supermenu()->UnlockLooper(); 2151 overrideFrame = &parentFrame; 2152 } 2153 } 2154 2155 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, 2156 frame); 2157 break; 2158 } 2159 2160 case B_ITEMS_IN_ROW: 2161 _ComputeRowLayout(index, bestFit, moveItems, frame); 2162 break; 2163 2164 case B_ITEMS_IN_MATRIX: 2165 _ComputeMatrixLayout(frame); 2166 break; 2167 } 2168 2169 // change width depending on resize mode 2170 BSize size; 2171 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2172 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL) 2173 size.width = Bounds().Width() - fPad.right; 2174 else if (Parent() != NULL) 2175 size.width = Parent()->Frame().Width() + 1; 2176 else if (Window() != NULL) 2177 size.width = Window()->Frame().Width() + 1; 2178 else 2179 size.width = Bounds().Width(); 2180 } else 2181 size.width = frame.Width(); 2182 2183 size.height = frame.Height(); 2184 2185 if (_width) 2186 *_width = size.width; 2187 2188 if (_height) 2189 *_height = size.height; 2190 2191 if (bestFit) 2192 fLayoutData->preferred = size; 2193 2194 if (moveItems) 2195 fUseCachedMenuLayout = true; 2196 } 2197 2198 2199 void 2200 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2201 BRect* overrideFrame, BRect& frame) 2202 { 2203 bool command = false; 2204 bool control = false; 2205 bool shift = false; 2206 bool option = false; 2207 2208 if (index > 0) 2209 frame = ItemAt(index - 1)->Frame(); 2210 else if (overrideFrame != NULL) 2211 frame.Set(0, 0, overrideFrame->right, -1); 2212 else 2213 frame.Set(0, 0, 0, -1); 2214 2215 BFont font; 2216 GetFont(&font); 2217 2218 for (; index < fItems.CountItems(); index++) { 2219 BMenuItem* item = ItemAt(index); 2220 2221 float width; 2222 float height; 2223 item->GetContentSize(&width, &height); 2224 2225 if (item->fModifiers && item->fShortcutChar) { 2226 width += font.Size(); 2227 if ((item->fModifiers & B_COMMAND_KEY) != 0) 2228 command = true; 2229 2230 if ((item->fModifiers & B_CONTROL_KEY) != 0) 2231 control = true; 2232 2233 if ((item->fModifiers & B_SHIFT_KEY) != 0) 2234 shift = true; 2235 2236 if ((item->fModifiers & B_OPTION_KEY) != 0) 2237 option = true; 2238 } 2239 2240 item->fBounds.left = 0.0f; 2241 item->fBounds.top = frame.bottom + 1.0f; 2242 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2243 + fPad.bottom; 2244 2245 if (item->fSubmenu != NULL) 2246 width += item->Frame().Height(); 2247 2248 frame.right = std::max(frame.right, width + fPad.left + fPad.right); 2249 frame.bottom = item->fBounds.bottom; 2250 } 2251 2252 if (command) { 2253 frame.right 2254 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2255 } 2256 if (control) { 2257 frame.right 2258 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2259 } 2260 if (option) { 2261 frame.right 2262 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2263 } 2264 if (shift) { 2265 frame.right 2266 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2267 } 2268 2269 if (fMaxContentWidth > 0) 2270 frame.right = std::min(frame.right, fMaxContentWidth); 2271 2272 if (moveItems) { 2273 for (int32 i = 0; i < fItems.CountItems(); i++) 2274 ItemAt(i)->fBounds.right = frame.right; 2275 } 2276 2277 frame.top = 0; 2278 frame.right = ceilf(frame.right); 2279 } 2280 2281 2282 void 2283 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2284 BRect& frame) 2285 { 2286 font_height fh; 2287 GetFontHeight(&fh); 2288 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2289 + fPad.bottom)); 2290 2291 for (int32 i = 0; i < fItems.CountItems(); i++) { 2292 BMenuItem* item = ItemAt(i); 2293 2294 float width, height; 2295 item->GetContentSize(&width, &height); 2296 2297 item->fBounds.left = frame.right; 2298 item->fBounds.top = 0.0f; 2299 item->fBounds.right = item->fBounds.left + width + fPad.left 2300 + fPad.right; 2301 2302 frame.right = item->Frame().right + 1.0f; 2303 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom); 2304 } 2305 2306 if (moveItems) { 2307 for (int32 i = 0; i < fItems.CountItems(); i++) 2308 ItemAt(i)->fBounds.bottom = frame.bottom; 2309 } 2310 2311 if (bestFit) 2312 frame.right = ceilf(frame.right); 2313 else 2314 frame.right = Bounds().right; 2315 } 2316 2317 2318 void 2319 BMenu::_ComputeMatrixLayout(BRect &frame) 2320 { 2321 frame.Set(0, 0, 0, 0); 2322 for (int32 i = 0; i < CountItems(); i++) { 2323 BMenuItem* item = ItemAt(i); 2324 if (item != NULL) { 2325 frame.left = std::min(frame.left, item->Frame().left); 2326 frame.right = std::max(frame.right, item->Frame().right); 2327 frame.top = std::min(frame.top, item->Frame().top); 2328 frame.bottom = std::max(frame.bottom, item->Frame().bottom); 2329 } 2330 } 2331 } 2332 2333 2334 void 2335 BMenu::LayoutInvalidated(bool descendants) 2336 { 2337 fUseCachedMenuLayout = false; 2338 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2339 } 2340 2341 2342 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2343 BPoint 2344 BMenu::ScreenLocation() 2345 { 2346 BMenu* superMenu = Supermenu(); 2347 BMenuItem* superItem = Superitem(); 2348 2349 if (superMenu == NULL || superItem == NULL) { 2350 debugger("BMenu can't determine where to draw." 2351 "Override BMenu::ScreenLocation() to determine location."); 2352 } 2353 2354 BPoint point; 2355 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2356 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2357 else 2358 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2359 2360 superMenu->ConvertToScreen(&point); 2361 2362 return point; 2363 } 2364 2365 2366 BRect 2367 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2368 { 2369 // TODO: Improve me 2370 BRect bounds = Bounds(); 2371 BRect frame = bounds.OffsetToCopy(where); 2372 2373 BScreen screen(Window()); 2374 BRect screenFrame = screen.Frame(); 2375 2376 BMenu* superMenu = Supermenu(); 2377 BMenuItem* superItem = Superitem(); 2378 2379 // TODO: Horrible hack: 2380 // When added to a BMenuField, a BPopUpMenu is the child of 2381 // a _BMCMenuBar_ to "fake" the menu hierarchy 2382 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2383 bool scroll = false; 2384 if (superMenu == NULL || superItem == NULL || inMenuField) { 2385 // just move the window on screen 2386 2387 if (frame.bottom > screenFrame.bottom) 2388 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2389 else if (frame.top < screenFrame.top) 2390 frame.OffsetBy(0, -frame.top); 2391 2392 if (frame.right > screenFrame.right) 2393 frame.OffsetBy(screenFrame.right - frame.right, 0); 2394 else if (frame.left < screenFrame.left) 2395 frame.OffsetBy(-frame.left, 0); 2396 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2397 if (frame.right > screenFrame.right) 2398 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2399 2400 if (frame.left < 0) 2401 frame.OffsetBy(-frame.left + 6, 0); 2402 2403 if (frame.bottom > screenFrame.bottom) 2404 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2405 } else { 2406 if (frame.bottom > screenFrame.bottom) { 2407 if (scrollOn != NULL && superMenu != NULL 2408 && dynamic_cast<BMenuBar*>(superMenu) != NULL 2409 && frame.top < (screenFrame.bottom - 80)) { 2410 scroll = true; 2411 } else { 2412 frame.OffsetBy(0, -superItem->Frame().Height() 2413 - frame.Height() - 3); 2414 } 2415 } 2416 2417 if (frame.right > screenFrame.right) 2418 frame.OffsetBy(screenFrame.right - frame.right, 0); 2419 } 2420 2421 if (!scroll) { 2422 // basically, if this returns false, it means 2423 // that the menu frame won't fit completely inside the screen 2424 // TODO: Scrolling will currently only work up/down, 2425 // not left/right 2426 scroll = screenFrame.Height() < frame.Height(); 2427 } 2428 2429 if (scrollOn != NULL) 2430 *scrollOn = scroll; 2431 2432 return frame; 2433 } 2434 2435 2436 void 2437 BMenu::_DrawItems(BRect updateRect) 2438 { 2439 int32 itemCount = fItems.CountItems(); 2440 for (int32 i = 0; i < itemCount; i++) { 2441 BMenuItem* item = ItemAt(i); 2442 if (item->Frame().Intersects(updateRect)) 2443 item->Draw(); 2444 } 2445 } 2446 2447 2448 int 2449 BMenu::_State(BMenuItem** item) const 2450 { 2451 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2452 return fState; 2453 2454 if (fSelected != NULL && fSelected->Submenu() != NULL) 2455 return fSelected->Submenu()->_State(item); 2456 2457 return fState; 2458 } 2459 2460 2461 void 2462 BMenu::_InvokeItem(BMenuItem* item, bool now) 2463 { 2464 if (!item->IsEnabled()) 2465 return; 2466 2467 // Do the "selected" animation 2468 // TODO: Doesn't work. This is supposed to highlight 2469 // and dehighlight the item, works on beos but not on haiku. 2470 if (!item->Submenu() && LockLooper()) { 2471 snooze(50000); 2472 item->Select(true); 2473 Window()->UpdateIfNeeded(); 2474 snooze(50000); 2475 item->Select(false); 2476 Window()->UpdateIfNeeded(); 2477 snooze(50000); 2478 item->Select(true); 2479 Window()->UpdateIfNeeded(); 2480 snooze(50000); 2481 item->Select(false); 2482 Window()->UpdateIfNeeded(); 2483 UnlockLooper(); 2484 } 2485 2486 // Lock the root menu window before calling BMenuItem::Invoke() 2487 BMenu* parent = this; 2488 BMenu* rootMenu = NULL; 2489 do { 2490 rootMenu = parent; 2491 parent = rootMenu->Supermenu(); 2492 } while (parent != NULL); 2493 2494 if (rootMenu->LockLooper()) { 2495 item->Invoke(); 2496 rootMenu->UnlockLooper(); 2497 } 2498 } 2499 2500 2501 bool 2502 BMenu::_OverSuper(BPoint location) 2503 { 2504 if (!Supermenu()) 2505 return false; 2506 2507 return fSuperbounds.Contains(location); 2508 } 2509 2510 2511 bool 2512 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2513 { 2514 if (item == NULL) 2515 return false; 2516 2517 BMenu* subMenu = item->Submenu(); 2518 if (subMenu == NULL || subMenu->Window() == NULL) 2519 return false; 2520 2521 // assume that loc is in screen coordinates 2522 if (subMenu->Window()->Frame().Contains(loc)) 2523 return true; 2524 2525 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2526 } 2527 2528 2529 BMenuWindow* 2530 BMenu::_MenuWindow() 2531 { 2532 #if USE_CACHED_MENUWINDOW 2533 if (fCachedMenuWindow == NULL) { 2534 char windowName[64]; 2535 snprintf(windowName, 64, "%s cached menu", Name()); 2536 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2537 } 2538 #endif 2539 return fCachedMenuWindow; 2540 } 2541 2542 2543 void 2544 BMenu::_DeleteMenuWindow() 2545 { 2546 if (fCachedMenuWindow != NULL) { 2547 fCachedMenuWindow->Lock(); 2548 fCachedMenuWindow->Quit(); 2549 fCachedMenuWindow = NULL; 2550 } 2551 } 2552 2553 2554 BMenuItem* 2555 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2556 { 2557 // TODO: Take "slop" into account ? 2558 2559 // if the point doesn't lie within the menu's 2560 // bounds, bail out immediately 2561 if (!Bounds().Contains(where)) 2562 return NULL; 2563 2564 int32 itemCount = CountItems(); 2565 for (int32 i = 0; i < itemCount; i++) { 2566 BMenuItem* item = ItemAt(i); 2567 if (item->Frame().Contains(where) 2568 && dynamic_cast<BSeparatorItem*>(item) == NULL) { 2569 return item; 2570 } 2571 } 2572 2573 return NULL; 2574 } 2575 2576 2577 BRect 2578 BMenu::_Superbounds() const 2579 { 2580 return fSuperbounds; 2581 } 2582 2583 2584 void 2585 BMenu::_CacheFontInfo() 2586 { 2587 font_height fh; 2588 GetFontHeight(&fh); 2589 fAscent = fh.ascent; 2590 fDescent = fh.descent; 2591 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2592 } 2593 2594 2595 void 2596 BMenu::_ItemMarked(BMenuItem* item) 2597 { 2598 if (IsRadioMode()) { 2599 for (int32 i = 0; i < CountItems(); i++) { 2600 if (ItemAt(i) != item) 2601 ItemAt(i)->SetMarked(false); 2602 } 2603 } 2604 2605 if (IsLabelFromMarked() && Superitem()) 2606 Superitem()->SetLabel(item->Label()); 2607 } 2608 2609 2610 void 2611 BMenu::_Install(BWindow* target) 2612 { 2613 for (int32 i = 0; i < CountItems(); i++) 2614 ItemAt(i)->Install(target); 2615 } 2616 2617 2618 void 2619 BMenu::_Uninstall() 2620 { 2621 for (int32 i = 0; i < CountItems(); i++) 2622 ItemAt(i)->Uninstall(); 2623 } 2624 2625 2626 void 2627 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem, 2628 bool keyDown) 2629 { 2630 // Avoid deselecting and then reselecting the same item 2631 // which would cause flickering 2632 if (item != fSelected) { 2633 if (fSelected != NULL) { 2634 fSelected->Select(false); 2635 BMenu* subMenu = fSelected->Submenu(); 2636 if (subMenu != NULL && subMenu->Window() != NULL) 2637 subMenu->_Hide(); 2638 } 2639 2640 fSelected = item; 2641 if (fSelected != NULL) 2642 fSelected->Select(true); 2643 } 2644 2645 if (fSelected != NULL && showSubmenu) { 2646 BMenu* subMenu = fSelected->Submenu(); 2647 if (subMenu != NULL && subMenu->Window() == NULL) { 2648 if (!subMenu->_Show(selectFirstItem, keyDown)) { 2649 // something went wrong, deselect the item 2650 fSelected->Select(false); 2651 fSelected = NULL; 2652 } 2653 } 2654 } 2655 } 2656 2657 2658 bool 2659 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 2660 { 2661 if (CountItems() == 0) // cannot select next item in an empty menu 2662 return false; 2663 2664 BMenuItem* nextItem = _NextItem(item, forward); 2665 if (nextItem == NULL) 2666 return false; 2667 2668 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL); 2669 2670 if (LockLooper()) { 2671 be_app->ObscureCursor(); 2672 UnlockLooper(); 2673 } 2674 2675 return true; 2676 } 2677 2678 2679 BMenuItem* 2680 BMenu::_NextItem(BMenuItem* item, bool forward) const 2681 { 2682 const int32 numItems = fItems.CountItems(); 2683 if (numItems == 0) 2684 return NULL; 2685 2686 int32 index = fItems.IndexOf(item); 2687 int32 loopCount = numItems; 2688 while (--loopCount) { 2689 // Cycle through menu items in the given direction... 2690 if (forward) 2691 index++; 2692 else 2693 index--; 2694 2695 // ... wrap around... 2696 if (index < 0) 2697 index = numItems - 1; 2698 else if (index >= numItems) 2699 index = 0; 2700 2701 // ... and return the first suitable item found. 2702 BMenuItem* nextItem = ItemAt(index); 2703 if (nextItem->IsEnabled()) 2704 return nextItem; 2705 } 2706 2707 // If no other suitable item was found, return NULL. 2708 return NULL; 2709 } 2710 2711 2712 void 2713 BMenu::_SetIgnoreHidden(bool on) 2714 { 2715 fIgnoreHidden = on; 2716 } 2717 2718 2719 void 2720 BMenu::_SetStickyMode(bool on) 2721 { 2722 if (fStickyMode == on) 2723 return; 2724 2725 fStickyMode = on; 2726 2727 if (fSuper != NULL) { 2728 // propagate the status to the super menu 2729 fSuper->_SetStickyMode(on); 2730 } else { 2731 // TODO: Ugly hack, but it needs to be done in this method 2732 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 2733 if (on && menuBar != NULL && menuBar->LockLooper()) { 2734 // If we are switching to sticky mode, 2735 // steal the focus from the current focus view 2736 // (needed to handle keyboard navigation) 2737 menuBar->_StealFocus(); 2738 menuBar->UnlockLooper(); 2739 } 2740 } 2741 } 2742 2743 2744 bool 2745 BMenu::_IsStickyMode() const 2746 { 2747 return fStickyMode; 2748 } 2749 2750 2751 void 2752 BMenu::_GetShiftKey(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_SHIFT_KEY, &value) != B_OK) 2761 value = 0x4b; 2762 } 2763 2764 2765 void 2766 BMenu::_GetControlKey(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_CONTROL_KEY, &value) != B_OK) 2775 value = 0x5c; 2776 } 2777 2778 2779 void 2780 BMenu::_GetCommandKey(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_LEFT_COMMAND_KEY, &value) != B_OK) 2789 value = 0x66; 2790 } 2791 2792 2793 void 2794 BMenu::_GetOptionKey(uint32 &value) const 2795 { 2796 // TODO: Move into init_interface_kit(). 2797 // Currently we can't do that, as get_modifier_key() blocks forever 2798 // when called on input_server initialization, since it tries 2799 // to send a synchronous message to itself (input_server is 2800 // a BApplication) 2801 2802 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 2803 value = 0x5d; 2804 } 2805 2806 2807 void 2808 BMenu::_GetMenuKey(uint32 &value) const 2809 { 2810 // TODO: Move into init_interface_kit(). 2811 // Currently we can't do that, as get_modifier_key() blocks forever 2812 // when called on input_server initialization, since it tries 2813 // to send a synchronous message to itself (input_server is 2814 // a BApplication) 2815 2816 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 2817 value = 0x68; 2818 } 2819 2820 2821 void 2822 BMenu::_CalcTriggers() 2823 { 2824 BPrivate::TriggerList triggerList; 2825 2826 // Gathers the existing triggers set by the user 2827 for (int32 i = 0; i < CountItems(); i++) { 2828 char trigger = ItemAt(i)->Trigger(); 2829 if (trigger != 0) 2830 triggerList.AddTrigger(trigger); 2831 } 2832 2833 // Set triggers for items which don't have one yet 2834 for (int32 i = 0; i < CountItems(); i++) { 2835 BMenuItem* item = ItemAt(i); 2836 if (item->Trigger() == 0) { 2837 uint32 trigger; 2838 int32 index; 2839 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 2840 item->SetAutomaticTrigger(index, trigger); 2841 } 2842 } 2843 } 2844 2845 2846 bool 2847 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 2848 BPrivate::TriggerList& triggers) 2849 { 2850 if (title == NULL) 2851 return false; 2852 2853 uint32 c; 2854 2855 // two runs: first we look out for uppercase letters 2856 // TODO: support Unicode characters correctly! 2857 for (uint32 i = 0; (c = title[i]) != '\0'; i++) { 2858 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) { 2859 index = i; 2860 trigger = tolower(c); 2861 return triggers.AddTrigger(c); 2862 } 2863 } 2864 2865 // then, if we still haven't found anything, we accept them all 2866 index = 0; 2867 while ((c = UTF8ToCharCode(&title)) != 0) { 2868 if (!isspace(c) && !triggers.HasTrigger(c)) { 2869 trigger = tolower(c); 2870 return triggers.AddTrigger(c); 2871 } 2872 2873 index++; 2874 } 2875 2876 return false; 2877 } 2878 2879 2880 void 2881 BMenu::_UpdateWindowViewSize(const bool &move) 2882 { 2883 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 2884 if (window == NULL) 2885 return; 2886 2887 if (dynamic_cast<BMenuBar*>(this) != NULL) 2888 return; 2889 2890 if (!fResizeToFit) 2891 return; 2892 2893 bool scroll = false; 2894 const BPoint screenLocation = move ? ScreenLocation() 2895 : window->Frame().LeftTop(); 2896 BRect frame = _CalcFrame(screenLocation, &scroll); 2897 ResizeTo(frame.Width(), frame.Height()); 2898 2899 if (fItems.CountItems() > 0) { 2900 if (!scroll) { 2901 window->ResizeTo(Bounds().Width(), Bounds().Height()); 2902 } else { 2903 BScreen screen(window); 2904 2905 // If we need scrolling, resize the window to fit the screen and 2906 // attach scrollers to our cached BMenuWindow. 2907 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) { 2908 window->ResizeTo(Bounds().Width(), screen.Frame().Height()); 2909 frame.top = 0; 2910 } else { 2911 // Or, in case our parent was a BMenuBar enable scrolling with 2912 // normal size. 2913 window->ResizeTo(Bounds().Width(), 2914 screen.Frame().bottom - frame.top); 2915 } 2916 2917 if (fLayout == B_ITEMS_IN_COLUMN) { 2918 // we currently only support scrolling for B_ITEMS_IN_COLUMN 2919 window->AttachScrollers(); 2920 2921 BMenuItem* selectedItem = FindMarked(); 2922 if (selectedItem != NULL) { 2923 // scroll to the selected item 2924 if (Supermenu() == NULL) { 2925 window->TryScrollTo(selectedItem->Frame().top); 2926 } else { 2927 BPoint point = selectedItem->Frame().LeftTop(); 2928 BPoint superPoint = Superitem()->Frame().LeftTop(); 2929 Supermenu()->ConvertToScreen(&superPoint); 2930 ConvertToScreen(&point); 2931 window->TryScrollTo(point.y - superPoint.y); 2932 } 2933 } 2934 } 2935 } 2936 } else { 2937 _CacheFontInfo(); 2938 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 2939 + fPad.left + fPad.right, 2940 fFontHeight + fPad.top + fPad.bottom); 2941 } 2942 2943 if (move) 2944 window->MoveTo(frame.LeftTop()); 2945 } 2946 2947 2948 bool 2949 BMenu::_AddDynamicItems(bool keyDown) 2950 { 2951 bool addAborted = false; 2952 if (AddDynamicItem(B_INITIAL_ADD)) { 2953 BMenuItem* superItem = Superitem(); 2954 BMenu* superMenu = Supermenu(); 2955 do { 2956 if (superMenu != NULL 2957 && !superMenu->_OkToProceed(superItem, keyDown)) { 2958 AddDynamicItem(B_ABORT); 2959 addAborted = true; 2960 break; 2961 } 2962 } while (AddDynamicItem(B_PROCESSING)); 2963 } 2964 2965 return addAborted; 2966 } 2967 2968 2969 bool 2970 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 2971 { 2972 BPoint where; 2973 uint32 buttons; 2974 GetMouse(&where, &buttons, false); 2975 bool stickyMode = _IsStickyMode(); 2976 // Quit if user clicks the mouse button in sticky mode 2977 // or releases the mouse button in nonsticky mode 2978 // or moves the pointer over another item 2979 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2980 // BeOS seems to do something similar. This could also be a bug in 2981 // Deskbar, though. 2982 if ((buttons != 0 && stickyMode) 2983 || ((dynamic_cast<BMenuBar*>(this) == NULL 2984 && (buttons == 0 && !stickyMode)) 2985 || ((_HitTestItems(where) != item) && !keyDown))) { 2986 return false; 2987 } 2988 2989 return true; 2990 } 2991 2992 2993 bool 2994 BMenu::_CustomTrackingWantsToQuit() 2995 { 2996 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 2997 && fExtraMenuData->trackingState != NULL) { 2998 return fExtraMenuData->trackingHook(this, 2999 fExtraMenuData->trackingState); 3000 } 3001 3002 return false; 3003 } 3004 3005 3006 void 3007 BMenu::_QuitTracking(bool onlyThis) 3008 { 3009 _SelectItem(NULL); 3010 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 3011 menuBar->_RestoreFocus(); 3012 3013 fState = MENU_STATE_CLOSED; 3014 3015 if (!onlyThis) { 3016 // Close the whole menu hierarchy 3017 if (Supermenu() != NULL) 3018 Supermenu()->fState = MENU_STATE_CLOSED; 3019 3020 if (_IsStickyMode()) 3021 _SetStickyMode(false); 3022 3023 if (LockLooper()) { 3024 be_app->ShowCursor(); 3025 UnlockLooper(); 3026 } 3027 } 3028 3029 _Hide(); 3030 } 3031 3032 3033 // #pragma mark - menu_info functions 3034 3035 3036 // TODO: Maybe the following two methods would fit better into 3037 // InterfaceDefs.cpp 3038 // In R5, they do all the work client side, we let the app_server handle the 3039 // details. 3040 status_t 3041 set_menu_info(menu_info* info) 3042 { 3043 if (!info) 3044 return B_BAD_VALUE; 3045 3046 BPrivate::AppServerLink link; 3047 link.StartMessage(AS_SET_MENU_INFO); 3048 link.Attach<menu_info>(*info); 3049 3050 status_t status = B_ERROR; 3051 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3052 BMenu::sMenuInfo = *info; 3053 // Update also the local copy, in case anyone relies on it 3054 3055 return status; 3056 } 3057 3058 3059 status_t 3060 get_menu_info(menu_info* info) 3061 { 3062 if (!info) 3063 return B_BAD_VALUE; 3064 3065 BPrivate::AppServerLink link; 3066 link.StartMessage(AS_GET_MENU_INFO); 3067 3068 status_t status = B_ERROR; 3069 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3070 link.Read<menu_info>(info); 3071 3072 return status; 3073 } 3074 3075 3076 extern "C" void 3077 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3078 BMenu* menu, bool descendants) 3079 { 3080 menu->InvalidateLayout(); 3081 } 3082