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