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