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