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