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