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 _UpdateWindowViewSize(false); 2418 return true; 2419 } 2420 return false; 2421 } 2422 2423 2424 void 2425 BMenu::_LayoutItems(int32 index) 2426 { 2427 _CalcTriggers(); 2428 2429 float width; 2430 float height; 2431 _ComputeLayout(index, fResizeToFit, true, &width, &height); 2432 2433 if (fResizeToFit) 2434 ResizeTo(width, height); 2435 } 2436 2437 2438 BSize 2439 BMenu::_ValidatePreferredSize() 2440 { 2441 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode() 2442 != fLayoutData->lastResizingMode) { 2443 _ComputeLayout(0, true, false, NULL, NULL); 2444 ResetLayoutInvalidation(); 2445 } 2446 2447 return fLayoutData->preferred; 2448 } 2449 2450 2451 void 2452 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 2453 float* _width, float* _height) 2454 { 2455 // TODO: Take "bestFit", "moveItems", "index" into account, 2456 // Recalculate only the needed items, 2457 // not the whole layout every time 2458 2459 fLayoutData->lastResizingMode = ResizingMode(); 2460 2461 BRect frame; 2462 switch (fLayout) { 2463 case B_ITEMS_IN_COLUMN: 2464 { 2465 BRect parentFrame; 2466 BRect* overrideFrame = NULL; 2467 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) { 2468 // When the menu is modified while it's open, we get here in a 2469 // situation where trying to lock the looper would deadlock 2470 // (the window is locked waiting for the menu to terminate). 2471 // In that case, just give up on getting the supermenu bounds 2472 // and keep the menu at the current width and position. 2473 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) { 2474 parentFrame = Supermenu()->Bounds(); 2475 Supermenu()->UnlockLooper(); 2476 overrideFrame = &parentFrame; 2477 } 2478 } 2479 2480 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, 2481 frame); 2482 break; 2483 } 2484 2485 case B_ITEMS_IN_ROW: 2486 _ComputeRowLayout(index, bestFit, moveItems, frame); 2487 break; 2488 2489 case B_ITEMS_IN_MATRIX: 2490 _ComputeMatrixLayout(frame); 2491 break; 2492 } 2493 2494 // change width depending on resize mode 2495 BSize size; 2496 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2497 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL) 2498 size.width = Bounds().Width() - fPad.right; 2499 else if (Parent() != NULL) 2500 size.width = Parent()->Frame().Width(); 2501 else if (Window() != NULL) 2502 size.width = Window()->Frame().Width(); 2503 else 2504 size.width = Bounds().Width(); 2505 } else 2506 size.width = frame.Width(); 2507 2508 size.height = frame.Height(); 2509 2510 if (_width) 2511 *_width = size.width; 2512 2513 if (_height) 2514 *_height = size.height; 2515 2516 if (bestFit) 2517 fLayoutData->preferred = size; 2518 2519 if (moveItems) 2520 fUseCachedMenuLayout = true; 2521 } 2522 2523 2524 void 2525 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2526 BRect* overrideFrame, BRect& frame) 2527 { 2528 bool command = false; 2529 bool control = false; 2530 bool shift = false; 2531 bool option = false; 2532 bool submenu = false; 2533 2534 if (index > 0) 2535 frame = ItemAt(index - 1)->Frame(); 2536 else if (overrideFrame != NULL) 2537 frame.Set(0, 0, overrideFrame->right, -1); 2538 else 2539 frame.Set(0, 0, 0, -1); 2540 2541 BFont font; 2542 GetFont(&font); 2543 2544 // Loop over all items to set their top, bottom and left coordinates, 2545 // all while computing the width of the menu 2546 for (; index < fItems.CountItems(); index++) { 2547 BMenuItem* item = ItemAt(index); 2548 2549 float width; 2550 float height; 2551 item->GetContentSize(&width, &height); 2552 2553 if (item->fModifiers && item->fShortcutChar) { 2554 width += font.Size(); 2555 if ((item->fModifiers & B_COMMAND_KEY) != 0) 2556 command = true; 2557 2558 if ((item->fModifiers & B_CONTROL_KEY) != 0) 2559 control = true; 2560 2561 if ((item->fModifiers & B_SHIFT_KEY) != 0) 2562 shift = true; 2563 2564 if ((item->fModifiers & B_OPTION_KEY) != 0) 2565 option = true; 2566 } 2567 2568 item->fBounds.left = 0.0f; 2569 item->fBounds.top = frame.bottom + 1.0f; 2570 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2571 + fPad.bottom; 2572 2573 if (item->fSubmenu != NULL) 2574 submenu = true; 2575 2576 frame.right = std::max(frame.right, width + fPad.left + fPad.right); 2577 frame.bottom = item->fBounds.bottom; 2578 } 2579 2580 // Compute the extra space needed for shortcuts and submenus 2581 if (command) { 2582 frame.right 2583 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2584 } 2585 if (control) { 2586 frame.right 2587 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2588 } 2589 if (option) { 2590 frame.right 2591 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2592 } 2593 if (shift) { 2594 frame.right 2595 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2596 } 2597 if (submenu) { 2598 frame.right += ItemAt(0)->Frame().Height() / 2; 2599 fHasSubmenus = true; 2600 } else { 2601 fHasSubmenus = false; 2602 } 2603 2604 if (fMaxContentWidth > 0) 2605 frame.right = std::min(frame.right, fMaxContentWidth); 2606 2607 // Finally update the "right" coordinate of all items 2608 if (moveItems) { 2609 for (int32 i = 0; i < fItems.CountItems(); i++) 2610 ItemAt(i)->fBounds.right = frame.right; 2611 } 2612 2613 frame.top = 0; 2614 frame.right = ceilf(frame.right); 2615 } 2616 2617 2618 void 2619 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2620 BRect& frame) 2621 { 2622 font_height fh; 2623 GetFontHeight(&fh); 2624 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2625 + fPad.bottom)); 2626 2627 for (int32 i = 0; i < fItems.CountItems(); i++) { 2628 BMenuItem* item = ItemAt(i); 2629 2630 float width, height; 2631 item->GetContentSize(&width, &height); 2632 2633 item->fBounds.left = frame.right; 2634 item->fBounds.top = 0.0f; 2635 item->fBounds.right = item->fBounds.left + width + fPad.left 2636 + fPad.right; 2637 2638 frame.right = item->Frame().right + 1.0f; 2639 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom); 2640 } 2641 2642 if (moveItems) { 2643 for (int32 i = 0; i < fItems.CountItems(); i++) 2644 ItemAt(i)->fBounds.bottom = frame.bottom; 2645 } 2646 2647 if (bestFit) 2648 frame.right = ceilf(frame.right); 2649 else 2650 frame.right = Bounds().right; 2651 } 2652 2653 2654 void 2655 BMenu::_ComputeMatrixLayout(BRect &frame) 2656 { 2657 frame.Set(0, 0, 0, 0); 2658 for (int32 i = 0; i < CountItems(); i++) { 2659 BMenuItem* item = ItemAt(i); 2660 if (item != NULL) { 2661 frame.left = std::min(frame.left, item->Frame().left); 2662 frame.right = std::max(frame.right, item->Frame().right); 2663 frame.top = std::min(frame.top, item->Frame().top); 2664 frame.bottom = std::max(frame.bottom, item->Frame().bottom); 2665 } 2666 } 2667 } 2668 2669 2670 void 2671 BMenu::LayoutInvalidated(bool descendants) 2672 { 2673 fUseCachedMenuLayout = false; 2674 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2675 } 2676 2677 2678 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2679 BPoint 2680 BMenu::ScreenLocation() 2681 { 2682 BMenu* superMenu = Supermenu(); 2683 BMenuItem* superItem = Superitem(); 2684 2685 if (superMenu == NULL || superItem == NULL) { 2686 debugger("BMenu can't determine where to draw." 2687 "Override BMenu::ScreenLocation() to determine location."); 2688 } 2689 2690 BPoint point; 2691 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2692 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2693 else 2694 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2695 2696 superMenu->ConvertToScreen(&point); 2697 2698 return point; 2699 } 2700 2701 2702 BRect 2703 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2704 { 2705 // TODO: Improve me 2706 BRect bounds = Bounds(); 2707 BRect frame = bounds.OffsetToCopy(where); 2708 2709 BScreen screen(Window()); 2710 BRect screenFrame = screen.Frame(); 2711 2712 BMenu* superMenu = Supermenu(); 2713 BMenuItem* superItem = Superitem(); 2714 2715 // Reset frame shifted state since this menu is being redrawn 2716 fExtraMenuData->frameShiftedLeft = false; 2717 2718 // TODO: Horrible hack: 2719 // When added to a BMenuField, a BPopUpMenu is the child of 2720 // a _BMCMenuBar_ to "fake" the menu hierarchy 2721 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2722 2723 // Offset the menu field menu window left by the width of the checkmark 2724 // so that the text when the menu is closed lines up with the text when 2725 // the menu is open. 2726 if (inMenuField) 2727 frame.OffsetBy(-8.0f, 0.0f); 2728 2729 bool scroll = false; 2730 if (superMenu == NULL || superItem == NULL || inMenuField) { 2731 // just move the window on screen 2732 if (frame.bottom > screenFrame.bottom) 2733 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2734 else if (frame.top < screenFrame.top) 2735 frame.OffsetBy(0, -frame.top); 2736 2737 if (frame.right > screenFrame.right) { 2738 frame.OffsetBy(screenFrame.right - frame.right, 0); 2739 fExtraMenuData->frameShiftedLeft = true; 2740 } 2741 else if (frame.left < screenFrame.left) 2742 frame.OffsetBy(-frame.left, 0); 2743 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2744 if (frame.right > screenFrame.right 2745 || superMenu->fExtraMenuData->frameShiftedLeft) { 2746 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2747 fExtraMenuData->frameShiftedLeft = true; 2748 } 2749 2750 if (frame.left < 0) 2751 frame.OffsetBy(-frame.left + 6, 0); 2752 2753 if (frame.bottom > screenFrame.bottom) 2754 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2755 } else { 2756 if (frame.bottom > screenFrame.bottom) { 2757 frame.OffsetBy(0, -superItem->Frame().Height() 2758 - frame.Height() - 3); 2759 } 2760 2761 if (frame.right > screenFrame.right) 2762 frame.OffsetBy(screenFrame.right - frame.right, 0); 2763 } 2764 2765 if (!scroll) { 2766 // basically, if this returns false, it means 2767 // that the menu frame won't fit completely inside the screen 2768 // TODO: Scrolling will currently only work up/down, 2769 // not left/right 2770 scroll = screenFrame.Height() < frame.Height(); 2771 } 2772 2773 if (scrollOn != NULL) 2774 *scrollOn = scroll; 2775 2776 return frame; 2777 } 2778 2779 2780 void 2781 BMenu::DrawItems(BRect updateRect) 2782 { 2783 int32 itemCount = fItems.CountItems(); 2784 for (int32 i = 0; i < itemCount; i++) { 2785 BMenuItem* item = ItemAt(i); 2786 if (item->Frame().Intersects(updateRect)) 2787 item->Draw(); 2788 } 2789 } 2790 2791 2792 int 2793 BMenu::_State(BMenuItem** item) const 2794 { 2795 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2796 return fState; 2797 2798 if (fSelected != NULL && fSelected->Submenu() != NULL) 2799 return fSelected->Submenu()->_State(item); 2800 2801 return fState; 2802 } 2803 2804 2805 void 2806 BMenu::_InvokeItem(BMenuItem* item, bool now) 2807 { 2808 if (!item->IsEnabled()) 2809 return; 2810 2811 // Do the "selected" animation 2812 // TODO: Doesn't work. This is supposed to highlight 2813 // and dehighlight the item, works on beos but not on haiku. 2814 if (!item->Submenu() && LockLooper()) { 2815 snooze(50000); 2816 item->Select(true); 2817 Window()->UpdateIfNeeded(); 2818 snooze(50000); 2819 item->Select(false); 2820 Window()->UpdateIfNeeded(); 2821 snooze(50000); 2822 item->Select(true); 2823 Window()->UpdateIfNeeded(); 2824 snooze(50000); 2825 item->Select(false); 2826 Window()->UpdateIfNeeded(); 2827 UnlockLooper(); 2828 } 2829 2830 // Lock the root menu window before calling BMenuItem::Invoke() 2831 BMenu* parent = this; 2832 BMenu* rootMenu = NULL; 2833 do { 2834 rootMenu = parent; 2835 parent = rootMenu->Supermenu(); 2836 } while (parent != NULL); 2837 2838 if (rootMenu->LockLooper()) { 2839 item->Invoke(); 2840 rootMenu->UnlockLooper(); 2841 } 2842 } 2843 2844 2845 bool 2846 BMenu::_OverSuper(BPoint location) 2847 { 2848 if (!Supermenu()) 2849 return false; 2850 2851 return fSuperbounds.Contains(location); 2852 } 2853 2854 2855 bool 2856 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2857 { 2858 if (item == NULL) 2859 return false; 2860 2861 BMenu* subMenu = item->Submenu(); 2862 if (subMenu == NULL || subMenu->Window() == NULL) 2863 return false; 2864 2865 // assume that loc is in screen coordinates 2866 if (subMenu->Window()->Frame().Contains(loc)) 2867 return true; 2868 2869 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2870 } 2871 2872 2873 BMenuWindow* 2874 BMenu::_MenuWindow() 2875 { 2876 #if USE_CACHED_MENUWINDOW 2877 if (fCachedMenuWindow == NULL) { 2878 char windowName[64]; 2879 snprintf(windowName, 64, "%s cached menu", Name()); 2880 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2881 } 2882 #endif 2883 return fCachedMenuWindow; 2884 } 2885 2886 2887 void 2888 BMenu::_DeleteMenuWindow() 2889 { 2890 if (fCachedMenuWindow != NULL) { 2891 fCachedMenuWindow->Lock(); 2892 fCachedMenuWindow->Quit(); 2893 fCachedMenuWindow = NULL; 2894 } 2895 } 2896 2897 2898 BMenuItem* 2899 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2900 { 2901 // TODO: Take "slop" into account ? 2902 2903 // if the point doesn't lie within the menu's 2904 // bounds, bail out immediately 2905 if (!Bounds().Contains(where)) 2906 return NULL; 2907 2908 int32 itemCount = CountItems(); 2909 for (int32 i = 0; i < itemCount; i++) { 2910 BMenuItem* item = ItemAt(i); 2911 if (item->Frame().Contains(where) 2912 && dynamic_cast<BSeparatorItem*>(item) == NULL) { 2913 return item; 2914 } 2915 } 2916 2917 return NULL; 2918 } 2919 2920 2921 BRect 2922 BMenu::_Superbounds() const 2923 { 2924 return fSuperbounds; 2925 } 2926 2927 2928 void 2929 BMenu::_CacheFontInfo() 2930 { 2931 font_height fh; 2932 GetFontHeight(&fh); 2933 fAscent = fh.ascent; 2934 fDescent = fh.descent; 2935 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2936 } 2937 2938 2939 void 2940 BMenu::_ItemMarked(BMenuItem* item) 2941 { 2942 if (IsRadioMode()) { 2943 for (int32 i = 0; i < CountItems(); i++) { 2944 if (ItemAt(i) != item) 2945 ItemAt(i)->SetMarked(false); 2946 } 2947 } 2948 2949 if (IsLabelFromMarked() && Superitem() != NULL) 2950 Superitem()->SetLabel(item->Label()); 2951 } 2952 2953 2954 void 2955 BMenu::_Install(BWindow* target) 2956 { 2957 for (int32 i = 0; i < CountItems(); i++) 2958 ItemAt(i)->Install(target); 2959 } 2960 2961 2962 void 2963 BMenu::_Uninstall() 2964 { 2965 for (int32 i = 0; i < CountItems(); i++) 2966 ItemAt(i)->Uninstall(); 2967 } 2968 2969 2970 void 2971 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem, 2972 bool keyDown) 2973 { 2974 // Avoid deselecting and then reselecting the same item 2975 // which would cause flickering 2976 if (item != fSelected) { 2977 if (fSelected != NULL) { 2978 fSelected->Select(false); 2979 BMenu* subMenu = fSelected->Submenu(); 2980 if (subMenu != NULL && subMenu->Window() != NULL) 2981 subMenu->_Hide(); 2982 } 2983 2984 fSelected = item; 2985 if (fSelected != NULL) 2986 fSelected->Select(true); 2987 } 2988 2989 if (fSelected != NULL && showSubmenu) { 2990 BMenu* subMenu = fSelected->Submenu(); 2991 if (subMenu != NULL && subMenu->Window() == NULL) { 2992 if (!subMenu->_Show(selectFirstItem, keyDown)) { 2993 // something went wrong, deselect the item 2994 fSelected->Select(false); 2995 fSelected = NULL; 2996 } 2997 } 2998 } 2999 } 3000 3001 3002 bool 3003 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 3004 { 3005 if (CountItems() == 0) // cannot select next item in an empty menu 3006 return false; 3007 3008 BMenuItem* nextItem = _NextItem(item, forward); 3009 if (nextItem == NULL) 3010 return false; 3011 3012 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL); 3013 3014 if (LockLooper()) { 3015 be_app->ObscureCursor(); 3016 UnlockLooper(); 3017 } 3018 3019 return true; 3020 } 3021 3022 3023 BMenuItem* 3024 BMenu::_NextItem(BMenuItem* item, bool forward) const 3025 { 3026 const int32 numItems = fItems.CountItems(); 3027 if (numItems == 0) 3028 return NULL; 3029 3030 int32 index = fItems.IndexOf(item); 3031 int32 loopCount = numItems; 3032 while (--loopCount) { 3033 // Cycle through menu items in the given direction... 3034 if (forward) 3035 index++; 3036 else 3037 index--; 3038 3039 // ... wrap around... 3040 if (index < 0) 3041 index = numItems - 1; 3042 else if (index >= numItems) 3043 index = 0; 3044 3045 // ... and return the first suitable item found. 3046 BMenuItem* nextItem = ItemAt(index); 3047 if (nextItem->IsEnabled()) 3048 return nextItem; 3049 } 3050 3051 // If no other suitable item was found, return NULL. 3052 return NULL; 3053 } 3054 3055 3056 void 3057 BMenu::_SetStickyMode(bool sticky) 3058 { 3059 if (fStickyMode == sticky) 3060 return; 3061 3062 fStickyMode = sticky; 3063 3064 if (fSuper != NULL) { 3065 // propagate the status to the super menu 3066 fSuper->_SetStickyMode(sticky); 3067 } else { 3068 // TODO: Ugly hack, but it needs to be done in this method 3069 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 3070 if (sticky && menuBar != NULL && menuBar->LockLooper()) { 3071 // If we are switching to sticky mode, 3072 // steal the focus from the current focus view 3073 // (needed to handle keyboard navigation) 3074 menuBar->_StealFocus(); 3075 menuBar->UnlockLooper(); 3076 } 3077 } 3078 } 3079 3080 3081 bool 3082 BMenu::_IsStickyMode() const 3083 { 3084 return fStickyMode; 3085 } 3086 3087 3088 void 3089 BMenu::_GetShiftKey(uint32 &value) const 3090 { 3091 // TODO: Move into init_interface_kit(). 3092 // Currently we can't do that, as get_modifier_key() blocks forever 3093 // when called on input_server initialization, since it tries 3094 // to send a synchronous message to itself (input_server is 3095 // a BApplication) 3096 3097 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK) 3098 value = 0x4b; 3099 } 3100 3101 3102 void 3103 BMenu::_GetControlKey(uint32 &value) const 3104 { 3105 // TODO: Move into init_interface_kit(). 3106 // Currently we can't do that, as get_modifier_key() blocks forever 3107 // when called on input_server initialization, since it tries 3108 // to send a synchronous message to itself (input_server is 3109 // a BApplication) 3110 3111 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK) 3112 value = 0x5c; 3113 } 3114 3115 3116 void 3117 BMenu::_GetCommandKey(uint32 &value) const 3118 { 3119 // TODO: Move into init_interface_kit(). 3120 // Currently we can't do that, as get_modifier_key() blocks forever 3121 // when called on input_server initialization, since it tries 3122 // to send a synchronous message to itself (input_server is 3123 // a BApplication) 3124 3125 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK) 3126 value = 0x66; 3127 } 3128 3129 3130 void 3131 BMenu::_GetOptionKey(uint32 &value) const 3132 { 3133 // TODO: Move into init_interface_kit(). 3134 // Currently we can't do that, as get_modifier_key() blocks forever 3135 // when called on input_server initialization, since it tries 3136 // to send a synchronous message to itself (input_server is 3137 // a BApplication) 3138 3139 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 3140 value = 0x5d; 3141 } 3142 3143 3144 void 3145 BMenu::_GetMenuKey(uint32 &value) const 3146 { 3147 // TODO: Move into init_interface_kit(). 3148 // Currently we can't do that, as get_modifier_key() blocks forever 3149 // when called on input_server initialization, since it tries 3150 // to send a synchronous message to itself (input_server is 3151 // a BApplication) 3152 3153 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 3154 value = 0x68; 3155 } 3156 3157 3158 void 3159 BMenu::_CalcTriggers() 3160 { 3161 BPrivate::TriggerList triggerList; 3162 3163 // Gathers the existing triggers set by the user 3164 for (int32 i = 0; i < CountItems(); i++) { 3165 char trigger = ItemAt(i)->Trigger(); 3166 if (trigger != 0) 3167 triggerList.AddTrigger(trigger); 3168 } 3169 3170 // Set triggers for items which don't have one yet 3171 for (int32 i = 0; i < CountItems(); i++) { 3172 BMenuItem* item = ItemAt(i); 3173 if (item->Trigger() == 0) { 3174 uint32 trigger; 3175 int32 index; 3176 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 3177 item->SetAutomaticTrigger(index, trigger); 3178 } 3179 } 3180 } 3181 3182 3183 bool 3184 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 3185 BPrivate::TriggerList& triggers) 3186 { 3187 if (title == NULL) 3188 return false; 3189 3190 index = 0; 3191 uint32 c; 3192 const char* nextCharacter, *character; 3193 3194 // two runs: first we look out for alphanumeric ASCII characters 3195 nextCharacter = title; 3196 character = nextCharacter; 3197 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3198 if (!(c < 128 && BUnicodeChar::IsAlNum(c)) || triggers.HasTrigger(c)) { 3199 character = nextCharacter; 3200 continue; 3201 } 3202 trigger = BUnicodeChar::ToLower(c); 3203 index = (int32)(character - title); 3204 return triggers.AddTrigger(c); 3205 } 3206 3207 // then, if we still haven't found something, we accept anything 3208 nextCharacter = title; 3209 character = nextCharacter; 3210 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3211 if (BUnicodeChar::IsSpace(c) || triggers.HasTrigger(c)) { 3212 character = nextCharacter; 3213 continue; 3214 } 3215 trigger = BUnicodeChar::ToLower(c); 3216 index = (int32)(character - title); 3217 return triggers.AddTrigger(c); 3218 } 3219 3220 return false; 3221 } 3222 3223 3224 void 3225 BMenu::_UpdateWindowViewSize(const bool &move) 3226 { 3227 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 3228 if (window == NULL) 3229 return; 3230 3231 if (dynamic_cast<BMenuBar*>(this) != NULL) 3232 return; 3233 3234 if (!fResizeToFit) 3235 return; 3236 3237 bool scroll = false; 3238 const BPoint screenLocation = move ? ScreenLocation() 3239 : window->Frame().LeftTop(); 3240 BRect frame = _CalcFrame(screenLocation, &scroll); 3241 ResizeTo(frame.Width(), frame.Height()); 3242 3243 if (fItems.CountItems() > 0) { 3244 if (!scroll) { 3245 if (fLayout == B_ITEMS_IN_COLUMN) 3246 window->DetachScrollers(); 3247 3248 window->ResizeTo(Bounds().Width(), Bounds().Height()); 3249 } else { 3250 BScreen screen(window); 3251 3252 // Only scroll on menus not attached to a menubar, or when the 3253 // menu frame is above the visible screen 3254 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) { 3255 3256 // If we need scrolling, resize the window to fit the screen and 3257 // attach scrollers to our cached BMenuWindow. 3258 window->ResizeTo(Bounds().Width(), screen.Frame().Height()); 3259 frame.top = 0; 3260 3261 // we currently only support scrolling for B_ITEMS_IN_COLUMN 3262 if (fLayout == B_ITEMS_IN_COLUMN) { 3263 window->AttachScrollers(); 3264 3265 BMenuItem* selectedItem = FindMarked(); 3266 if (selectedItem != NULL) { 3267 // scroll to the selected item 3268 if (Supermenu() == NULL) { 3269 window->TryScrollTo(selectedItem->Frame().top); 3270 } else { 3271 BPoint point = selectedItem->Frame().LeftTop(); 3272 BPoint superPoint = Superitem()->Frame().LeftTop(); 3273 Supermenu()->ConvertToScreen(&superPoint); 3274 ConvertToScreen(&point); 3275 window->TryScrollTo(point.y - superPoint.y); 3276 } 3277 } 3278 } 3279 } 3280 } 3281 } else { 3282 _CacheFontInfo(); 3283 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 3284 + fPad.left + fPad.right, 3285 fFontHeight + fPad.top + fPad.bottom); 3286 } 3287 3288 if (move) 3289 window->MoveTo(frame.LeftTop()); 3290 } 3291 3292 3293 bool 3294 BMenu::_AddDynamicItems(bool keyDown) 3295 { 3296 bool addAborted = false; 3297 if (AddDynamicItem(B_INITIAL_ADD)) { 3298 BMenuItem* superItem = Superitem(); 3299 BMenu* superMenu = Supermenu(); 3300 do { 3301 if (superMenu != NULL 3302 && !superMenu->_OkToProceed(superItem, keyDown)) { 3303 AddDynamicItem(B_ABORT); 3304 addAborted = true; 3305 break; 3306 } 3307 } while (AddDynamicItem(B_PROCESSING)); 3308 } 3309 3310 return addAborted; 3311 } 3312 3313 3314 bool 3315 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 3316 { 3317 BPoint where; 3318 uint32 buttons; 3319 GetMouse(&where, &buttons, false); 3320 bool stickyMode = _IsStickyMode(); 3321 // Quit if user clicks the mouse button in sticky mode 3322 // or releases the mouse button in nonsticky mode 3323 // or moves the pointer over another item 3324 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 3325 // BeOS seems to do something similar. This could also be a bug in 3326 // Deskbar, though. 3327 if ((buttons != 0 && stickyMode) 3328 || ((dynamic_cast<BMenuBar*>(this) == NULL 3329 && (buttons == 0 && !stickyMode)) 3330 || ((_HitTestItems(where) != item) && !keyDown))) { 3331 return false; 3332 } 3333 3334 return true; 3335 } 3336 3337 3338 bool 3339 BMenu::_CustomTrackingWantsToQuit() 3340 { 3341 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 3342 && fExtraMenuData->trackingState != NULL) { 3343 return fExtraMenuData->trackingHook(this, 3344 fExtraMenuData->trackingState); 3345 } 3346 3347 return false; 3348 } 3349 3350 3351 void 3352 BMenu::_QuitTracking(bool onlyThis) 3353 { 3354 _SelectItem(NULL); 3355 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 3356 menuBar->_RestoreFocus(); 3357 3358 fState = MENU_STATE_CLOSED; 3359 3360 if (!onlyThis) { 3361 // Close the whole menu hierarchy 3362 if (Supermenu() != NULL) 3363 Supermenu()->fState = MENU_STATE_CLOSED; 3364 3365 if (_IsStickyMode()) 3366 _SetStickyMode(false); 3367 3368 if (LockLooper()) { 3369 be_app->ShowCursor(); 3370 UnlockLooper(); 3371 } 3372 } 3373 3374 _Hide(); 3375 } 3376 3377 3378 // #pragma mark - menu_info functions 3379 3380 3381 // TODO: Maybe the following two methods would fit better into 3382 // InterfaceDefs.cpp 3383 // In R5, they do all the work client side, we let the app_server handle the 3384 // details. 3385 status_t 3386 set_menu_info(menu_info* info) 3387 { 3388 if (!info) 3389 return B_BAD_VALUE; 3390 3391 BPrivate::AppServerLink link; 3392 link.StartMessage(AS_SET_MENU_INFO); 3393 link.Attach<menu_info>(*info); 3394 3395 status_t status = B_ERROR; 3396 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3397 BMenu::sMenuInfo = *info; 3398 // Update also the local copy, in case anyone relies on it 3399 3400 return status; 3401 } 3402 3403 3404 status_t 3405 get_menu_info(menu_info* info) 3406 { 3407 if (!info) 3408 return B_BAD_VALUE; 3409 3410 BPrivate::AppServerLink link; 3411 link.StartMessage(AS_GET_MENU_INFO); 3412 3413 status_t status = B_ERROR; 3414 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3415 link.Read<menu_info>(info); 3416 3417 return status; 3418 } 3419 3420 3421 extern "C" void 3422 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3423 BMenu* menu, bool descendants) 3424 { 3425 menu->InvalidateLayout(); 3426 } 3427