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, flags, 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 // #pragma mark - Reorder item methods 1443 1444 1445 void 1446 BMenu::SortItems(int (*compare)(const BMenuItem*, const BMenuItem*)) 1447 { 1448 fItems.SortItems((int (*)(const void*, const void*))compare); 1449 InvalidateLayout(); 1450 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1451 _LayoutItems(0); 1452 Invalidate(); 1453 UnlockLooper(); 1454 } 1455 } 1456 1457 1458 bool 1459 BMenu::SwapItems(int32 indexA, int32 indexB) 1460 { 1461 bool swapped = fItems.SwapItems(indexA, indexB); 1462 if (swapped) { 1463 InvalidateLayout(); 1464 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1465 _LayoutItems(std::min(indexA, indexB)); 1466 Invalidate(); 1467 UnlockLooper(); 1468 } 1469 } 1470 1471 return swapped; 1472 } 1473 1474 1475 bool 1476 BMenu::MoveItem(int32 indexFrom, int32 indexTo) 1477 { 1478 bool moved = fItems.MoveItem(indexFrom, indexTo); 1479 if (moved) { 1480 InvalidateLayout(); 1481 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) { 1482 _LayoutItems(std::min(indexFrom, indexTo)); 1483 Invalidate(); 1484 UnlockLooper(); 1485 } 1486 } 1487 1488 return moved; 1489 } 1490 1491 1492 void BMenu::_ReservedMenu3() {} 1493 void BMenu::_ReservedMenu4() {} 1494 void BMenu::_ReservedMenu5() {} 1495 void BMenu::_ReservedMenu6() {} 1496 1497 1498 void 1499 BMenu::_InitData(BMessage* archive) 1500 { 1501 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>"); 1502 1503 // TODO: Get _color, _fname, _fflt from the message, if present 1504 BFont font; 1505 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1506 font.SetSize(sMenuInfo.font_size); 1507 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1508 1509 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(); 1510 1511 fLayoutData = new LayoutData; 1512 fLayoutData->lastResizingMode = ResizingMode(); 1513 1514 SetLowUIColor(B_MENU_BACKGROUND_COLOR); 1515 SetViewColor(B_TRANSPARENT_COLOR); 1516 1517 fTriggerEnabled = sMenuInfo.triggers_always_shown; 1518 1519 if (archive != NULL) { 1520 archive->FindInt32("_layout", (int32*)&fLayout); 1521 archive->FindBool("_rsize_to_fit", &fResizeToFit); 1522 bool disabled; 1523 if (archive->FindBool("_disable", &disabled) == B_OK) 1524 fEnabled = !disabled; 1525 archive->FindBool("_radio", &fRadioMode); 1526 1527 bool disableTrigger = false; 1528 archive->FindBool("_trig_disabled", &disableTrigger); 1529 fTriggerEnabled = !disableTrigger; 1530 1531 archive->FindBool("_dyn_label", &fDynamicName); 1532 archive->FindFloat("_maxwidth", &fMaxContentWidth); 1533 1534 BMessage msg; 1535 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) { 1536 BArchivable* object = instantiate_object(&msg); 1537 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) { 1538 BRect bounds; 1539 if (fLayout == B_ITEMS_IN_MATRIX 1540 && archive->FindRect("_i_frames", i, &bounds) == B_OK) 1541 AddItem(item, bounds); 1542 else 1543 AddItem(item); 1544 } 1545 } 1546 } 1547 } 1548 1549 1550 bool 1551 BMenu::_Show(bool selectFirstItem, bool keyDown) 1552 { 1553 if (Window() != NULL) 1554 return false; 1555 1556 // See if the supermenu has a cached menuwindow, 1557 // and use that one if possible. 1558 BMenuWindow* window = NULL; 1559 bool ourWindow = false; 1560 if (fSuper != NULL) { 1561 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1562 window = fSuper->_MenuWindow(); 1563 } 1564 1565 // Otherwise, create a new one 1566 // This happens for "stand alone" BPopUpMenus 1567 // (i.e. not within a BMenuField) 1568 if (window == NULL) { 1569 // Menu windows get the BMenu's handler name 1570 window = new (nothrow) BMenuWindow(Name()); 1571 ourWindow = true; 1572 } 1573 1574 if (window == NULL) 1575 return false; 1576 1577 if (window->Lock()) { 1578 bool addAborted = false; 1579 if (keyDown) 1580 addAborted = _AddDynamicItems(keyDown); 1581 1582 if (addAborted) { 1583 if (ourWindow) 1584 window->Quit(); 1585 else 1586 window->Unlock(); 1587 return false; 1588 } 1589 fAttachAborted = false; 1590 1591 window->AttachMenu(this); 1592 1593 if (ItemAt(0) != NULL) { 1594 float width, height; 1595 ItemAt(0)->GetContentSize(&width, &height); 1596 1597 window->SetSmallStep(ceilf(height)); 1598 } 1599 1600 // Menu didn't have the time to add its items: aborting... 1601 if (fAttachAborted) { 1602 window->DetachMenu(); 1603 // TODO: Probably not needed, we can just let _hide() quit the 1604 // window. 1605 if (ourWindow) 1606 window->Quit(); 1607 else 1608 window->Unlock(); 1609 return false; 1610 } 1611 1612 _UpdateWindowViewSize(true); 1613 window->Show(); 1614 1615 if (selectFirstItem) 1616 _SelectItem(ItemAt(0), false); 1617 1618 window->Unlock(); 1619 } 1620 1621 return true; 1622 } 1623 1624 1625 void 1626 BMenu::_Hide() 1627 { 1628 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window()); 1629 if (window == NULL || !window->Lock()) 1630 return; 1631 1632 if (fSelected != NULL) 1633 _SelectItem(NULL); 1634 1635 window->Hide(); 1636 window->DetachMenu(); 1637 // we don't want to be deleted when the window is removed 1638 1639 #if USE_CACHED_MENUWINDOW 1640 if (fSuper != NULL) 1641 window->Unlock(); 1642 else 1643 #endif 1644 window->Quit(); 1645 // it's our window, quit it 1646 1647 _DeleteMenuWindow(); 1648 // Delete the menu window used by our submenus 1649 } 1650 1651 1652 void BMenu::_ScriptReceived(BMessage* message) 1653 { 1654 BMessage replyMsg(B_REPLY); 1655 status_t err = B_BAD_SCRIPT_SYNTAX; 1656 int32 index; 1657 BMessage specifier; 1658 int32 what; 1659 const char* property; 1660 1661 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property) 1662 != B_OK) { 1663 return BView::MessageReceived(message); 1664 } 1665 1666 BPropertyInfo propertyInfo(sPropList); 1667 switch (propertyInfo.FindMatch(message, index, &specifier, what, 1668 property)) { 1669 case 0: // Enabled: GET 1670 if (message->what == B_GET_PROPERTY) 1671 err = replyMsg.AddBool("result", IsEnabled()); 1672 break; 1673 case 1: // Enabled: SET 1674 if (message->what == B_SET_PROPERTY) { 1675 bool isEnabled; 1676 err = message->FindBool("data", &isEnabled); 1677 if (err >= B_OK) 1678 SetEnabled(isEnabled); 1679 } 1680 break; 1681 case 2: // Label: GET 1682 case 3: // Label: SET 1683 case 4: // Mark: GET 1684 case 5: { // Mark: SET 1685 BMenuItem *item = Superitem(); 1686 if (item != NULL) 1687 return Supermenu()->_ItemScriptReceived(message, item); 1688 1689 break; 1690 } 1691 case 6: // Menu: CREATE 1692 if (message->what == B_CREATE_PROPERTY) { 1693 const char *label; 1694 ObjectDeleter<BMessage> invokeMessage(new BMessage()); 1695 BMessenger target; 1696 ObjectDeleter<BMenuItem> item; 1697 err = message->FindString("data", &label); 1698 if (err >= B_OK) { 1699 invokeMessage.SetTo(new BMessage()); 1700 err = message->FindInt32("what", 1701 (int32*)&invokeMessage->what); 1702 if (err == B_NAME_NOT_FOUND) { 1703 invokeMessage.Unset(); 1704 err = B_OK; 1705 } 1706 } 1707 if (err >= B_OK) { 1708 item.SetTo(new BMenuItem(new BMenu(label), 1709 invokeMessage.Detach())); 1710 } 1711 if (err >= B_OK) { 1712 err = _InsertItemAtSpecifier(specifier, what, item.Get()); 1713 } 1714 if (err >= B_OK) 1715 item.Detach(); 1716 } 1717 break; 1718 case 7: { // Menu: DELETE 1719 if (message->what == B_DELETE_PROPERTY) { 1720 BMenuItem *item = NULL; 1721 int32 index; 1722 err = _ResolveItemSpecifier(specifier, what, item, &index); 1723 if (err >= B_OK) { 1724 if (item->Submenu() == NULL) 1725 err = B_BAD_VALUE; 1726 else { 1727 if (index >= 0) 1728 RemoveItem(index); 1729 else 1730 RemoveItem(item); 1731 } 1732 } 1733 } 1734 break; 1735 } 1736 case 8: { // Menu: * 1737 // TODO: check that submenu looper is running and handle it 1738 // correctly 1739 BMenu *submenu = NULL; 1740 BMenuItem *item; 1741 err = _ResolveItemSpecifier(specifier, what, item); 1742 if (err >= B_OK) 1743 submenu = item->Submenu(); 1744 if (submenu != NULL) { 1745 message->PopSpecifier(); 1746 return submenu->_ScriptReceived(message); 1747 } 1748 break; 1749 } 1750 case 9: // MenuItem: COUNT 1751 if (message->what == B_COUNT_PROPERTIES) 1752 err = replyMsg.AddInt32("result", CountItems()); 1753 break; 1754 case 10: // MenuItem: CREATE 1755 if (message->what == B_CREATE_PROPERTY) { 1756 const char *label; 1757 ObjectDeleter<BMessage> invokeMessage(new BMessage()); 1758 bool targetPresent = true; 1759 BMessenger target; 1760 ObjectDeleter<BMenuItem> item; 1761 err = message->FindString("data", &label); 1762 if (err >= B_OK) { 1763 err = message->FindMessage("be:invoke_message", 1764 invokeMessage.Get()); 1765 if (err == B_NAME_NOT_FOUND) { 1766 err = message->FindInt32("what", 1767 (int32*)&invokeMessage->what); 1768 if (err == B_NAME_NOT_FOUND) { 1769 invokeMessage.Unset(); 1770 err = B_OK; 1771 } 1772 } 1773 } 1774 if (err >= B_OK) { 1775 err = message->FindMessenger("be:target", &target); 1776 if (err == B_NAME_NOT_FOUND) { 1777 targetPresent = false; 1778 err = B_OK; 1779 } 1780 } 1781 if (err >= B_OK) { 1782 item.SetTo(new BMenuItem(label, invokeMessage.Detach())); 1783 if (targetPresent) 1784 err = item->SetTarget(target); 1785 } 1786 if (err >= B_OK) { 1787 err = _InsertItemAtSpecifier(specifier, what, item.Get()); 1788 } 1789 if (err >= B_OK) 1790 item.Detach(); 1791 } 1792 break; 1793 case 11: // MenuItem: DELETE 1794 if (message->what == B_DELETE_PROPERTY) { 1795 BMenuItem *item = NULL; 1796 int32 index; 1797 err = _ResolveItemSpecifier(specifier, what, item, &index); 1798 if (err >= B_OK) { 1799 if (index >= 0) 1800 RemoveItem(index); 1801 else 1802 RemoveItem(item); 1803 } 1804 } 1805 break; 1806 case 12: { // MenuItem: EXECUTE 1807 if (message->what == B_EXECUTE_PROPERTY) { 1808 BMenuItem *item = NULL; 1809 err = _ResolveItemSpecifier(specifier, what, item); 1810 if (err >= B_OK) { 1811 if (!item->IsEnabled()) 1812 err = B_NOT_ALLOWED; 1813 else 1814 err = item->Invoke(); 1815 } 1816 } 1817 break; 1818 } 1819 case 13: { // MenuItem: * 1820 BMenuItem *item = NULL; 1821 err = _ResolveItemSpecifier(specifier, what, item); 1822 if (err >= B_OK) { 1823 message->PopSpecifier(); 1824 return _ItemScriptReceived(message, item); 1825 } 1826 break; 1827 } 1828 default: 1829 return BView::MessageReceived(message); 1830 } 1831 1832 if (err != B_OK) { 1833 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD; 1834 1835 if (err == B_BAD_SCRIPT_SYNTAX) 1836 replyMsg.AddString("message", "Didn't understand the specifier(s)"); 1837 else 1838 replyMsg.AddString("message", strerror(err)); 1839 } 1840 1841 replyMsg.AddInt32("error", err); 1842 message->SendReply(&replyMsg); 1843 } 1844 1845 1846 void BMenu::_ItemScriptReceived(BMessage* message, BMenuItem* item) 1847 { 1848 BMessage replyMsg(B_REPLY); 1849 status_t err = B_BAD_SCRIPT_SYNTAX; 1850 int32 index; 1851 BMessage specifier; 1852 int32 what; 1853 const char* property; 1854 1855 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property) 1856 != B_OK) { 1857 return BView::MessageReceived(message); 1858 } 1859 1860 BPropertyInfo propertyInfo(sPropList); 1861 switch (propertyInfo.FindMatch(message, index, &specifier, what, 1862 property)) { 1863 case 0: // Enabled: GET 1864 if (message->what == B_GET_PROPERTY) 1865 err = replyMsg.AddBool("result", item->IsEnabled()); 1866 break; 1867 case 1: // Enabled: SET 1868 if (message->what == B_SET_PROPERTY) { 1869 bool isEnabled; 1870 err = message->FindBool("data", &isEnabled); 1871 if (err >= B_OK) 1872 item->SetEnabled(isEnabled); 1873 } 1874 break; 1875 case 2: // Label: GET 1876 if (message->what == B_GET_PROPERTY) 1877 err = replyMsg.AddString("result", item->Label()); 1878 break; 1879 case 3: // Label: SET 1880 if (message->what == B_SET_PROPERTY) { 1881 const char *label; 1882 err = message->FindString("data", &label); 1883 if (err >= B_OK) 1884 item->SetLabel(label); 1885 } 1886 case 4: // Mark: GET 1887 if (message->what == B_GET_PROPERTY) 1888 err = replyMsg.AddBool("result", item->IsMarked()); 1889 break; 1890 case 5: // Mark: SET 1891 if (message->what == B_SET_PROPERTY) { 1892 bool isMarked; 1893 err = message->FindBool("data", &isMarked); 1894 if (err >= B_OK) 1895 item->SetMarked(isMarked); 1896 } 1897 break; 1898 case 6: // Menu: CREATE 1899 case 7: // Menu: DELETE 1900 case 8: // Menu: * 1901 case 9: // MenuItem: COUNT 1902 case 10: // MenuItem: CREATE 1903 case 11: // MenuItem: DELETE 1904 case 12: // MenuItem: EXECUTE 1905 case 13: // MenuItem: * 1906 break; 1907 default: 1908 return BView::MessageReceived(message); 1909 } 1910 1911 if (err != B_OK) { 1912 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD; 1913 replyMsg.AddString("message", strerror(err)); 1914 } 1915 1916 replyMsg.AddInt32("error", err); 1917 message->SendReply(&replyMsg); 1918 } 1919 1920 1921 status_t BMenu::_ResolveItemSpecifier(const BMessage& specifier, int32 what, 1922 BMenuItem*& item, int32 *_index) 1923 { 1924 status_t err; 1925 item = NULL; 1926 int32 index = -1; 1927 switch (what) { 1928 case B_INDEX_SPECIFIER: 1929 case B_REVERSE_INDEX_SPECIFIER: { 1930 err = specifier.FindInt32("index", &index); 1931 if (err < B_OK) 1932 return err; 1933 if (what == B_REVERSE_INDEX_SPECIFIER) 1934 index = CountItems() - index; 1935 item = ItemAt(index); 1936 break; 1937 } 1938 case B_NAME_SPECIFIER: { 1939 const char* name; 1940 err = specifier.FindString("name", &name); 1941 if (err < B_OK) 1942 return err; 1943 item = FindItem(name); 1944 break; 1945 } 1946 } 1947 if (item == NULL) 1948 return B_BAD_INDEX; 1949 1950 if (_index != NULL) 1951 *_index = index; 1952 1953 return B_OK; 1954 } 1955 1956 1957 status_t BMenu::_InsertItemAtSpecifier(const BMessage& specifier, int32 what, 1958 BMenuItem* item) 1959 { 1960 status_t err; 1961 switch (what) { 1962 case B_INDEX_SPECIFIER: 1963 case B_REVERSE_INDEX_SPECIFIER: { 1964 int32 index; 1965 err = specifier.FindInt32("index", &index); 1966 if (err < B_OK) return err; 1967 if (what == B_REVERSE_INDEX_SPECIFIER) 1968 index = CountItems() - index; 1969 if (!AddItem(item, index)) 1970 return B_BAD_INDEX; 1971 break; 1972 } 1973 case B_NAME_SPECIFIER: 1974 return B_NOT_SUPPORTED; 1975 break; 1976 } 1977 1978 return B_OK; 1979 } 1980 1981 1982 // #pragma mark - mouse tracking 1983 1984 1985 const static bigtime_t kOpenSubmenuDelay = 0; 1986 const static bigtime_t kNavigationAreaTimeout = 1000000; 1987 1988 1989 BMenuItem* 1990 BMenu::_Track(int* action, long start) 1991 { 1992 // TODO: cleanup 1993 BMenuItem* item = NULL; 1994 BRect navAreaRectAbove; 1995 BRect navAreaRectBelow; 1996 bigtime_t selectedTime = system_time(); 1997 bigtime_t navigationAreaTime = 0; 1998 1999 fState = MENU_STATE_TRACKING; 2000 fChosenItem = NULL; 2001 // we will use this for keyboard selection 2002 2003 BPoint location; 2004 uint32 buttons = 0; 2005 if (LockLooper()) { 2006 GetMouse(&location, &buttons); 2007 UnlockLooper(); 2008 } 2009 2010 bool releasedOnce = buttons == 0; 2011 while (fState != MENU_STATE_CLOSED) { 2012 if (_CustomTrackingWantsToQuit()) 2013 break; 2014 2015 if (!LockLooper()) 2016 break; 2017 2018 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 2019 BPoint screenLocation = ConvertToScreen(location); 2020 if (window->CheckForScrolling(screenLocation)) { 2021 UnlockLooper(); 2022 continue; 2023 } 2024 2025 // The order of the checks is important 2026 // to be able to handle overlapping menus: 2027 // first we check if mouse is inside a submenu, 2028 // then if the mouse is inside this menu, 2029 // then if it's over a super menu. 2030 if (_OverSubmenu(fSelected, screenLocation) 2031 || fState == MENU_STATE_KEY_TO_SUBMENU) { 2032 if (fState == MENU_STATE_TRACKING) { 2033 // not if from R.Arrow 2034 fState = MENU_STATE_TRACKING_SUBMENU; 2035 } 2036 navAreaRectAbove = BRect(); 2037 navAreaRectBelow = BRect(); 2038 2039 // Since the submenu has its own looper, 2040 // we can unlock ours. Doing so also make sure 2041 // that our window gets any update message to 2042 // redraw itself 2043 UnlockLooper(); 2044 2045 // To prevent NULL access violation, ensure a menu has actually 2046 // been selected and that it has a submenu. Because keyboard and 2047 // mouse interactions set selected items differently, the menu 2048 // tracking thread needs to be careful in triggering the navigation 2049 // to the submenu. 2050 if (fSelected != NULL) { 2051 BMenu* submenu = fSelected->Submenu(); 2052 int submenuAction = MENU_STATE_TRACKING; 2053 if (submenu != NULL) { 2054 submenu->_SetStickyMode(_IsStickyMode()); 2055 2056 // The following call blocks until the submenu 2057 // gives control back to us, either because the mouse 2058 // pointer goes out of the submenu's bounds, or because 2059 // the user closes the menu 2060 BMenuItem* submenuItem = submenu->_Track(&submenuAction); 2061 if (submenuAction == MENU_STATE_CLOSED) { 2062 item = submenuItem; 2063 fState = MENU_STATE_CLOSED; 2064 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) { 2065 if (LockLooper()) { 2066 BMenuItem* temp = fSelected; 2067 // close the submenu: 2068 _SelectItem(NULL); 2069 // but reselect the item itself for user: 2070 _SelectItem(temp, false); 2071 UnlockLooper(); 2072 } 2073 // cancel key-nav state 2074 fState = MENU_STATE_TRACKING; 2075 } else 2076 fState = MENU_STATE_TRACKING; 2077 } 2078 } 2079 if (!LockLooper()) 2080 break; 2081 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) { 2082 _UpdateStateOpenSelect(item, location, navAreaRectAbove, 2083 navAreaRectBelow, selectedTime, navigationAreaTime); 2084 releasedOnce = true; 2085 } else if (_OverSuper(screenLocation) 2086 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 2087 fState = MENU_STATE_TRACKING; 2088 UnlockLooper(); 2089 break; 2090 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) { 2091 UnlockLooper(); 2092 break; 2093 } else if (fSuper == NULL 2094 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) { 2095 // Mouse pointer outside menu: 2096 // If there's no other submenu opened, 2097 // deselect the current selected item 2098 if (fSelected != NULL 2099 && (fSelected->Submenu() == NULL 2100 || fSelected->Submenu()->Window() == NULL)) { 2101 _SelectItem(NULL); 2102 fState = MENU_STATE_TRACKING; 2103 } 2104 2105 if (fSuper != NULL) { 2106 // Give supermenu the chance to continue tracking 2107 *action = fState; 2108 UnlockLooper(); 2109 return NULL; 2110 } 2111 } 2112 2113 UnlockLooper(); 2114 2115 if (releasedOnce) 2116 _UpdateStateClose(item, location, buttons); 2117 2118 if (fState != MENU_STATE_CLOSED) { 2119 bigtime_t snoozeAmount = 50000; 2120 2121 BPoint newLocation = location; 2122 uint32 newButtons = buttons; 2123 2124 // If user doesn't move the mouse, loop here, 2125 // so we don't interfere with keyboard menu navigation 2126 do { 2127 snooze(snoozeAmount); 2128 if (!LockLooper()) 2129 break; 2130 GetMouse(&newLocation, &newButtons, true); 2131 UnlockLooper(); 2132 } while (newLocation == location && newButtons == buttons 2133 && !(item != NULL && item->Submenu() != NULL 2134 && item->Submenu()->Window() == NULL) 2135 && fState == MENU_STATE_TRACKING); 2136 2137 if (newLocation != location || newButtons != buttons) { 2138 if (!releasedOnce && newButtons == 0 && buttons != 0) 2139 releasedOnce = true; 2140 location = newLocation; 2141 buttons = newButtons; 2142 } 2143 2144 if (releasedOnce) 2145 _UpdateStateClose(item, location, buttons); 2146 } 2147 } 2148 2149 if (action != NULL) 2150 *action = fState; 2151 2152 // keyboard Enter will set this 2153 if (fChosenItem != NULL) 2154 item = fChosenItem; 2155 else if (fSelected == NULL) { 2156 // needed to cover (rare) mouse/ESC combination 2157 item = NULL; 2158 } 2159 2160 if (fSelected != NULL && LockLooper()) { 2161 _SelectItem(NULL); 2162 UnlockLooper(); 2163 } 2164 2165 // delete the menu window recycled for all the child menus 2166 _DeleteMenuWindow(); 2167 2168 return item; 2169 } 2170 2171 2172 void 2173 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove, 2174 BRect& navAreaRectBelow) 2175 { 2176 #define NAV_AREA_THRESHOLD 8 2177 2178 // The navigation area is a region in which mouse-overs won't select 2179 // the item under the cursor. This makes it easier to navigate to 2180 // submenus, as the cursor can be moved to submenu items directly instead 2181 // of having to move it horizontally into the submenu first. The concept 2182 // is illustrated below: 2183 // 2184 // +-------+----+---------+ 2185 // | | /| | 2186 // | | /*| | 2187 // |[2]--> | /**| | 2188 // | |/[4]| | 2189 // |------------| | 2190 // | [1] | [6] | 2191 // |------------| | 2192 // | |\[5]| | 2193 // |[3]--> | \**| | 2194 // | | \*| | 2195 // | | \| | 2196 // | +----|---------+ 2197 // | | 2198 // +------------+ 2199 // 2200 // [1] Selected item, cursor position ('position') 2201 // [2] Upper navigation area rectangle ('navAreaRectAbove') 2202 // [3] Lower navigation area rectangle ('navAreaRectBelow') 2203 // [4] Upper navigation area 2204 // [5] Lower navigation area 2205 // [6] Submenu 2206 // 2207 // The rectangles are used to calculate if the cursor is in the actual 2208 // navigation area (see _UpdateStateOpenSelect()). 2209 2210 if (fSelected == NULL) 2211 return; 2212 2213 BMenu* submenu = fSelected->Submenu(); 2214 2215 if (submenu != NULL) { 2216 BRect menuBounds = ConvertToScreen(Bounds()); 2217 2218 BRect submenuBounds; 2219 if (fSelected->Submenu()->LockLooper()) { 2220 submenuBounds = fSelected->Submenu()->ConvertToScreen( 2221 fSelected->Submenu()->Bounds()); 2222 fSelected->Submenu()->UnlockLooper(); 2223 } 2224 2225 if (menuBounds.left < submenuBounds.left) { 2226 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD, 2227 submenuBounds.top, menuBounds.right, 2228 position.y); 2229 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD, 2230 position.y, menuBounds.right, 2231 submenuBounds.bottom); 2232 } else { 2233 navAreaRectAbove.Set(menuBounds.left, 2234 submenuBounds.top, position.x - NAV_AREA_THRESHOLD, 2235 position.y); 2236 navAreaRectBelow.Set(menuBounds.left, 2237 position.y, position.x - NAV_AREA_THRESHOLD, 2238 submenuBounds.bottom); 2239 } 2240 } else { 2241 navAreaRectAbove = BRect(); 2242 navAreaRectBelow = BRect(); 2243 } 2244 } 2245 2246 2247 void 2248 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position, 2249 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime, 2250 bigtime_t& navigationAreaTime) 2251 { 2252 if (fState == MENU_STATE_CLOSED) 2253 return; 2254 2255 if (item != fSelected) { 2256 if (navigationAreaTime == 0) 2257 navigationAreaTime = system_time(); 2258 2259 position = ConvertToScreen(position); 2260 2261 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position); 2262 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position); 2263 2264 if (fSelected == NULL 2265 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) { 2266 _SelectItem(item, false); 2267 navAreaRectAbove = BRect(); 2268 navAreaRectBelow = BRect(); 2269 selectedTime = system_time(); 2270 navigationAreaTime = 0; 2271 return; 2272 } 2273 2274 bool isLeft = ConvertFromScreen(navAreaRectAbove).left == 0; 2275 BPoint p1, p2; 2276 2277 if (inNavAreaRectAbove) { 2278 if (!isLeft) { 2279 p1 = navAreaRectAbove.LeftBottom(); 2280 p2 = navAreaRectAbove.RightTop(); 2281 } else { 2282 p2 = navAreaRectAbove.RightBottom(); 2283 p1 = navAreaRectAbove.LeftTop(); 2284 } 2285 } else { 2286 if (!isLeft) { 2287 p2 = navAreaRectBelow.LeftTop(); 2288 p1 = navAreaRectBelow.RightBottom(); 2289 } else { 2290 p1 = navAreaRectBelow.RightTop(); 2291 p2 = navAreaRectBelow.LeftBottom(); 2292 } 2293 } 2294 bool inNavArea = 2295 (p1.y - p2.y) * position.x + (p2.x - p1.x) * position.y 2296 + (p1.x - p2.x) * p1.y + (p2.y - p1.y) * p1.x >= 0; 2297 2298 bigtime_t systime = system_time(); 2299 2300 if (!inNavArea || (navigationAreaTime > 0 && systime - 2301 navigationAreaTime > kNavigationAreaTimeout)) { 2302 // Don't delay opening of submenu if the user had 2303 // to wait for the navigation area timeout anyway 2304 _SelectItem(item, inNavArea); 2305 2306 if (inNavArea) { 2307 _UpdateNavigationArea(position, navAreaRectAbove, 2308 navAreaRectBelow); 2309 } else { 2310 navAreaRectAbove = BRect(); 2311 navAreaRectBelow = BRect(); 2312 } 2313 2314 selectedTime = system_time(); 2315 navigationAreaTime = 0; 2316 } 2317 } else if (fSelected->Submenu() != NULL && 2318 system_time() - selectedTime > kOpenSubmenuDelay) { 2319 _SelectItem(fSelected, true); 2320 2321 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) { 2322 position = ConvertToScreen(position); 2323 _UpdateNavigationArea(position, navAreaRectAbove, 2324 navAreaRectBelow); 2325 } 2326 } 2327 2328 if (fState != MENU_STATE_TRACKING) 2329 fState = MENU_STATE_TRACKING; 2330 } 2331 2332 2333 void 2334 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where, 2335 const uint32& buttons) 2336 { 2337 if (fState == MENU_STATE_CLOSED) 2338 return; 2339 2340 if (buttons != 0 && _IsStickyMode()) { 2341 if (item == NULL) { 2342 if (item != fSelected && LockLooper()) { 2343 _SelectItem(item, false); 2344 UnlockLooper(); 2345 } 2346 fState = MENU_STATE_CLOSED; 2347 } else 2348 _SetStickyMode(false); 2349 } else if (buttons == 0 && !_IsStickyMode()) { 2350 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 2351 _SetStickyMode(true); 2352 fExtraRect = NULL; 2353 // Setting this to NULL will prevent this code 2354 // to be executed next time 2355 } else { 2356 if (item != fSelected && LockLooper()) { 2357 _SelectItem(item, false); 2358 UnlockLooper(); 2359 } 2360 fState = MENU_STATE_CLOSED; 2361 } 2362 } 2363 } 2364 2365 2366 bool 2367 BMenu::_AddItem(BMenuItem* item, int32 index) 2368 { 2369 ASSERT(item != NULL); 2370 if (index < 0 || index > fItems.CountItems()) 2371 return false; 2372 2373 if (item->IsMarked()) 2374 _ItemMarked(item); 2375 2376 if (!fItems.AddItem(item, index)) 2377 return false; 2378 2379 // install the item on the supermenu's window 2380 // or onto our window, if we are a root menu 2381 BWindow* window = NULL; 2382 if (Superitem() != NULL) 2383 window = Superitem()->fWindow; 2384 else 2385 window = Window(); 2386 if (window != NULL) 2387 item->Install(window); 2388 2389 item->SetSuper(this); 2390 return true; 2391 } 2392 2393 2394 bool 2395 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item, 2396 bool deleteItems) 2397 { 2398 bool success = false; 2399 bool invalidateLayout = false; 2400 2401 bool locked = LockLooper(); 2402 BWindow* window = Window(); 2403 2404 // The plan is simple: If we're given a BMenuItem directly, we use it 2405 // and ignore index and count. Otherwise, we use them instead. 2406 if (item != NULL) { 2407 if (fItems.RemoveItem(item)) { 2408 if (item == fSelected && window != NULL) 2409 _SelectItem(NULL); 2410 item->Uninstall(); 2411 item->SetSuper(NULL); 2412 if (deleteItems) 2413 delete item; 2414 success = invalidateLayout = true; 2415 } 2416 } else { 2417 // We iterate backwards because it's simpler 2418 int32 i = std::min(index + count - 1, fItems.CountItems() - 1); 2419 // NOTE: the range check for "index" is done after 2420 // calculating the last index to be removed, so 2421 // that the range is not "shifted" unintentionally 2422 index = std::max((int32)0, index); 2423 for (; i >= index; i--) { 2424 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 2425 if (item != NULL) { 2426 if (fItems.RemoveItem(i)) { 2427 if (item == fSelected && window != NULL) 2428 _SelectItem(NULL); 2429 item->Uninstall(); 2430 item->SetSuper(NULL); 2431 if (deleteItems) 2432 delete item; 2433 success = true; 2434 invalidateLayout = true; 2435 } else { 2436 // operation not entirely successful 2437 success = false; 2438 break; 2439 } 2440 } 2441 } 2442 } 2443 2444 if (invalidateLayout) { 2445 InvalidateLayout(); 2446 if (locked && window != NULL) { 2447 _LayoutItems(0); 2448 _UpdateWindowViewSize(false); 2449 Invalidate(); 2450 } 2451 } 2452 2453 if (locked) 2454 UnlockLooper(); 2455 2456 return success; 2457 } 2458 2459 2460 bool 2461 BMenu::_RelayoutIfNeeded() 2462 { 2463 if (!fUseCachedMenuLayout) { 2464 fUseCachedMenuLayout = true; 2465 _CacheFontInfo(); 2466 _LayoutItems(0); 2467 _UpdateWindowViewSize(false); 2468 return true; 2469 } 2470 return false; 2471 } 2472 2473 2474 void 2475 BMenu::_LayoutItems(int32 index) 2476 { 2477 _CalcTriggers(); 2478 2479 float width; 2480 float height; 2481 _ComputeLayout(index, fResizeToFit, true, &width, &height); 2482 2483 if (fResizeToFit) 2484 ResizeTo(width, height); 2485 } 2486 2487 2488 BSize 2489 BMenu::_ValidatePreferredSize() 2490 { 2491 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode() 2492 != fLayoutData->lastResizingMode) { 2493 _ComputeLayout(0, true, false, NULL, NULL); 2494 ResetLayoutInvalidation(); 2495 } 2496 2497 return fLayoutData->preferred; 2498 } 2499 2500 2501 void 2502 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 2503 float* _width, float* _height) 2504 { 2505 // TODO: Take "bestFit", "moveItems", "index" into account, 2506 // Recalculate only the needed items, 2507 // not the whole layout every time 2508 2509 fLayoutData->lastResizingMode = ResizingMode(); 2510 2511 BRect frame; 2512 switch (fLayout) { 2513 case B_ITEMS_IN_COLUMN: 2514 { 2515 BRect parentFrame; 2516 BRect* overrideFrame = NULL; 2517 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) { 2518 // When the menu is modified while it's open, we get here in a 2519 // situation where trying to lock the looper would deadlock 2520 // (the window is locked waiting for the menu to terminate). 2521 // In that case, just give up on getting the supermenu bounds 2522 // and keep the menu at the current width and position. 2523 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) { 2524 parentFrame = Supermenu()->Bounds(); 2525 Supermenu()->UnlockLooper(); 2526 overrideFrame = &parentFrame; 2527 } 2528 } 2529 2530 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame, 2531 frame); 2532 break; 2533 } 2534 2535 case B_ITEMS_IN_ROW: 2536 _ComputeRowLayout(index, bestFit, moveItems, frame); 2537 break; 2538 2539 case B_ITEMS_IN_MATRIX: 2540 _ComputeMatrixLayout(frame); 2541 break; 2542 } 2543 2544 // change width depending on resize mode 2545 BSize size; 2546 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 2547 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL) 2548 size.width = Bounds().Width() - fPad.right; 2549 else if (Parent() != NULL) 2550 size.width = Parent()->Frame().Width(); 2551 else if (Window() != NULL) 2552 size.width = Window()->Frame().Width(); 2553 else 2554 size.width = Bounds().Width(); 2555 } else 2556 size.width = frame.Width(); 2557 2558 size.height = frame.Height(); 2559 2560 if (_width) 2561 *_width = size.width; 2562 2563 if (_height) 2564 *_height = size.height; 2565 2566 if (bestFit) 2567 fLayoutData->preferred = size; 2568 2569 if (moveItems) 2570 fUseCachedMenuLayout = true; 2571 } 2572 2573 2574 void 2575 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 2576 BRect* overrideFrame, BRect& frame) 2577 { 2578 bool command = false; 2579 bool control = false; 2580 bool shift = false; 2581 bool option = false; 2582 bool submenu = false; 2583 2584 if (index > 0) 2585 frame = ItemAt(index - 1)->Frame(); 2586 else if (overrideFrame != NULL) 2587 frame.Set(0, 0, overrideFrame->right, -1); 2588 else 2589 frame.Set(0, 0, 0, -1); 2590 2591 BFont font; 2592 GetFont(&font); 2593 2594 // Loop over all items to set their top, bottom and left coordinates, 2595 // all while computing the width of the menu 2596 for (; index < fItems.CountItems(); index++) { 2597 BMenuItem* item = ItemAt(index); 2598 2599 float width; 2600 float height; 2601 item->GetContentSize(&width, &height); 2602 2603 if (item->fModifiers && item->fShortcutChar) { 2604 width += font.Size(); 2605 if ((item->fModifiers & B_COMMAND_KEY) != 0) 2606 command = true; 2607 2608 if ((item->fModifiers & B_CONTROL_KEY) != 0) 2609 control = true; 2610 2611 if ((item->fModifiers & B_SHIFT_KEY) != 0) 2612 shift = true; 2613 2614 if ((item->fModifiers & B_OPTION_KEY) != 0) 2615 option = true; 2616 } 2617 2618 item->fBounds.left = 0.0f; 2619 item->fBounds.top = frame.bottom + 1.0f; 2620 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2621 + fPad.bottom; 2622 2623 if (item->fSubmenu != NULL) 2624 submenu = true; 2625 2626 frame.right = std::max(frame.right, width + fPad.left + fPad.right); 2627 frame.bottom = item->fBounds.bottom; 2628 } 2629 2630 // Compute the extra space needed for shortcuts and submenus 2631 if (command) { 2632 frame.right 2633 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1; 2634 } 2635 if (control) { 2636 frame.right 2637 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1; 2638 } 2639 if (option) { 2640 frame.right 2641 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1; 2642 } 2643 if (shift) { 2644 frame.right 2645 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1; 2646 } 2647 if (submenu) { 2648 frame.right += ItemAt(0)->Frame().Height() / 2; 2649 fHasSubmenus = true; 2650 } else { 2651 fHasSubmenus = false; 2652 } 2653 2654 if (fMaxContentWidth > 0) 2655 frame.right = std::min(frame.right, fMaxContentWidth); 2656 2657 frame.top = 0; 2658 frame.right = ceilf(frame.right); 2659 2660 // Finally update the "right" coordinate of all items 2661 if (moveItems) { 2662 for (int32 i = 0; i < fItems.CountItems(); i++) 2663 ItemAt(i)->fBounds.right = frame.right; 2664 } 2665 } 2666 2667 2668 void 2669 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2670 BRect& frame) 2671 { 2672 font_height fh; 2673 GetFontHeight(&fh); 2674 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2675 + fPad.bottom)); 2676 2677 for (int32 i = 0; i < fItems.CountItems(); i++) { 2678 BMenuItem* item = ItemAt(i); 2679 2680 float width, height; 2681 item->GetContentSize(&width, &height); 2682 2683 item->fBounds.left = frame.right; 2684 item->fBounds.top = 0.0f; 2685 item->fBounds.right = item->fBounds.left + width + fPad.left 2686 + fPad.right; 2687 2688 frame.right = item->Frame().right + 1.0f; 2689 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom); 2690 } 2691 2692 if (moveItems) { 2693 for (int32 i = 0; i < fItems.CountItems(); i++) 2694 ItemAt(i)->fBounds.bottom = frame.bottom; 2695 } 2696 2697 if (bestFit) 2698 frame.right = ceilf(frame.right); 2699 else 2700 frame.right = Bounds().right; 2701 } 2702 2703 2704 void 2705 BMenu::_ComputeMatrixLayout(BRect &frame) 2706 { 2707 frame.Set(0, 0, 0, 0); 2708 for (int32 i = 0; i < CountItems(); i++) { 2709 BMenuItem* item = ItemAt(i); 2710 if (item != NULL) { 2711 frame.left = std::min(frame.left, item->Frame().left); 2712 frame.right = std::max(frame.right, item->Frame().right); 2713 frame.top = std::min(frame.top, item->Frame().top); 2714 frame.bottom = std::max(frame.bottom, item->Frame().bottom); 2715 } 2716 } 2717 } 2718 2719 2720 void 2721 BMenu::LayoutInvalidated(bool descendants) 2722 { 2723 fUseCachedMenuLayout = false; 2724 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 2725 } 2726 2727 2728 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2729 BPoint 2730 BMenu::ScreenLocation() 2731 { 2732 BMenu* superMenu = Supermenu(); 2733 BMenuItem* superItem = Superitem(); 2734 2735 if (superMenu == NULL || superItem == NULL) { 2736 debugger("BMenu can't determine where to draw." 2737 "Override BMenu::ScreenLocation() to determine location."); 2738 } 2739 2740 BPoint point; 2741 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2742 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2743 else 2744 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2745 2746 superMenu->ConvertToScreen(&point); 2747 2748 return point; 2749 } 2750 2751 2752 BRect 2753 BMenu::_CalcFrame(BPoint where, bool* scrollOn) 2754 { 2755 // TODO: Improve me 2756 BRect bounds = Bounds(); 2757 BRect frame = bounds.OffsetToCopy(where); 2758 2759 BScreen screen(Window()); 2760 BRect screenFrame = screen.Frame(); 2761 2762 BMenu* superMenu = Supermenu(); 2763 BMenuItem* superItem = Superitem(); 2764 2765 // Reset frame shifted state since this menu is being redrawn 2766 fExtraMenuData->frameShiftedLeft = false; 2767 2768 // TODO: Horrible hack: 2769 // When added to a BMenuField, a BPopUpMenu is the child of 2770 // a _BMCMenuBar_ to "fake" the menu hierarchy 2771 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL; 2772 2773 // Offset the menu field menu window left by the width of the checkmark 2774 // so that the text when the menu is closed lines up with the text when 2775 // the menu is open. 2776 if (inMenuField) 2777 frame.OffsetBy(-8.0f, 0.0f); 2778 2779 if (superMenu == NULL || superItem == NULL || inMenuField) { 2780 // just move the window on screen 2781 if (frame.bottom > screenFrame.bottom) 2782 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2783 else if (frame.top < screenFrame.top) 2784 frame.OffsetBy(0, -frame.top); 2785 2786 if (frame.right > screenFrame.right) { 2787 frame.OffsetBy(screenFrame.right - frame.right, 0); 2788 fExtraMenuData->frameShiftedLeft = true; 2789 } 2790 else if (frame.left < screenFrame.left) 2791 frame.OffsetBy(-frame.left, 0); 2792 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2793 if (frame.right > screenFrame.right 2794 || superMenu->fExtraMenuData->frameShiftedLeft) { 2795 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2796 fExtraMenuData->frameShiftedLeft = true; 2797 } 2798 2799 if (frame.left < 0) 2800 frame.OffsetBy(-frame.left + 6, 0); 2801 2802 if (frame.bottom > screenFrame.bottom) 2803 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2804 } else { 2805 if (frame.bottom > screenFrame.bottom) { 2806 float spaceBelow = screenFrame.bottom - frame.top; 2807 float spaceOver = frame.top - screenFrame.top 2808 - superItem->Frame().Height(); 2809 if (spaceOver > spaceBelow) { 2810 frame.OffsetBy(0, -superItem->Frame().Height() 2811 - frame.Height() - 3); 2812 } 2813 } 2814 2815 if (frame.right > screenFrame.right) 2816 frame.OffsetBy(screenFrame.right - frame.right, 0); 2817 } 2818 2819 if (scrollOn != NULL) { 2820 // basically, if this returns false, it means 2821 // that the menu frame won't fit completely inside the screen 2822 // TODO: Scrolling will currently only work up/down, 2823 // not left/right 2824 *scrollOn = screenFrame.top > frame.top 2825 || screenFrame.bottom < frame.bottom; 2826 } 2827 2828 return frame; 2829 } 2830 2831 2832 void 2833 BMenu::DrawItems(BRect updateRect) 2834 { 2835 int32 itemCount = fItems.CountItems(); 2836 for (int32 i = 0; i < itemCount; i++) { 2837 BMenuItem* item = ItemAt(i); 2838 if (item->Frame().Intersects(updateRect)) 2839 item->Draw(); 2840 } 2841 } 2842 2843 2844 int 2845 BMenu::_State(BMenuItem** item) const 2846 { 2847 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2848 return fState; 2849 2850 if (fSelected != NULL && fSelected->Submenu() != NULL) 2851 return fSelected->Submenu()->_State(item); 2852 2853 return fState; 2854 } 2855 2856 2857 void 2858 BMenu::_InvokeItem(BMenuItem* item, bool now) 2859 { 2860 if (!item->IsEnabled()) 2861 return; 2862 2863 // Do the "selected" animation 2864 // TODO: Doesn't work. This is supposed to highlight 2865 // and dehighlight the item, works on beos but not on haiku. 2866 if (!item->Submenu() && LockLooper()) { 2867 snooze(50000); 2868 item->Select(true); 2869 Window()->UpdateIfNeeded(); 2870 snooze(50000); 2871 item->Select(false); 2872 Window()->UpdateIfNeeded(); 2873 snooze(50000); 2874 item->Select(true); 2875 Window()->UpdateIfNeeded(); 2876 snooze(50000); 2877 item->Select(false); 2878 Window()->UpdateIfNeeded(); 2879 UnlockLooper(); 2880 } 2881 2882 // Lock the root menu window before calling BMenuItem::Invoke() 2883 BMenu* parent = this; 2884 BMenu* rootMenu = NULL; 2885 do { 2886 rootMenu = parent; 2887 parent = rootMenu->Supermenu(); 2888 } while (parent != NULL); 2889 2890 if (rootMenu->LockLooper()) { 2891 item->Invoke(); 2892 rootMenu->UnlockLooper(); 2893 } 2894 } 2895 2896 2897 bool 2898 BMenu::_OverSuper(BPoint location) 2899 { 2900 if (!Supermenu()) 2901 return false; 2902 2903 return fSuperbounds.Contains(location); 2904 } 2905 2906 2907 bool 2908 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc) 2909 { 2910 if (item == NULL) 2911 return false; 2912 2913 BMenu* subMenu = item->Submenu(); 2914 if (subMenu == NULL || subMenu->Window() == NULL) 2915 return false; 2916 2917 // assume that loc is in screen coordinates 2918 if (subMenu->Window()->Frame().Contains(loc)) 2919 return true; 2920 2921 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2922 } 2923 2924 2925 BMenuWindow* 2926 BMenu::_MenuWindow() 2927 { 2928 #if USE_CACHED_MENUWINDOW 2929 if (fCachedMenuWindow == NULL) { 2930 char windowName[64]; 2931 snprintf(windowName, 64, "%s cached menu", Name()); 2932 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2933 } 2934 #endif 2935 return fCachedMenuWindow; 2936 } 2937 2938 2939 void 2940 BMenu::_DeleteMenuWindow() 2941 { 2942 if (fCachedMenuWindow != NULL) { 2943 fCachedMenuWindow->Lock(); 2944 fCachedMenuWindow->Quit(); 2945 fCachedMenuWindow = NULL; 2946 } 2947 } 2948 2949 2950 BMenuItem* 2951 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2952 { 2953 // TODO: Take "slop" into account ? 2954 2955 // if the point doesn't lie within the menu's 2956 // bounds, bail out immediately 2957 if (!Bounds().Contains(where)) 2958 return NULL; 2959 2960 int32 itemCount = CountItems(); 2961 for (int32 i = 0; i < itemCount; i++) { 2962 BMenuItem* item = ItemAt(i); 2963 if (item->Frame().Contains(where) 2964 && dynamic_cast<BSeparatorItem*>(item) == NULL) { 2965 return item; 2966 } 2967 } 2968 2969 return NULL; 2970 } 2971 2972 2973 BRect 2974 BMenu::_Superbounds() const 2975 { 2976 return fSuperbounds; 2977 } 2978 2979 2980 void 2981 BMenu::_CacheFontInfo() 2982 { 2983 font_height fh; 2984 GetFontHeight(&fh); 2985 fAscent = fh.ascent; 2986 fDescent = fh.descent; 2987 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2988 } 2989 2990 2991 void 2992 BMenu::_ItemMarked(BMenuItem* item) 2993 { 2994 if (IsRadioMode()) { 2995 for (int32 i = 0; i < CountItems(); i++) { 2996 if (ItemAt(i) != item) 2997 ItemAt(i)->SetMarked(false); 2998 } 2999 } 3000 3001 if (IsLabelFromMarked() && Superitem() != NULL) 3002 Superitem()->SetLabel(item->Label()); 3003 } 3004 3005 3006 void 3007 BMenu::_Install(BWindow* target) 3008 { 3009 for (int32 i = 0; i < CountItems(); i++) 3010 ItemAt(i)->Install(target); 3011 } 3012 3013 3014 void 3015 BMenu::_Uninstall() 3016 { 3017 for (int32 i = 0; i < CountItems(); i++) 3018 ItemAt(i)->Uninstall(); 3019 } 3020 3021 3022 void 3023 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem, 3024 bool keyDown) 3025 { 3026 // Avoid deselecting and then reselecting the same item 3027 // which would cause flickering 3028 if (item != fSelected) { 3029 if (fSelected != NULL) { 3030 fSelected->Select(false); 3031 BMenu* subMenu = fSelected->Submenu(); 3032 if (subMenu != NULL && subMenu->Window() != NULL) 3033 subMenu->_Hide(); 3034 } 3035 3036 fSelected = item; 3037 if (fSelected != NULL) 3038 fSelected->Select(true); 3039 } 3040 3041 if (fSelected != NULL && showSubmenu) { 3042 BMenu* subMenu = fSelected->Submenu(); 3043 if (subMenu != NULL && subMenu->Window() == NULL) { 3044 if (!subMenu->_Show(selectFirstItem, keyDown)) { 3045 // something went wrong, deselect the item 3046 fSelected->Select(false); 3047 fSelected = NULL; 3048 } 3049 } 3050 } 3051 } 3052 3053 3054 bool 3055 BMenu::_SelectNextItem(BMenuItem* item, bool forward) 3056 { 3057 if (CountItems() == 0) // cannot select next item in an empty menu 3058 return false; 3059 3060 BMenuItem* nextItem = _NextItem(item, forward); 3061 if (nextItem == NULL) 3062 return false; 3063 3064 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL); 3065 3066 if (LockLooper()) { 3067 be_app->ObscureCursor(); 3068 UnlockLooper(); 3069 } 3070 3071 return true; 3072 } 3073 3074 3075 BMenuItem* 3076 BMenu::_NextItem(BMenuItem* item, bool forward) const 3077 { 3078 const int32 numItems = fItems.CountItems(); 3079 if (numItems == 0) 3080 return NULL; 3081 3082 int32 index = fItems.IndexOf(item); 3083 int32 loopCount = numItems; 3084 while (--loopCount) { 3085 // Cycle through menu items in the given direction... 3086 if (forward) 3087 index++; 3088 else 3089 index--; 3090 3091 // ... wrap around... 3092 if (index < 0) 3093 index = numItems - 1; 3094 else if (index >= numItems) 3095 index = 0; 3096 3097 // ... and return the first suitable item found. 3098 BMenuItem* nextItem = ItemAt(index); 3099 if (nextItem->IsEnabled()) 3100 return nextItem; 3101 } 3102 3103 // If no other suitable item was found, return NULL. 3104 return NULL; 3105 } 3106 3107 3108 void 3109 BMenu::_SetStickyMode(bool sticky) 3110 { 3111 if (fStickyMode == sticky) 3112 return; 3113 3114 fStickyMode = sticky; 3115 3116 if (fSuper != NULL) { 3117 // propagate the status to the super menu 3118 fSuper->_SetStickyMode(sticky); 3119 } else { 3120 // TODO: Ugly hack, but it needs to be done in this method 3121 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this); 3122 if (sticky && menuBar != NULL && menuBar->LockLooper()) { 3123 // If we are switching to sticky mode, 3124 // steal the focus from the current focus view 3125 // (needed to handle keyboard navigation) 3126 menuBar->_StealFocus(); 3127 menuBar->UnlockLooper(); 3128 } 3129 } 3130 } 3131 3132 3133 bool 3134 BMenu::_IsStickyMode() const 3135 { 3136 return fStickyMode; 3137 } 3138 3139 3140 void 3141 BMenu::_GetShiftKey(uint32 &value) const 3142 { 3143 // TODO: Move into init_interface_kit(). 3144 // Currently we can't do that, as get_modifier_key() blocks forever 3145 // when called on input_server initialization, since it tries 3146 // to send a synchronous message to itself (input_server is 3147 // a BApplication) 3148 3149 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK) 3150 value = 0x4b; 3151 } 3152 3153 3154 void 3155 BMenu::_GetControlKey(uint32 &value) const 3156 { 3157 // TODO: Move into init_interface_kit(). 3158 // Currently we can't do that, as get_modifier_key() blocks forever 3159 // when called on input_server initialization, since it tries 3160 // to send a synchronous message to itself (input_server is 3161 // a BApplication) 3162 3163 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK) 3164 value = 0x5c; 3165 } 3166 3167 3168 void 3169 BMenu::_GetCommandKey(uint32 &value) const 3170 { 3171 // TODO: Move into init_interface_kit(). 3172 // Currently we can't do that, as get_modifier_key() blocks forever 3173 // when called on input_server initialization, since it tries 3174 // to send a synchronous message to itself (input_server is 3175 // a BApplication) 3176 3177 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK) 3178 value = 0x66; 3179 } 3180 3181 3182 void 3183 BMenu::_GetOptionKey(uint32 &value) const 3184 { 3185 // TODO: Move into init_interface_kit(). 3186 // Currently we can't do that, as get_modifier_key() blocks forever 3187 // when called on input_server initialization, since it tries 3188 // to send a synchronous message to itself (input_server is 3189 // a BApplication) 3190 3191 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK) 3192 value = 0x5d; 3193 } 3194 3195 3196 void 3197 BMenu::_GetMenuKey(uint32 &value) const 3198 { 3199 // TODO: Move into init_interface_kit(). 3200 // Currently we can't do that, as get_modifier_key() blocks forever 3201 // when called on input_server initialization, since it tries 3202 // to send a synchronous message to itself (input_server is 3203 // a BApplication) 3204 3205 if (get_modifier_key(B_MENU_KEY, &value) != B_OK) 3206 value = 0x68; 3207 } 3208 3209 3210 void 3211 BMenu::_CalcTriggers() 3212 { 3213 BPrivate::TriggerList triggerList; 3214 3215 // Gathers the existing triggers set by the user 3216 for (int32 i = 0; i < CountItems(); i++) { 3217 char trigger = ItemAt(i)->Trigger(); 3218 if (trigger != 0) 3219 triggerList.AddTrigger(trigger); 3220 } 3221 3222 // Set triggers for items which don't have one yet 3223 for (int32 i = 0; i < CountItems(); i++) { 3224 BMenuItem* item = ItemAt(i); 3225 if (item->Trigger() == 0) { 3226 uint32 trigger; 3227 int32 index; 3228 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 3229 item->SetAutomaticTrigger(index, trigger); 3230 } 3231 } 3232 } 3233 3234 3235 bool 3236 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, 3237 BPrivate::TriggerList& triggers) 3238 { 3239 if (title == NULL) 3240 return false; 3241 3242 index = 0; 3243 uint32 c; 3244 const char* nextCharacter, *character; 3245 3246 // two runs: first we look out for alphanumeric ASCII characters 3247 nextCharacter = title; 3248 character = nextCharacter; 3249 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3250 if (!(c < 128 && BUnicodeChar::IsAlNum(c)) || triggers.HasTrigger(c)) { 3251 character = nextCharacter; 3252 continue; 3253 } 3254 trigger = BUnicodeChar::ToLower(c); 3255 index = (int32)(character - title); 3256 return triggers.AddTrigger(c); 3257 } 3258 3259 // then, if we still haven't found something, we accept anything 3260 nextCharacter = title; 3261 character = nextCharacter; 3262 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) { 3263 if (BUnicodeChar::IsSpace(c) || triggers.HasTrigger(c)) { 3264 character = nextCharacter; 3265 continue; 3266 } 3267 trigger = BUnicodeChar::ToLower(c); 3268 index = (int32)(character - title); 3269 return triggers.AddTrigger(c); 3270 } 3271 3272 return false; 3273 } 3274 3275 3276 void 3277 BMenu::_UpdateWindowViewSize(const bool &move) 3278 { 3279 BMenuWindow* window = static_cast<BMenuWindow*>(Window()); 3280 if (window == NULL) 3281 return; 3282 3283 if (dynamic_cast<BMenuBar*>(this) != NULL) 3284 return; 3285 3286 if (!fResizeToFit) 3287 return; 3288 3289 bool scroll = false; 3290 const BPoint screenLocation = move ? ScreenLocation() 3291 : window->Frame().LeftTop(); 3292 BRect frame = _CalcFrame(screenLocation, &scroll); 3293 ResizeTo(frame.Width(), frame.Height()); 3294 3295 if (fItems.CountItems() > 0) { 3296 if (!scroll) { 3297 if (fLayout == B_ITEMS_IN_COLUMN) 3298 window->DetachScrollers(); 3299 3300 window->ResizeTo(Bounds().Width(), Bounds().Height()); 3301 } else { 3302 3303 // Resize the window to fit the screen without overflowing the 3304 // frame, and attach scrollers to our cached BMenuWindow. 3305 BScreen screen(window); 3306 frame = frame & screen.Frame(); 3307 window->ResizeTo(Bounds().Width(), frame.Height()); 3308 3309 // we currently only support scrolling for B_ITEMS_IN_COLUMN 3310 if (fLayout == B_ITEMS_IN_COLUMN) { 3311 window->AttachScrollers(); 3312 3313 BMenuItem* selectedItem = FindMarked(); 3314 if (selectedItem != NULL) { 3315 // scroll to the selected item 3316 if (Supermenu() == NULL) { 3317 window->TryScrollTo(selectedItem->Frame().top); 3318 } else { 3319 BPoint point = selectedItem->Frame().LeftTop(); 3320 BPoint superPoint = Superitem()->Frame().LeftTop(); 3321 Supermenu()->ConvertToScreen(&superPoint); 3322 ConvertToScreen(&point); 3323 window->TryScrollTo(point.y - superPoint.y); 3324 } 3325 } 3326 } 3327 } 3328 } else { 3329 _CacheFontInfo(); 3330 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel) 3331 + fPad.left + fPad.right, 3332 fFontHeight + fPad.top + fPad.bottom); 3333 } 3334 3335 if (move) 3336 window->MoveTo(frame.LeftTop()); 3337 } 3338 3339 3340 bool 3341 BMenu::_AddDynamicItems(bool keyDown) 3342 { 3343 bool addAborted = false; 3344 if (AddDynamicItem(B_INITIAL_ADD)) { 3345 BMenuItem* superItem = Superitem(); 3346 BMenu* superMenu = Supermenu(); 3347 do { 3348 if (superMenu != NULL 3349 && !superMenu->_OkToProceed(superItem, keyDown)) { 3350 AddDynamicItem(B_ABORT); 3351 addAborted = true; 3352 break; 3353 } 3354 } while (AddDynamicItem(B_PROCESSING)); 3355 } 3356 3357 return addAborted; 3358 } 3359 3360 3361 bool 3362 BMenu::_OkToProceed(BMenuItem* item, bool keyDown) 3363 { 3364 BPoint where; 3365 uint32 buttons; 3366 GetMouse(&where, &buttons, false); 3367 bool stickyMode = _IsStickyMode(); 3368 // Quit if user clicks the mouse button in sticky mode 3369 // or releases the mouse button in nonsticky mode 3370 // or moves the pointer over another item 3371 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 3372 // BeOS seems to do something similar. This could also be a bug in 3373 // Deskbar, though. 3374 if ((buttons != 0 && stickyMode) 3375 || ((dynamic_cast<BMenuBar*>(this) == NULL 3376 && (buttons == 0 && !stickyMode)) 3377 || ((_HitTestItems(where) != item) && !keyDown))) { 3378 return false; 3379 } 3380 3381 return true; 3382 } 3383 3384 3385 bool 3386 BMenu::_CustomTrackingWantsToQuit() 3387 { 3388 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 3389 && fExtraMenuData->trackingState != NULL) { 3390 return fExtraMenuData->trackingHook(this, 3391 fExtraMenuData->trackingState); 3392 } 3393 3394 return false; 3395 } 3396 3397 3398 void 3399 BMenu::_QuitTracking(bool onlyThis) 3400 { 3401 _SelectItem(NULL); 3402 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this)) 3403 menuBar->_RestoreFocus(); 3404 3405 fState = MENU_STATE_CLOSED; 3406 3407 if (!onlyThis) { 3408 // Close the whole menu hierarchy 3409 if (Supermenu() != NULL) 3410 Supermenu()->fState = MENU_STATE_CLOSED; 3411 3412 if (_IsStickyMode()) 3413 _SetStickyMode(false); 3414 3415 if (LockLooper()) { 3416 be_app->ShowCursor(); 3417 UnlockLooper(); 3418 } 3419 } 3420 3421 _Hide(); 3422 } 3423 3424 3425 // #pragma mark - menu_info functions 3426 3427 3428 // TODO: Maybe the following two methods would fit better into 3429 // InterfaceDefs.cpp 3430 // In R5, they do all the work client side, we let the app_server handle the 3431 // details. 3432 status_t 3433 set_menu_info(menu_info* info) 3434 { 3435 if (!info) 3436 return B_BAD_VALUE; 3437 3438 BPrivate::AppServerLink link; 3439 link.StartMessage(AS_SET_MENU_INFO); 3440 link.Attach<menu_info>(*info); 3441 3442 status_t status = B_ERROR; 3443 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3444 BMenu::sMenuInfo = *info; 3445 // Update also the local copy, in case anyone relies on it 3446 3447 return status; 3448 } 3449 3450 3451 status_t 3452 get_menu_info(menu_info* info) 3453 { 3454 if (!info) 3455 return B_BAD_VALUE; 3456 3457 BPrivate::AppServerLink link; 3458 link.StartMessage(AS_GET_MENU_INFO); 3459 3460 status_t status = B_ERROR; 3461 if (link.FlushWithReply(status) == B_OK && status == B_OK) 3462 link.Read<menu_info>(info); 3463 3464 return status; 3465 } 3466 3467 3468 extern "C" void 3469 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)( 3470 BMenu* menu, bool descendants) 3471 { 3472 menu->InvalidateLayout(); 3473 } 3474