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