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