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