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