1 /* 2 * Copyright 2001-2008, Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Stefano Ceccherini (stefano.ceccherini@gmail.com) 8 * Rene Gollent (anevilyak@gmail.com) 9 */ 10 11 #include <new> 12 #include <ctype.h> 13 #include <string.h> 14 15 #include <Debug.h> 16 #include <File.h> 17 #include <FindDirectory.h> 18 #include <Layout.h> 19 #include <LayoutUtils.h> 20 #include <Menu.h> 21 #include <MenuBar.h> 22 #include <MenuItem.h> 23 #include <Messenger.h> 24 #include <Path.h> 25 #include <PropertyInfo.h> 26 #include <Screen.h> 27 #include <Window.h> 28 29 #include <AppServerLink.h> 30 #include <BMCPrivate.h> 31 #include <MenuPrivate.h> 32 #include <MenuWindow.h> 33 #include <ServerProtocol.h> 34 35 #include "utf8_functions.h" 36 37 #define USE_CACHED_MENUWINDOW 1 38 39 using std::nothrow; 40 using BPrivate::BMenuWindow; 41 42 namespace BPrivate { 43 44 class TriggerList { 45 public: 46 TriggerList() {} 47 ~TriggerList() {} 48 49 // TODO: make this work with Unicode characters! 50 51 bool HasTrigger(uint32 c) 52 { return fList.HasItem((void*)tolower(c)); } 53 bool AddTrigger(uint32 c) 54 { return fList.AddItem((void*)tolower(c)); } 55 56 private: 57 BList fList; 58 }; 59 60 61 class ExtraMenuData { 62 public: 63 menu_tracking_hook trackingHook; 64 void *trackingState; 65 66 ExtraMenuData(menu_tracking_hook func, void *state) 67 { 68 trackingHook = func; 69 trackingState = state; 70 } 71 }; 72 73 74 } // namespace BPrivate 75 76 77 menu_info BMenu::sMenuInfo; 78 bool BMenu::sAltAsCommandKey; 79 80 81 static property_info sPropList[] = { 82 { "Enabled", { B_GET_PROPERTY, 0 }, 83 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false " 84 "otherwise.", 85 0, { B_BOOL_TYPE } 86 }, 87 88 { "Enabled", { B_SET_PROPERTY, 0 }, 89 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.", 90 0, { B_BOOL_TYPE } 91 }, 92 93 { "Label", { B_GET_PROPERTY, 0 }, 94 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item.", 95 0, { B_STRING_TYPE } 96 }, 97 98 { "Label", { B_SET_PROPERTY, 0 }, 99 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item.", 100 0, { B_STRING_TYPE } 101 }, 102 103 { "Mark", { B_GET_PROPERTY, 0 }, 104 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem " 105 "is marked; false otherwise.", 106 0, { B_BOOL_TYPE } 107 }, 108 109 { "Mark", { B_SET_PROPERTY, 0 }, 110 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem.", 111 0, { B_BOOL_TYPE } 112 }, 113 114 { "Menu", { B_CREATE_PROPERTY, 0 }, 115 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 116 "Adds a new menu item at the specified index with the text label found in \"data\" " 117 "and the int32 command found in \"what\" (used as the what field in the CMessage " 118 "sent by the item)." , 0, {}, 119 { {{{"data", B_STRING_TYPE}}} 120 } 121 }, 122 123 { "Menu", { B_DELETE_PROPERTY, 0 }, 124 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 125 "Removes the selected menu or menus.", 0, {} 126 }, 127 128 { "Menu", { }, 129 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 130 "Directs scripting message to the specified menu, first popping the current " 131 "specifier off the stack.", 0, {} 132 }, 133 134 { "MenuItem", { B_COUNT_PROPERTIES, 0 }, 135 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu.", 136 0, { B_INT32_TYPE } 137 }, 138 139 { "MenuItem", { B_CREATE_PROPERTY, 0 }, 140 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 141 "Adds a new menu item at the specified index with the text label found in \"data\" " 142 "and the int32 command found in \"what\" (used as the what field in the CMessage " 143 "sent by the item).", 0, {}, 144 { { {{"data", B_STRING_TYPE }, 145 {"be:invoke_message", B_MESSAGE_TYPE}, 146 {"what", B_INT32_TYPE}, 147 {"be:target", B_MESSENGER_TYPE}} } 148 } 149 }, 150 151 { "MenuItem", { B_DELETE_PROPERTY, 0 }, 152 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 153 "Removes the specified menu item from its parent menu." 154 }, 155 156 { "MenuItem", { B_EXECUTE_PROPERTY, 0 }, 157 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 158 "Invokes the specified menu item." 159 }, 160 161 { "MenuItem", { }, 162 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 163 "Directs scripting message to the specified menu, first popping the current " 164 "specifier off the stack." 165 }, 166 167 {} 168 }; 169 170 171 const char *kEmptyMenuLabel = "<empty>"; 172 173 174 struct BMenu::LayoutData { 175 BSize preferred; 176 }; 177 178 179 BMenu::BMenu(const char *name, menu_layout layout) 180 : BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW), 181 fChosenItem(NULL), 182 fPad(14.0f, 2.0f, 20.0f, 0.0f), 183 fSelected(NULL), 184 fCachedMenuWindow(NULL), 185 fSuper(NULL), 186 fSuperitem(NULL), 187 fAscent(-1.0f), 188 fDescent(-1.0f), 189 fFontHeight(-1.0f), 190 fState(0), 191 fLayout(layout), 192 fExtraRect(NULL), 193 fMaxContentWidth(0.0f), 194 fInitMatrixSize(NULL), 195 fExtraMenuData(NULL), 196 fTrigger(0), 197 fResizeToFit(true), 198 fUseCachedMenuLayout(false), 199 fEnabled(true), 200 fDynamicName(false), 201 fRadioMode(false), 202 fTrackNewBounds(false), 203 fStickyMode(false), 204 fIgnoreHidden(true), 205 fTriggerEnabled(true), 206 fRedrawAfterSticky(false), 207 fAttachAborted(false) 208 { 209 _InitData(NULL); 210 } 211 212 213 BMenu::BMenu(const char *name, float width, float height) 214 : BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW), 215 fChosenItem(NULL), 216 fSelected(NULL), 217 fCachedMenuWindow(NULL), 218 fSuper(NULL), 219 fSuperitem(NULL), 220 fAscent(-1.0f), 221 fDescent(-1.0f), 222 fFontHeight(-1.0f), 223 fState(0), 224 fLayout(B_ITEMS_IN_MATRIX), 225 fExtraRect(NULL), 226 fMaxContentWidth(0.0f), 227 fInitMatrixSize(NULL), 228 fExtraMenuData(NULL), 229 fTrigger(0), 230 fResizeToFit(true), 231 fUseCachedMenuLayout(false), 232 fEnabled(true), 233 fDynamicName(false), 234 fRadioMode(false), 235 fTrackNewBounds(false), 236 fStickyMode(false), 237 fIgnoreHidden(true), 238 fTriggerEnabled(true), 239 fRedrawAfterSticky(false), 240 fAttachAborted(false) 241 { 242 _InitData(NULL); 243 } 244 245 246 BMenu::~BMenu() 247 { 248 _DeleteMenuWindow(); 249 250 RemoveItems(0, CountItems(), true); 251 252 delete fInitMatrixSize; 253 delete fExtraMenuData; 254 delete fLayoutData; 255 } 256 257 258 BMenu::BMenu(BMessage *archive) 259 : BView(archive), 260 fChosenItem(NULL), 261 fPad(14.0f, 2.0f, 20.0f, 0.0f), 262 fSelected(NULL), 263 fCachedMenuWindow(NULL), 264 fSuper(NULL), 265 fSuperitem(NULL), 266 fAscent(-1.0f), 267 fDescent(-1.0f), 268 fFontHeight(-1.0f), 269 fState(0), 270 fLayout(B_ITEMS_IN_ROW), 271 fExtraRect(NULL), 272 fMaxContentWidth(0.0f), 273 fInitMatrixSize(NULL), 274 fExtraMenuData(NULL), 275 fTrigger(0), 276 fResizeToFit(true), 277 fUseCachedMenuLayout(false), 278 fEnabled(true), 279 fDynamicName(false), 280 fRadioMode(false), 281 fTrackNewBounds(false), 282 fStickyMode(false), 283 fIgnoreHidden(true), 284 fTriggerEnabled(true), 285 fRedrawAfterSticky(false), 286 fAttachAborted(false) 287 { 288 _InitData(archive); 289 } 290 291 292 BArchivable* 293 BMenu::Instantiate(BMessage* archive) 294 { 295 if (validate_instantiation(archive, "BMenu")) 296 return new (nothrow) BMenu(archive); 297 298 return NULL; 299 } 300 301 302 status_t 303 BMenu::Archive(BMessage *data, bool deep) const 304 { 305 status_t err = BView::Archive(data, deep); 306 307 if (err == B_OK && Layout() != B_ITEMS_IN_ROW) 308 err = data->AddInt32("_layout", Layout()); 309 if (err == B_OK) 310 err = data->AddBool("_rsize_to_fit", fResizeToFit); 311 if (err == B_OK) 312 err = data->AddBool("_disable", !IsEnabled()); 313 if (err == B_OK) 314 err = data->AddBool("_radio", IsRadioMode()); 315 if (err == B_OK) 316 err = data->AddBool("_trig_disabled", AreTriggersEnabled()); 317 if (err == B_OK) 318 err = data->AddBool("_dyn_label", fDynamicName); 319 if (err == B_OK) 320 err = data->AddFloat("_maxwidth", fMaxContentWidth); 321 if (err == B_OK && deep) { 322 BMenuItem *item = NULL; 323 int32 index = 0; 324 while ((item = ItemAt(index++)) != NULL) { 325 BMessage itemData; 326 item->Archive(&itemData, deep); 327 err = data->AddMessage("_items", &itemData); 328 if (err != B_OK) 329 break; 330 if (fLayout == B_ITEMS_IN_MATRIX) { 331 err = data->AddRect("_i_frames", item->fBounds); 332 } 333 } 334 } 335 336 return err; 337 } 338 339 340 void 341 BMenu::AttachedToWindow() 342 { 343 BView::AttachedToWindow(); 344 345 sAltAsCommandKey = true; 346 key_map *keys = NULL; 347 char *chars = NULL; 348 get_key_map(&keys, &chars); 349 if (keys == NULL || keys->left_command_key != 0x5d 350 || keys->right_command_key != 0x5f) 351 sAltAsCommandKey = false; 352 free(chars); 353 free(keys); 354 355 BMenuItem *superItem = Superitem(); 356 BMenu *superMenu = Supermenu(); 357 if (AddDynamicItem(B_INITIAL_ADD)) { 358 do { 359 if (superMenu != NULL && !superMenu->_OkToProceed(superItem)) { 360 AddDynamicItem(B_ABORT); 361 fAttachAborted = true; 362 break; 363 } 364 } while (AddDynamicItem(B_PROCESSING)); 365 } 366 367 if (!fAttachAborted) { 368 _CacheFontInfo(); 369 _LayoutItems(0); 370 _UpdateWindowViewSize(false); 371 } 372 } 373 374 375 void 376 BMenu::DetachedFromWindow() 377 { 378 BView::DetachedFromWindow(); 379 } 380 381 382 bool 383 BMenu::AddItem(BMenuItem *item) 384 { 385 return AddItem(item, CountItems()); 386 } 387 388 389 bool 390 BMenu::AddItem(BMenuItem *item, int32 index) 391 { 392 if (fLayout == B_ITEMS_IN_MATRIX) 393 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only " 394 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 395 396 if (!item || !_AddItem(item, index)) 397 return false; 398 399 InvalidateLayout(); 400 if (LockLooper()) { 401 if (!Window()->IsHidden()) { 402 _LayoutItems(index); 403 _UpdateWindowViewSize(false); 404 Invalidate(); 405 } 406 UnlockLooper(); 407 } 408 return true; 409 } 410 411 412 bool 413 BMenu::AddItem(BMenuItem *item, BRect frame) 414 { 415 if (fLayout != B_ITEMS_IN_MATRIX) 416 debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only " 417 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 418 419 if (!item) 420 return false; 421 422 item->fBounds = frame; 423 424 int32 index = CountItems(); 425 if (!_AddItem(item, index)) { 426 return false; 427 } 428 429 if (LockLooper()) { 430 if (!Window()->IsHidden()) { 431 _LayoutItems(index); 432 Invalidate(); 433 } 434 UnlockLooper(); 435 } 436 437 return true; 438 } 439 440 441 bool 442 BMenu::AddItem(BMenu *submenu) 443 { 444 BMenuItem *item = new (nothrow) BMenuItem(submenu); 445 if (!item) 446 return false; 447 448 if (!AddItem(item, CountItems())) { 449 item->fSubmenu = NULL; 450 delete item; 451 return false; 452 } 453 454 return true; 455 } 456 457 458 bool 459 BMenu::AddItem(BMenu *submenu, int32 index) 460 { 461 if (fLayout == B_ITEMS_IN_MATRIX) 462 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only " 463 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 464 465 BMenuItem *item = new (nothrow) BMenuItem(submenu); 466 if (!item) 467 return false; 468 469 if (!AddItem(item, index)) { 470 item->fSubmenu = NULL; 471 delete item; 472 return false; 473 } 474 475 return true; 476 } 477 478 479 bool 480 BMenu::AddItem(BMenu *submenu, BRect frame) 481 { 482 if (fLayout != B_ITEMS_IN_MATRIX) 483 debugger("BMenu::AddItem(BMenu *, BRect) this method can only " 484 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 485 486 BMenuItem *item = new (nothrow) BMenuItem(submenu); 487 if (!item) 488 return false; 489 490 if (!AddItem(item, frame)) { 491 item->fSubmenu = NULL; 492 delete item; 493 return false; 494 } 495 496 return true; 497 } 498 499 500 bool 501 BMenu::AddList(BList *list, int32 index) 502 { 503 // TODO: test this function, it's not documented in the bebook. 504 if (list == NULL) 505 return false; 506 507 bool locked = LockLooper(); 508 509 int32 numItems = list->CountItems(); 510 for (int32 i = 0; i < numItems; i++) { 511 BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i)); 512 if (item != NULL) { 513 if (!_AddItem(item, index + i)) 514 break; 515 } 516 } 517 518 InvalidateLayout(); 519 if (locked && Window() != NULL && !Window()->IsHidden()) { 520 // Make sure we update the layout if needed. 521 _LayoutItems(index); 522 _UpdateWindowViewSize(false); 523 Invalidate(); 524 } 525 526 if (locked) 527 UnlockLooper(); 528 529 return true; 530 } 531 532 533 bool 534 BMenu::AddSeparatorItem() 535 { 536 BMenuItem *item = new (nothrow) BSeparatorItem(); 537 if (!item || !AddItem(item, CountItems())) { 538 delete item; 539 return false; 540 } 541 542 return true; 543 } 544 545 546 bool 547 BMenu::RemoveItem(BMenuItem *item) 548 { 549 return _RemoveItems(0, 0, item, false); 550 } 551 552 553 BMenuItem * 554 BMenu::RemoveItem(int32 index) 555 { 556 BMenuItem *item = ItemAt(index); 557 if (item != NULL) 558 _RemoveItems(0, 0, item, false); 559 return item; 560 } 561 562 563 bool 564 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems) 565 { 566 return _RemoveItems(index, count, NULL, deleteItems); 567 } 568 569 570 bool 571 BMenu::RemoveItem(BMenu *submenu) 572 { 573 for (int32 i = 0; i < fItems.CountItems(); i++) { 574 if (static_cast<BMenuItem *>(fItems.ItemAtFast(i))->Submenu() == submenu) 575 return _RemoveItems(i, 1, NULL, false); 576 } 577 578 return false; 579 } 580 581 582 int32 583 BMenu::CountItems() const 584 { 585 return fItems.CountItems(); 586 } 587 588 589 BMenuItem * 590 BMenu::ItemAt(int32 index) const 591 { 592 return static_cast<BMenuItem *>(fItems.ItemAt(index)); 593 } 594 595 596 BMenu * 597 BMenu::SubmenuAt(int32 index) const 598 { 599 BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index)); 600 return (item != NULL) ? item->Submenu() : NULL; 601 } 602 603 604 int32 605 BMenu::IndexOf(BMenuItem *item) const 606 { 607 return fItems.IndexOf(item); 608 } 609 610 611 int32 612 BMenu::IndexOf(BMenu *submenu) const 613 { 614 for (int32 i = 0; i < fItems.CountItems(); i++) { 615 if (ItemAt(i)->Submenu() == submenu) 616 return i; 617 } 618 619 return -1; 620 } 621 622 623 BMenuItem * 624 BMenu::FindItem(const char *label) const 625 { 626 BMenuItem *item = NULL; 627 628 for (int32 i = 0; i < CountItems(); i++) { 629 item = ItemAt(i); 630 631 if (item->Label() && strcmp(item->Label(), label) == 0) 632 return item; 633 634 if (item->Submenu() != NULL) { 635 item = item->Submenu()->FindItem(label); 636 if (item != NULL) 637 return item; 638 } 639 } 640 641 return NULL; 642 } 643 644 645 BMenuItem * 646 BMenu::FindItem(uint32 command) const 647 { 648 BMenuItem *item = NULL; 649 650 for (int32 i = 0; i < CountItems(); i++) { 651 item = ItemAt(i); 652 653 if (item->Command() == command) 654 return item; 655 656 if (item->Submenu() != NULL) { 657 item = item->Submenu()->FindItem(command); 658 if (item != NULL) 659 return item; 660 } 661 } 662 663 return NULL; 664 } 665 666 667 status_t 668 BMenu::SetTargetForItems(BHandler *handler) 669 { 670 status_t status = B_OK; 671 for (int32 i = 0; i < fItems.CountItems(); i++) { 672 status = ItemAt(i)->SetTarget(handler); 673 if (status < B_OK) 674 break; 675 } 676 677 return status; 678 } 679 680 681 status_t 682 BMenu::SetTargetForItems(BMessenger messenger) 683 { 684 status_t status = B_OK; 685 for (int32 i = 0; i < fItems.CountItems(); i++) { 686 status = ItemAt(i)->SetTarget(messenger); 687 if (status < B_OK) 688 break; 689 } 690 691 return status; 692 } 693 694 695 void 696 BMenu::SetEnabled(bool enabled) 697 { 698 if (fEnabled == enabled) 699 return; 700 701 fEnabled = enabled; 702 703 if (fSuperitem) 704 fSuperitem->SetEnabled(enabled); 705 } 706 707 708 void 709 BMenu::SetRadioMode(bool flag) 710 { 711 fRadioMode = flag; 712 if (!flag) 713 SetLabelFromMarked(false); 714 } 715 716 717 void 718 BMenu::SetTriggersEnabled(bool flag) 719 { 720 fTriggerEnabled = flag; 721 } 722 723 724 void 725 BMenu::SetMaxContentWidth(float width) 726 { 727 fMaxContentWidth = width; 728 } 729 730 731 void 732 BMenu::SetLabelFromMarked(bool flag) 733 { 734 fDynamicName = flag; 735 if (flag) 736 SetRadioMode(true); 737 } 738 739 740 bool 741 BMenu::IsLabelFromMarked() 742 { 743 return fDynamicName; 744 } 745 746 747 bool 748 BMenu::IsEnabled() const 749 { 750 if (!fEnabled) 751 return false; 752 753 return fSuper ? fSuper->IsEnabled() : true ; 754 } 755 756 757 bool 758 BMenu::IsRadioMode() const 759 { 760 return fRadioMode; 761 } 762 763 764 bool 765 BMenu::AreTriggersEnabled() const 766 { 767 return fTriggerEnabled; 768 } 769 770 771 bool 772 BMenu::IsRedrawAfterSticky() const 773 { 774 return fRedrawAfterSticky; 775 } 776 777 778 float 779 BMenu::MaxContentWidth() const 780 { 781 return fMaxContentWidth; 782 } 783 784 785 BMenuItem * 786 BMenu::FindMarked() 787 { 788 for (int32 i = 0; i < fItems.CountItems(); i++) { 789 BMenuItem *item = ItemAt(i); 790 if (item->IsMarked()) 791 return item; 792 } 793 794 return NULL; 795 } 796 797 798 BMenu * 799 BMenu::Supermenu() const 800 { 801 return fSuper; 802 } 803 804 805 BMenuItem * 806 BMenu::Superitem() const 807 { 808 return fSuperitem; 809 } 810 811 812 void 813 BMenu::MessageReceived(BMessage *msg) 814 { 815 BView::MessageReceived(msg); 816 } 817 818 819 void 820 BMenu::KeyDown(const char *bytes, int32 numBytes) 821 { 822 // TODO: Test how it works on beos and implement it correctly 823 switch (bytes[0]) { 824 case B_UP_ARROW: 825 if (fLayout == B_ITEMS_IN_COLUMN) 826 _SelectNextItem(fSelected, false); 827 break; 828 829 case B_DOWN_ARROW: 830 if (fLayout == B_ITEMS_IN_COLUMN) 831 _SelectNextItem(fSelected, true); 832 break; 833 834 case B_LEFT_ARROW: 835 if (fLayout == B_ITEMS_IN_ROW) 836 _SelectNextItem(fSelected, false); 837 else { 838 // this case has to be handled a bit specially. 839 BMenuItem *item = Superitem(); 840 if (item) { 841 if (dynamic_cast<BMenuBar *>(Supermenu())) { 842 // if we're at the top menu below the menu bar, pass the keypress to 843 // the menu bar so we can move to another top level menu 844 BMessenger msgr(Supermenu()); 845 msgr.SendMessage(Window()->CurrentMessage()); 846 } else 847 Supermenu()->_SelectItem(item, false, false); 848 } 849 } 850 break; 851 852 case B_RIGHT_ARROW: 853 if (fLayout == B_ITEMS_IN_ROW) 854 _SelectNextItem(fSelected, true); 855 else { 856 if (fSelected && fSelected->Submenu()) { 857 _SelectItem(fSelected, true, true); 858 } else if (dynamic_cast<BMenuBar *>(Supermenu())) { 859 // if we have no submenu and we're an 860 // item in the top menu below the menubar, 861 // pass the keypress to the menubar 862 // so you can use the keypress to switch menus. 863 BMessenger msgr(Supermenu()); 864 msgr.SendMessage(Window()->CurrentMessage()); 865 } 866 } 867 break; 868 869 case B_ENTER: 870 case B_SPACE: 871 if (fSelected) { 872 _InvokeItem(fSelected); 873 _QuitTracking(false); 874 } 875 break; 876 877 case B_ESCAPE: 878 _QuitTracking(); 879 break; 880 881 default: 882 { 883 uint32 trigger = UTF8ToCharCode(&bytes); 884 885 for (uint32 i = CountItems(); i-- > 0;) { 886 BMenuItem* item = ItemAt(i); 887 if (item->fTriggerIndex < 0 || item->fTrigger != trigger) 888 continue; 889 890 _InvokeItem(item); 891 } 892 break; 893 } 894 } 895 } 896 897 898 void 899 BMenu::Draw(BRect updateRect) 900 { 901 if (_RelayoutIfNeeded()) { 902 Invalidate(); 903 return; 904 } 905 906 DrawBackground(updateRect); 907 _DrawItems(updateRect); 908 } 909 910 911 BSize 912 BMenu::MinSize() 913 { 914 _ValidatePreferredSize(); 915 916 BSize size = (GetLayout() ? GetLayout()->MinSize() 917 : fLayoutData->preferred); 918 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 919 } 920 921 922 BSize 923 BMenu::MaxSize() 924 { 925 _ValidatePreferredSize(); 926 927 BSize size = (GetLayout() ? GetLayout()->MaxSize() 928 : fLayoutData->preferred); 929 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 930 } 931 932 933 BSize 934 BMenu::PreferredSize() 935 { 936 _ValidatePreferredSize(); 937 938 BSize size = (GetLayout() ? GetLayout()->PreferredSize() 939 : fLayoutData->preferred); 940 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 941 } 942 943 944 void 945 BMenu::GetPreferredSize(float *_width, float *_height) 946 { 947 _ValidatePreferredSize(); 948 949 if (_width) 950 *_width = fLayoutData->preferred.width; 951 if (_height) 952 *_height = fLayoutData->preferred.height; 953 } 954 955 956 void 957 BMenu::ResizeToPreferred() 958 { 959 BView::ResizeToPreferred(); 960 } 961 962 963 void 964 BMenu::DoLayout() 965 { 966 // If the user set a layout, we let the base class version call its 967 // hook. 968 if (GetLayout()) { 969 BView::DoLayout(); 970 return; 971 } 972 973 if (_RelayoutIfNeeded()) 974 Invalidate(); 975 } 976 977 978 void 979 BMenu::FrameMoved(BPoint new_position) 980 { 981 BView::FrameMoved(new_position); 982 } 983 984 985 void 986 BMenu::FrameResized(float new_width, float new_height) 987 { 988 BView::FrameResized(new_width, new_height); 989 } 990 991 992 void 993 BMenu::InvalidateLayout() 994 { 995 InvalidateLayout(false); 996 } 997 998 999 void 1000 BMenu::InvalidateLayout(bool descendants) 1001 { 1002 fUseCachedMenuLayout = false; 1003 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET); 1004 1005 BView::InvalidateLayout(descendants); 1006 } 1007 1008 1009 BHandler * 1010 BMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, 1011 int32 form, const char *property) 1012 { 1013 BPropertyInfo propInfo(sPropList); 1014 BHandler *target = NULL; 1015 1016 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) { 1017 case B_ERROR: 1018 break; 1019 1020 case 0: 1021 case 1: 1022 case 2: 1023 case 3: 1024 case 4: 1025 case 5: 1026 case 6: 1027 case 7: 1028 target = this; 1029 break; 1030 case 8: 1031 // TODO: redirect to menu 1032 target = this; 1033 break; 1034 case 9: 1035 case 10: 1036 case 11: 1037 case 12: 1038 target = this; 1039 break; 1040 case 13: 1041 // TODO: redirect to menuitem 1042 target = this; 1043 break; 1044 } 1045 1046 if (!target) 1047 target = BView::ResolveSpecifier(msg, index, specifier, form, 1048 property); 1049 1050 return target; 1051 } 1052 1053 1054 status_t 1055 BMenu::GetSupportedSuites(BMessage *data) 1056 { 1057 if (data == NULL) 1058 return B_BAD_VALUE; 1059 1060 status_t err = data->AddString("suites", "suite/vnd.Be-menu"); 1061 1062 if (err < B_OK) 1063 return err; 1064 1065 BPropertyInfo propertyInfo(sPropList); 1066 err = data->AddFlat("messages", &propertyInfo); 1067 1068 if (err < B_OK) 1069 return err; 1070 1071 return BView::GetSupportedSuites(data); 1072 } 1073 1074 1075 status_t 1076 BMenu::Perform(perform_code d, void *arg) 1077 { 1078 return BView::Perform(d, arg); 1079 } 1080 1081 1082 void 1083 BMenu::MakeFocus(bool focused) 1084 { 1085 BView::MakeFocus(focused); 1086 } 1087 1088 1089 void 1090 BMenu::AllAttached() 1091 { 1092 BView::AllAttached(); 1093 } 1094 1095 1096 void 1097 BMenu::AllDetached() 1098 { 1099 BView::AllDetached(); 1100 } 1101 1102 1103 BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags, 1104 menu_layout layout, bool resizeToFit) 1105 : BView(frame, name, resizingMode, flags), 1106 fChosenItem(NULL), 1107 fSelected(NULL), 1108 fCachedMenuWindow(NULL), 1109 fSuper(NULL), 1110 fSuperitem(NULL), 1111 fAscent(-1.0f), 1112 fDescent(-1.0f), 1113 fFontHeight(-1.0f), 1114 fState(0), 1115 fLayout(layout), 1116 fExtraRect(NULL), 1117 fMaxContentWidth(0.0f), 1118 fInitMatrixSize(NULL), 1119 fExtraMenuData(NULL), 1120 fTrigger(0), 1121 fResizeToFit(resizeToFit), 1122 fUseCachedMenuLayout(false), 1123 fEnabled(true), 1124 fDynamicName(false), 1125 fRadioMode(false), 1126 fTrackNewBounds(false), 1127 fStickyMode(false), 1128 fIgnoreHidden(true), 1129 fTriggerEnabled(true), 1130 fRedrawAfterSticky(false), 1131 fAttachAborted(false) 1132 { 1133 _InitData(NULL); 1134 } 1135 1136 1137 void 1138 BMenu::SetItemMargins(float left, float top, float right, float bottom) 1139 { 1140 fPad.Set(left, top, right, bottom); 1141 } 1142 1143 1144 void 1145 BMenu::GetItemMargins(float *left, float *top, float *right, 1146 float *bottom) const 1147 { 1148 if (left != NULL) 1149 *left = fPad.left; 1150 if (top != NULL) 1151 *top = fPad.top; 1152 if (right != NULL) 1153 *right = fPad.right; 1154 if (bottom != NULL) 1155 *bottom = fPad.bottom; 1156 } 1157 1158 1159 menu_layout 1160 BMenu::Layout() const 1161 { 1162 return fLayout; 1163 } 1164 1165 1166 void 1167 BMenu::Show() 1168 { 1169 Show(false); 1170 } 1171 1172 1173 void 1174 BMenu::Show(bool selectFirst) 1175 { 1176 _Install(NULL); 1177 _Show(selectFirst); 1178 } 1179 1180 1181 void 1182 BMenu::Hide() 1183 { 1184 _Hide(); 1185 _Uninstall(); 1186 } 1187 1188 1189 BMenuItem * 1190 BMenu::Track(bool sticky, BRect *clickToOpenRect) 1191 { 1192 if (sticky && LockLooper()) { 1193 //RedrawAfterSticky(Bounds()); 1194 // the call above didn't do anything, so I've removed it for now 1195 UnlockLooper(); 1196 } 1197 1198 if (clickToOpenRect != NULL && LockLooper()) { 1199 fExtraRect = clickToOpenRect; 1200 ConvertFromScreen(fExtraRect); 1201 UnlockLooper(); 1202 } 1203 1204 _SetStickyMode(sticky); 1205 1206 int action; 1207 BMenuItem *menuItem = _Track(&action); 1208 1209 fExtraRect = NULL; 1210 1211 return menuItem; 1212 } 1213 1214 1215 bool 1216 BMenu::AddDynamicItem(add_state state) 1217 { 1218 // Implemented in subclasses 1219 return false; 1220 } 1221 1222 1223 void 1224 BMenu::DrawBackground(BRect update) 1225 { 1226 rgb_color oldColor = HighColor(); 1227 SetHighColor(sMenuInfo.background_color); 1228 FillRect(Bounds() & update, B_SOLID_HIGH); 1229 SetHighColor(oldColor); 1230 } 1231 1232 1233 void 1234 BMenu::SetTrackingHook(menu_tracking_hook func, void *state) 1235 { 1236 delete fExtraMenuData; 1237 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(func, state); 1238 } 1239 1240 1241 void BMenu::_ReservedMenu3() {} 1242 void BMenu::_ReservedMenu4() {} 1243 void BMenu::_ReservedMenu5() {} 1244 void BMenu::_ReservedMenu6() {} 1245 1246 1247 BMenu & 1248 BMenu::operator=(const BMenu &) 1249 { 1250 return *this; 1251 } 1252 1253 1254 void 1255 BMenu::_InitData(BMessage* archive) 1256 { 1257 // TODO: Get _color, _fname, _fflt from the message, if present 1258 BFont font; 1259 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1260 font.SetSize(sMenuInfo.font_size); 1261 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1262 1263 fLayoutData = new LayoutData; 1264 1265 SetLowColor(sMenuInfo.background_color); 1266 SetViewColor(sMenuInfo.background_color); 1267 1268 fTriggerEnabled = sMenuInfo.triggers_always_shown; 1269 1270 if (archive != NULL) { 1271 archive->FindInt32("_layout", (int32 *)&fLayout); 1272 archive->FindBool("_rsize_to_fit", &fResizeToFit); 1273 bool disabled; 1274 if (archive->FindBool("_disable", &disabled) == B_OK) 1275 fEnabled = !disabled; 1276 archive->FindBool("_radio", &fRadioMode); 1277 1278 bool disableTrigger = false; 1279 archive->FindBool("_trig_disabled", &disableTrigger); 1280 fTriggerEnabled = !disableTrigger; 1281 1282 archive->FindBool("_dyn_label", &fDynamicName); 1283 archive->FindFloat("_maxwidth", &fMaxContentWidth); 1284 1285 BMessage msg; 1286 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) { 1287 BArchivable *object = instantiate_object(&msg); 1288 if (BMenuItem *item = dynamic_cast<BMenuItem *>(object)) { 1289 BRect bounds; 1290 if (fLayout == B_ITEMS_IN_MATRIX 1291 && archive->FindRect("_i_frames", i, &bounds) == B_OK) 1292 AddItem(item, bounds); 1293 else 1294 AddItem(item); 1295 } 1296 } 1297 } 1298 } 1299 1300 1301 bool 1302 BMenu::_Show(bool selectFirstItem) 1303 { 1304 // See if the supermenu has a cached menuwindow, 1305 // and use that one if possible. 1306 BMenuWindow *window = NULL; 1307 bool ourWindow = false; 1308 if (fSuper != NULL) { 1309 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1310 window = fSuper->_MenuWindow(); 1311 } 1312 1313 // Otherwise, create a new one 1314 // This happens for "stand alone" BPopUpMenus 1315 // (i.e. not within a BMenuField) 1316 if (window == NULL) { 1317 // Menu windows get the BMenu's handler name 1318 window = new (nothrow) BMenuWindow(Name()); 1319 ourWindow = true; 1320 } 1321 1322 if (window == NULL) 1323 return false; 1324 1325 if (window->Lock()) { 1326 fAttachAborted = false; 1327 window->AttachMenu(this); 1328 1329 // Menu didn't have the time to add its items: aborting... 1330 if (fAttachAborted) { 1331 window->DetachMenu(); 1332 // TODO: Probably not needed, we can just let _hide() quit the window 1333 if (ourWindow) 1334 window->Quit(); 1335 else 1336 window->Unlock(); 1337 return false; 1338 } 1339 1340 // Move the BMenu to 1, 1, if it's attached to a BMenuWindow, 1341 // (that means it's a BMenu, BMenuBars are attached to regular BWindows). 1342 // This is needed to be able to draw the frame around the BMenu. 1343 if (dynamic_cast<BMenuWindow *>(window) != NULL) 1344 MoveTo(1, 1); 1345 1346 _UpdateWindowViewSize(true); 1347 window->Show(); 1348 1349 if (selectFirstItem) 1350 _SelectItem(ItemAt(0)); 1351 1352 window->Unlock(); 1353 } 1354 1355 return true; 1356 } 1357 1358 1359 void 1360 BMenu::_Hide() 1361 { 1362 BMenuWindow *window = dynamic_cast<BMenuWindow *>(Window()); 1363 if (window == NULL || !window->Lock()) 1364 return; 1365 1366 if (fSelected != NULL) 1367 _SelectItem(NULL); 1368 1369 window->Hide(); 1370 window->DetachMenu(); 1371 // we don't want to be deleted when the window is removed 1372 1373 #if USE_CACHED_MENUWINDOW 1374 if (fSuper != NULL) 1375 window->Unlock(); 1376 else 1377 #endif 1378 window->Quit(); 1379 // it's our window, quit it 1380 1381 1382 // Delete the menu window used by our submenus 1383 _DeleteMenuWindow(); 1384 } 1385 1386 1387 const static bigtime_t kOpenSubmenuDelay = 225000; 1388 const static bigtime_t kNavigationAreaTimeout = 1000000; 1389 const static bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed. 1390 const static int32 kMouseMotionThreshold = 15; 1391 // TODO: Same as above. Actually, we could get rid of the kHysteresis 1392 1393 1394 BMenuItem * 1395 BMenu::_Track(int *action, long start) 1396 { 1397 // TODO: cleanup 1398 BMenuItem *item = NULL; 1399 BRect navAreaRectAbove, navAreaRectBelow; 1400 bigtime_t selectedTime = system_time(); 1401 bigtime_t navigationAreaTime = 0; 1402 1403 fState = MENU_STATE_TRACKING; 1404 if (fSuper != NULL) 1405 fSuper->fState = MENU_STATE_TRACKING_SUBMENU; 1406 1407 BPoint location; 1408 uint32 buttons; 1409 if (LockLooper()) { 1410 GetMouse(&location, &buttons); 1411 UnlockLooper(); 1412 } 1413 1414 int32 mouseSpeed = 0; 1415 bigtime_t pollTime = system_time(); 1416 bool releasedOnce = buttons == 0; 1417 while (fState != MENU_STATE_CLOSED) { 1418 if (_CustomTrackingWantsToQuit()) 1419 break; 1420 1421 if (!LockLooper()) 1422 break; 1423 1424 BMenuWindow *window = static_cast<BMenuWindow *>(Window()); 1425 BPoint screenLocation = ConvertToScreen(location); 1426 if (window->CheckForScrolling(screenLocation)) { 1427 UnlockLooper(); 1428 continue; 1429 } 1430 1431 // The order of the checks is important 1432 // to be able to handle overlapping menus: 1433 // first we check if mouse is inside a submenu, 1434 // then if the menu is inside this menu, 1435 // then if it's over a super menu. 1436 bool overSub = _OverSubmenu(fSelected, screenLocation); 1437 item = _HitTestItems(location, B_ORIGIN); 1438 if (overSub) { 1439 navAreaRectAbove = BRect(); 1440 navAreaRectBelow = BRect(); 1441 1442 // Since the submenu has its own looper, 1443 // we can unlock ours. Doing so also make sure 1444 // that our window gets any update message to 1445 // redraw itself 1446 UnlockLooper(); 1447 int submenuAction = MENU_STATE_TRACKING; 1448 BMenu *submenu = fSelected->Submenu(); 1449 submenu->_SetStickyMode(_IsStickyMode()); 1450 1451 // The following call blocks until the submenu 1452 // gives control back to us, either because the mouse 1453 // pointer goes out of the submenu's bounds, or because 1454 // the user closes the menu 1455 BMenuItem *submenuItem = submenu->_Track(&submenuAction); 1456 if (submenuAction == MENU_STATE_CLOSED) { 1457 item = submenuItem; 1458 fState = MENU_STATE_CLOSED; 1459 } 1460 if (!LockLooper()) 1461 break; 1462 } else if (item != NULL) { 1463 _UpdateStateOpenSelect(item, location, navAreaRectAbove, 1464 navAreaRectBelow, selectedTime, navigationAreaTime); 1465 if (!releasedOnce) 1466 releasedOnce = true; 1467 } else if (_OverSuper(screenLocation)) { 1468 fState = MENU_STATE_TRACKING; 1469 UnlockLooper(); 1470 break; 1471 } else { 1472 // Mouse pointer outside menu: 1473 // If there's no other submenu opened, 1474 // deselect the current selected item 1475 if (fSelected != NULL 1476 && (fSelected->Submenu() == NULL 1477 || fSelected->Submenu()->Window() == NULL)) { 1478 _SelectItem(NULL); 1479 fState = MENU_STATE_TRACKING; 1480 } 1481 1482 if (fSuper != NULL) { 1483 // Give supermenu the chance to continue tracking 1484 *action = fState; 1485 UnlockLooper(); 1486 return NULL; 1487 } 1488 } 1489 1490 UnlockLooper(); 1491 1492 if (fState != MENU_STATE_CLOSED) { 1493 bigtime_t snoozeAmount = 50000; 1494 snooze(snoozeAmount); 1495 1496 BPoint newLocation; 1497 uint32 newButtons; 1498 1499 bigtime_t newPollTime = system_time(); 1500 if (LockLooper()) { 1501 GetMouse(&newLocation, &newButtons, true); 1502 UnlockLooper(); 1503 } 1504 1505 // mouseSpeed in px per ms 1506 // (actually point_distance returns the square of the distance, 1507 // so it's more px^2 per ms) 1508 mouseSpeed = (int32)(point_distance(newLocation, location) * 1000 / (newPollTime - pollTime)); 1509 pollTime = newPollTime; 1510 1511 if (newLocation != location || newButtons != buttons) { 1512 if (!releasedOnce && newButtons == 0 && buttons != 0) 1513 releasedOnce = true; 1514 location = newLocation; 1515 buttons = newButtons; 1516 } 1517 1518 if (releasedOnce) 1519 _UpdateStateClose(item, location, buttons); 1520 } 1521 } 1522 1523 if (action != NULL) 1524 *action = fState; 1525 1526 if (fSelected != NULL && LockLooper()) { 1527 _SelectItem(NULL); 1528 UnlockLooper(); 1529 } 1530 1531 // delete the menu window recycled for all the child menus 1532 _DeleteMenuWindow(); 1533 1534 return item; 1535 } 1536 1537 1538 void 1539 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove, 1540 BRect& navAreaRectBelow) 1541 { 1542 #define NAV_AREA_THRESHOLD 8 1543 1544 // The navigation area is a region in which mouse-overs won't select 1545 // the item under the cursor. This makes it easier to navigate to 1546 // submenus, as the cursor can be moved to submenu items directly instead 1547 // of having to move it horizontally into the submenu first. The concept 1548 // is illustrated below: 1549 // 1550 // +-------+----+---------+ 1551 // | | /| | 1552 // | | /*| | 1553 // |[2]--> | /**| | 1554 // | |/[4]| | 1555 // |------------| | 1556 // | [1] | [6] | 1557 // |------------| | 1558 // | |\[5]| | 1559 // |[3]--> | \**| | 1560 // | | \*| | 1561 // | | \| | 1562 // | +----|---------+ 1563 // | | 1564 // +------------+ 1565 // 1566 // [1] Selected item, cursor position ('position') 1567 // [2] Upper navigation area rectangle ('navAreaRectAbove') 1568 // [3] Lower navigation area rectangle ('navAreaRectBelow') 1569 // [4] Upper navigation area 1570 // [5] Lower navigation area 1571 // [6] Submenu 1572 // 1573 // The rectangles are used to calculate if the cursor is in the actual 1574 // navigation area (see _UpdateStateOpenSelect()). 1575 1576 if (fSelected == NULL) 1577 return; 1578 1579 BMenu *submenu = fSelected->Submenu(); 1580 1581 if (submenu != NULL) { 1582 BRect menuBounds = ConvertToScreen(Bounds()); 1583 1584 fSelected->Submenu()->LockLooper(); 1585 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1586 fSelected->Submenu()->Bounds()); 1587 fSelected->Submenu()->UnlockLooper(); 1588 1589 if (menuBounds.left < submenuBounds.left) { 1590 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD, 1591 submenuBounds.top, menuBounds.right, 1592 position.y); 1593 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD, 1594 position.y, menuBounds.right, 1595 submenuBounds.bottom); 1596 } else { 1597 navAreaRectAbove.Set(menuBounds.left, 1598 submenuBounds.top, position.x - NAV_AREA_THRESHOLD, 1599 position.y); 1600 navAreaRectBelow.Set(menuBounds.left, 1601 position.y, position.x - NAV_AREA_THRESHOLD, 1602 submenuBounds.bottom); 1603 } 1604 } else { 1605 navAreaRectAbove = BRect(); 1606 navAreaRectBelow = BRect(); 1607 } 1608 } 1609 1610 1611 void 1612 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position, 1613 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime, 1614 bigtime_t& navigationAreaTime) 1615 { 1616 if (fState == MENU_STATE_CLOSED) 1617 return; 1618 1619 if (item != fSelected) { 1620 if (navigationAreaTime == 0) 1621 navigationAreaTime = system_time(); 1622 1623 position = ConvertToScreen(position); 1624 1625 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position); 1626 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position); 1627 1628 if (!inNavAreaRectAbove && !inNavAreaRectBelow) { 1629 _SelectItem(item, false); 1630 navAreaRectAbove = BRect(); 1631 navAreaRectBelow = BRect(); 1632 selectedTime = system_time(); 1633 navigationAreaTime = 0; 1634 return; 1635 } 1636 1637 BRect menuBounds = ConvertToScreen(Bounds()); 1638 1639 fSelected->Submenu()->LockLooper(); 1640 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen( 1641 fSelected->Submenu()->Bounds()); 1642 fSelected->Submenu()->UnlockLooper(); 1643 1644 float xOffset; 1645 1646 // navAreaRectAbove and navAreaRectBelow have the same X 1647 // position and width, so it doesn't matter which one we use to 1648 // calculate the X offset 1649 if (menuBounds.left < submenuBounds.left) 1650 xOffset = position.x - navAreaRectAbove.left; 1651 else 1652 xOffset = navAreaRectAbove.right - position.x; 1653 1654 bool inNavArea; 1655 1656 if (inNavAreaRectAbove) { 1657 float yOffset = navAreaRectAbove.bottom - position.y; 1658 float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height(); 1659 1660 inNavArea = yOffset <= xOffset / ratio; 1661 } else { 1662 float yOffset = navAreaRectBelow.bottom - position.y; 1663 float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height(); 1664 1665 inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset / ratio); 1666 } 1667 1668 bigtime_t systime = system_time(); 1669 1670 if (!inNavArea || (navigationAreaTime > 0 && systime - 1671 navigationAreaTime > kNavigationAreaTimeout)) { 1672 // Don't delay opening of submenu if the user had 1673 // to wait for the navigation area timeout anyway 1674 _SelectItem(item, inNavArea); 1675 1676 if (inNavArea) { 1677 _UpdateNavigationArea(position, navAreaRectAbove, 1678 navAreaRectBelow); 1679 } else { 1680 navAreaRectAbove = BRect(); 1681 navAreaRectBelow = BRect(); 1682 } 1683 1684 selectedTime = system_time(); 1685 navigationAreaTime = 0; 1686 } 1687 } else if (fSelected->Submenu() != NULL && 1688 system_time() - selectedTime > kOpenSubmenuDelay) { 1689 _SelectItem(fSelected, true); 1690 1691 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) { 1692 position = ConvertToScreen(position); 1693 _UpdateNavigationArea(position, navAreaRectAbove, navAreaRectBelow); 1694 } 1695 } 1696 1697 if (fState != MENU_STATE_TRACKING) 1698 fState = MENU_STATE_TRACKING; 1699 } 1700 1701 1702 void 1703 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where, 1704 const uint32& buttons) 1705 { 1706 if (fState == MENU_STATE_CLOSED) 1707 return; 1708 1709 if (buttons != 0 && _IsStickyMode()) { 1710 if (item == NULL) { 1711 if (item != fSelected) { 1712 LockLooper(); 1713 _SelectItem(item, false); 1714 UnlockLooper(); 1715 } 1716 fState = MENU_STATE_CLOSED; 1717 } else 1718 _SetStickyMode(false); 1719 } else if (buttons == 0 && !_IsStickyMode()) { 1720 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 1721 _SetStickyMode(true); 1722 fExtraRect = NULL; 1723 // Setting this to NULL will prevent this code 1724 // to be executed next time 1725 } else { 1726 if (item != fSelected) { 1727 LockLooper(); 1728 _SelectItem(item, false); 1729 UnlockLooper(); 1730 } 1731 fState = MENU_STATE_CLOSED; 1732 } 1733 } 1734 } 1735 1736 1737 bool 1738 BMenu::_AddItem(BMenuItem *item, int32 index) 1739 { 1740 ASSERT(item != NULL); 1741 if (index < 0 || index > fItems.CountItems()) 1742 return false; 1743 1744 if (!fItems.AddItem(item, index)) 1745 return false; 1746 1747 // install the item on the supermenu's window 1748 // or onto our window, if we are a root menu 1749 BWindow* window = NULL; 1750 if (Superitem() != NULL) 1751 window = Superitem()->fWindow; 1752 else 1753 window = Window(); 1754 if (window != NULL) 1755 item->Install(window); 1756 1757 item->SetSuper(this); 1758 return true; 1759 } 1760 1761 1762 bool 1763 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems) 1764 { 1765 bool success = false; 1766 bool invalidateLayout = false; 1767 1768 bool locked = LockLooper(); 1769 BWindow *window = Window(); 1770 1771 // The plan is simple: If we're given a BMenuItem directly, we use it 1772 // and ignore index and count. Otherwise, we use them instead. 1773 if (item != NULL) { 1774 if (fItems.RemoveItem(item)) { 1775 if (item == fSelected && window != NULL) 1776 _SelectItem(NULL); 1777 item->Uninstall(); 1778 item->SetSuper(NULL); 1779 if (deleteItems) 1780 delete item; 1781 success = invalidateLayout = true; 1782 } 1783 } else { 1784 // We iterate backwards because it's simpler 1785 int32 i = min_c(index + count - 1, fItems.CountItems() - 1); 1786 // NOTE: the range check for "index" is done after 1787 // calculating the last index to be removed, so 1788 // that the range is not "shifted" unintentionally 1789 index = max_c(0, index); 1790 for (; i >= index; i--) { 1791 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 1792 if (item != NULL) { 1793 if (fItems.RemoveItem(item)) { 1794 if (item == fSelected && window != NULL) 1795 _SelectItem(NULL); 1796 item->Uninstall(); 1797 item->SetSuper(NULL); 1798 if (deleteItems) 1799 delete item; 1800 success = true; 1801 invalidateLayout = true; 1802 } else { 1803 // operation not entirely successful 1804 success = false; 1805 break; 1806 } 1807 } 1808 } 1809 } 1810 1811 if (invalidateLayout) { 1812 InvalidateLayout(); 1813 if (locked && window != NULL) { 1814 _LayoutItems(0); 1815 _UpdateWindowViewSize(false); 1816 Invalidate(); 1817 } 1818 } 1819 1820 if (locked) 1821 UnlockLooper(); 1822 1823 return success; 1824 } 1825 1826 1827 bool 1828 BMenu::_RelayoutIfNeeded() 1829 { 1830 if (!fUseCachedMenuLayout) { 1831 fUseCachedMenuLayout = true; 1832 _CacheFontInfo(); 1833 _LayoutItems(0); 1834 return true; 1835 } 1836 return false; 1837 } 1838 1839 1840 void 1841 BMenu::_LayoutItems(int32 index) 1842 { 1843 _CalcTriggers(); 1844 1845 float width, height; 1846 _ComputeLayout(index, fResizeToFit, true, &width, &height); 1847 1848 if (fResizeToFit) 1849 ResizeTo(width, height); 1850 } 1851 1852 1853 BSize 1854 BMenu::_ValidatePreferredSize() 1855 { 1856 if (!fLayoutData->preferred.IsWidthSet()) 1857 _ComputeLayout(0, true, false, NULL, NULL); 1858 1859 return fLayoutData->preferred; 1860 } 1861 1862 1863 void 1864 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems, 1865 float* _width, float* _height) 1866 { 1867 // TODO: Take "bestFit", "moveItems", "index" into account, 1868 // Recalculate only the needed items, 1869 // not the whole layout every time 1870 1871 BRect frame; 1872 1873 switch (fLayout) { 1874 case B_ITEMS_IN_COLUMN: 1875 _ComputeColumnLayout(index, bestFit, moveItems, frame); 1876 break; 1877 1878 case B_ITEMS_IN_ROW: 1879 _ComputeRowLayout(index, bestFit, moveItems, frame); 1880 break; 1881 1882 case B_ITEMS_IN_MATRIX: 1883 _ComputeMatrixLayout(frame); 1884 break; 1885 1886 default: 1887 break; 1888 } 1889 1890 // change width depending on resize mode 1891 BSize size; 1892 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 1893 if (Parent()) 1894 size.width = Parent()->Frame().Width() + 1; 1895 else if (Window()) 1896 size.width = Window()->Frame().Width() + 1; 1897 else 1898 size.width = Bounds().Width(); 1899 } else 1900 size.width = frame.Width(); 1901 1902 size.height = frame.Height(); 1903 1904 if (_width) 1905 *_width = size.width; 1906 1907 if (_height) 1908 *_height = size.height; 1909 1910 if (bestFit) 1911 fLayoutData->preferred = size; 1912 1913 if (moveItems) 1914 fUseCachedMenuLayout = true; 1915 } 1916 1917 1918 void 1919 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems, 1920 BRect& frame) 1921 { 1922 BFont font; 1923 GetFont(&font); 1924 bool command = false; 1925 bool control = false; 1926 bool shift = false; 1927 1928 if (index > 0) 1929 frame = ItemAt(index - 1)->Frame(); 1930 else 1931 frame.Set(0, 0, 0, 0); 1932 1933 for (; index < fItems.CountItems(); index++) { 1934 BMenuItem *item = ItemAt(index); 1935 1936 float width, height; 1937 item->GetContentSize(&width, &height); 1938 1939 if (item->fModifiers && item->fShortcutChar) { 1940 width += font.Size(); 1941 if (item->fModifiers & B_COMMAND_KEY) 1942 command = true; 1943 if (item->fModifiers & B_CONTROL_KEY) 1944 control = true; 1945 if (item->fModifiers & B_SHIFT_KEY) 1946 shift = true; 1947 } 1948 1949 item->fBounds.left = 0.0f; 1950 item->fBounds.top = frame.bottom + (index > 0 ? 1.0f : 0.0f); 1951 item->fBounds.bottom = item->fBounds.top + height + fPad.top 1952 + fPad.bottom; 1953 1954 if (item->fSubmenu != NULL) 1955 width += item->Frame().Height(); 1956 1957 frame.right = max_c(frame.right, width + fPad.left + fPad.right); 1958 frame.bottom = item->fBounds.bottom; 1959 } 1960 1961 if (command) 1962 frame.right += 17; 1963 if (control) 1964 frame.right += 17; 1965 if (shift) 1966 frame.right += 22; 1967 1968 if (fMaxContentWidth > 0) 1969 frame.right = min_c(frame.right, fMaxContentWidth); 1970 1971 if (moveItems) { 1972 for (int32 i = 0; i < fItems.CountItems(); i++) 1973 ItemAt(i)->fBounds.right = frame.right; 1974 } 1975 1976 frame.top = 0; 1977 frame.right = ceilf(frame.right); 1978 } 1979 1980 1981 void 1982 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 1983 BRect& frame) 1984 { 1985 font_height fh; 1986 GetFontHeight(&fh); 1987 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 1988 + fPad.bottom)); 1989 1990 for (int32 i = 0; i < fItems.CountItems(); i++) { 1991 BMenuItem *item = ItemAt(i); 1992 float width, height; 1993 if (item != NULL) { 1994 item->GetContentSize(&width, &height); 1995 1996 item->fBounds.left = frame.right; 1997 item->fBounds.top = 0.0f; 1998 item->fBounds.right = item->fBounds.left + width + fPad.left 1999 + fPad.right; 2000 2001 frame.right = item->Frame().right + 1.0f; 2002 frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom); 2003 } 2004 } 2005 2006 if (moveItems) { 2007 for (int32 i = 0; i < fItems.CountItems(); i++) 2008 ItemAt(i)->fBounds.bottom = frame.bottom; 2009 } 2010 2011 if (bestFit) 2012 frame.right = ceilf(frame.right); 2013 else 2014 frame.right = Bounds().right; 2015 } 2016 2017 2018 void 2019 BMenu::_ComputeMatrixLayout(BRect &frame) 2020 { 2021 frame.Set(0, 0, 0, 0); 2022 for (int32 i = 0; i < CountItems(); i++) { 2023 BMenuItem *item = ItemAt(i); 2024 if (item != NULL) { 2025 frame.left = min_c(frame.left, item->Frame().left); 2026 frame.right = max_c(frame.right, item->Frame().right); 2027 frame.top = min_c(frame.top, item->Frame().top); 2028 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 2029 } 2030 } 2031 } 2032 2033 2034 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2035 BPoint 2036 BMenu::ScreenLocation() 2037 { 2038 BMenu *superMenu = Supermenu(); 2039 BMenuItem *superItem = Superitem(); 2040 2041 if (superMenu == NULL || superItem == NULL) { 2042 debugger("BMenu can't determine where to draw." 2043 "Override BMenu::ScreenLocation() to determine location."); 2044 } 2045 2046 BPoint point; 2047 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2048 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2049 else 2050 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2051 2052 superMenu->ConvertToScreen(&point); 2053 2054 return point; 2055 } 2056 2057 2058 BRect 2059 BMenu::_CalcFrame(BPoint where, bool *scrollOn) 2060 { 2061 // TODO: Improve me 2062 BRect bounds = Bounds(); 2063 BRect frame = bounds.OffsetToCopy(where); 2064 2065 BScreen screen(Window()); 2066 BRect screenFrame = screen.Frame(); 2067 2068 BMenu *superMenu = Supermenu(); 2069 BMenuItem *superItem = Superitem(); 2070 2071 if (scrollOn != NULL) { 2072 // basically, if this returns false, it means 2073 // that the menu frame won't fit completely inside the screen 2074 *scrollOn = !screenFrame.Contains(bounds); 2075 } 2076 2077 // TODO: Horrible hack: 2078 // When added to a BMenuField, a BPopUpMenu is the child of 2079 // a _BMCMenuBar_ to "fake" the menu hierarchy 2080 if (superMenu == NULL || superItem == NULL 2081 || dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) { 2082 // just move the window on screen 2083 2084 if (frame.bottom > screenFrame.bottom) 2085 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2086 else if (frame.top < screenFrame.top) 2087 frame.OffsetBy(0, -frame.top); 2088 2089 if (frame.right > screenFrame.right) 2090 frame.OffsetBy(screenFrame.right - frame.right, 0); 2091 else if (frame.left < screenFrame.left) 2092 frame.OffsetBy(-frame.left, 0); 2093 2094 return frame; 2095 } 2096 2097 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2098 if (frame.right > screenFrame.right) 2099 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2100 2101 if (frame.left < 0) 2102 frame.OffsetBy(-frame.left + 6, 0); 2103 2104 if (frame.bottom > screenFrame.bottom) 2105 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2106 } else { 2107 if (frame.bottom > screenFrame.bottom) { 2108 if (scrollOn != NULL && superMenu != NULL 2109 && dynamic_cast<BMenuBar *>(superMenu) != NULL 2110 && frame.top < (screenFrame.bottom - 80)) { 2111 *scrollOn = true; 2112 } else { 2113 frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3); 2114 } 2115 } 2116 2117 if (frame.right > screenFrame.right) 2118 frame.OffsetBy(screenFrame.right - frame.right, 0); 2119 } 2120 2121 return frame; 2122 } 2123 2124 2125 void 2126 BMenu::_DrawItems(BRect updateRect) 2127 { 2128 int32 itemCount = fItems.CountItems(); 2129 for (int32 i = 0; i < itemCount; i++) { 2130 BMenuItem *item = ItemAt(i); 2131 if (item->Frame().Intersects(updateRect)) 2132 item->Draw(); 2133 } 2134 } 2135 2136 2137 int 2138 BMenu::_State(BMenuItem **item) const 2139 { 2140 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2141 return fState; 2142 2143 if (fSelected != NULL && fSelected->Submenu() != NULL) 2144 return fSelected->Submenu()->_State(item); 2145 2146 return fState; 2147 } 2148 2149 2150 void 2151 BMenu::_InvokeItem(BMenuItem *item, bool now) 2152 { 2153 if (!item->IsEnabled()) 2154 return; 2155 2156 // Do the "selected" animation 2157 // TODO: Doesn't work. This is supposed to highlight 2158 // and dehighlight the item, works on beos but not on haiku. 2159 if (!item->Submenu() && LockLooper()) { 2160 snooze(50000); 2161 item->Select(true); 2162 Sync(); 2163 snooze(50000); 2164 item->Select(false); 2165 Sync(); 2166 snooze(50000); 2167 item->Select(true); 2168 Sync(); 2169 snooze(50000); 2170 item->Select(false); 2171 Sync(); 2172 UnlockLooper(); 2173 } 2174 2175 item->Invoke(); 2176 } 2177 2178 2179 bool 2180 BMenu::_OverSuper(BPoint location) 2181 { 2182 if (!Supermenu()) 2183 return false; 2184 2185 return fSuperbounds.Contains(location); 2186 } 2187 2188 2189 bool 2190 BMenu::_OverSubmenu(BMenuItem *item, BPoint loc) 2191 { 2192 if (item == NULL) 2193 return false; 2194 2195 BMenu *subMenu = item->Submenu(); 2196 if (subMenu == NULL || subMenu->Window() == NULL) 2197 return false; 2198 2199 // we assume that loc is in screen coords { 2200 if (subMenu->Window()->Frame().Contains(loc)) 2201 return true; 2202 2203 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2204 } 2205 2206 2207 BMenuWindow * 2208 BMenu::_MenuWindow() 2209 { 2210 #if USE_CACHED_MENUWINDOW 2211 if (fCachedMenuWindow == NULL) { 2212 char windowName[64]; 2213 snprintf(windowName, 64, "%s cached menu", Name()); 2214 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2215 } 2216 #endif 2217 return fCachedMenuWindow; 2218 } 2219 2220 2221 void 2222 BMenu::_DeleteMenuWindow() 2223 { 2224 if (fCachedMenuWindow != NULL) { 2225 fCachedMenuWindow->Lock(); 2226 fCachedMenuWindow->Quit(); 2227 fCachedMenuWindow = NULL; 2228 } 2229 } 2230 2231 2232 BMenuItem * 2233 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2234 { 2235 // TODO: Take "slop" into account ? 2236 2237 // if the point doesn't lie within the menu's 2238 // bounds, bail out immediately 2239 if (!Bounds().Contains(where)) 2240 return NULL; 2241 2242 int32 itemCount = CountItems(); 2243 for (int32 i = 0; i < itemCount; i++) { 2244 BMenuItem *item = ItemAt(i); 2245 if (item->Frame().Contains(where)) 2246 return item; 2247 } 2248 2249 return NULL; 2250 } 2251 2252 2253 BRect 2254 BMenu::_Superbounds() const 2255 { 2256 return fSuperbounds; 2257 } 2258 2259 2260 void 2261 BMenu::_CacheFontInfo() 2262 { 2263 font_height fh; 2264 GetFontHeight(&fh); 2265 fAscent = fh.ascent; 2266 fDescent = fh.descent; 2267 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2268 } 2269 2270 2271 void 2272 BMenu::_ItemMarked(BMenuItem *item) 2273 { 2274 if (IsRadioMode()) { 2275 for (int32 i = 0; i < CountItems(); i++) { 2276 if (ItemAt(i) != item) 2277 ItemAt(i)->SetMarked(false); 2278 } 2279 InvalidateLayout(); 2280 } 2281 2282 if (IsLabelFromMarked() && Superitem()) 2283 Superitem()->SetLabel(item->Label()); 2284 } 2285 2286 2287 void 2288 BMenu::_Install(BWindow *target) 2289 { 2290 for (int32 i = 0; i < CountItems(); i++) 2291 ItemAt(i)->Install(target); 2292 } 2293 2294 2295 void 2296 BMenu::_Uninstall() 2297 { 2298 for (int32 i = 0; i < CountItems(); i++) 2299 ItemAt(i)->Uninstall(); 2300 } 2301 2302 2303 void 2304 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem) 2305 { 2306 // Avoid deselecting and then reselecting the same item 2307 // which would cause flickering 2308 if (menuItem != fSelected) { 2309 if (fSelected != NULL) { 2310 fSelected->Select(false); 2311 BMenu *subMenu = fSelected->Submenu(); 2312 if (subMenu != NULL && subMenu->Window() != NULL) 2313 subMenu->_Hide(); 2314 } 2315 2316 fSelected = menuItem; 2317 if (fSelected != NULL) 2318 fSelected->Select(true); 2319 } 2320 2321 if (fSelected != NULL && showSubmenu) { 2322 BMenu *subMenu = fSelected->Submenu(); 2323 if (subMenu != NULL && subMenu->Window() == NULL) { 2324 if (!subMenu->_Show(selectFirstItem)) { 2325 // something went wrong, deselect the item 2326 fSelected->Select(false); 2327 fSelected = NULL; 2328 } 2329 } 2330 } 2331 } 2332 2333 2334 bool 2335 BMenu::_SelectNextItem(BMenuItem *item, bool forward) 2336 { 2337 if (CountItems() == 0) // cannot select next item in an empty menu 2338 return false; 2339 2340 BMenuItem *nextItem = _NextItem(item, forward); 2341 if (nextItem == NULL) 2342 return false; 2343 2344 bool openMenu = false; 2345 if (dynamic_cast<BMenuBar *>(this) != NULL) 2346 openMenu = true; 2347 _SelectItem(nextItem, openMenu); 2348 return true; 2349 } 2350 2351 2352 BMenuItem * 2353 BMenu::_NextItem(BMenuItem *item, bool forward) const 2354 { 2355 // go to next item, and skip over disabled items such as separators 2356 int32 index = fItems.IndexOf(item); 2357 const int32 numItems = fItems.CountItems(); 2358 if (index < 0) { 2359 if (forward) 2360 index = -1; 2361 else 2362 index = numItems; 2363 } 2364 int32 startIndex = index; 2365 do { 2366 if (forward) 2367 index++; 2368 else 2369 index--; 2370 2371 // cycle through menu items 2372 if (index < 0) 2373 index = numItems - 1; 2374 else if (index >= numItems) 2375 index = 0; 2376 } while (!ItemAt(index)->IsEnabled() && index != startIndex); 2377 2378 if (index == startIndex) // we are back where we started and no item was enabled 2379 return NULL; 2380 2381 return ItemAt(index); 2382 } 2383 2384 2385 void 2386 BMenu::_SetIgnoreHidden(bool on) 2387 { 2388 fIgnoreHidden = on; 2389 } 2390 2391 2392 void 2393 BMenu::_SetStickyMode(bool on) 2394 { 2395 if (fStickyMode == on) 2396 return; 2397 2398 fStickyMode = on; 2399 2400 // If we are switching to sticky mode, propagate the status 2401 // back to the super menu 2402 if (fSuper != NULL) 2403 fSuper->_SetStickyMode(on); 2404 else { 2405 // TODO: Ugly hack, but it needs to be done right here in this method 2406 BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this); 2407 if (on && menuBar != NULL && menuBar->LockLooper()) { 2408 // Steal the focus from the current focus view 2409 // (needed to handle keyboard navigation) 2410 menuBar->_StealFocus(); 2411 menuBar->UnlockLooper(); 2412 } 2413 } 2414 } 2415 2416 2417 bool 2418 BMenu::_IsStickyMode() const 2419 { 2420 return fStickyMode; 2421 } 2422 2423 2424 void 2425 BMenu::_CalcTriggers() 2426 { 2427 BPrivate::TriggerList triggerList; 2428 2429 // Gathers the existing triggers set by the user 2430 for (int32 i = 0; i < CountItems(); i++) { 2431 char trigger = ItemAt(i)->Trigger(); 2432 if (trigger != 0) 2433 triggerList.AddTrigger(trigger); 2434 } 2435 2436 // Set triggers for items which don't have one yet 2437 for (int32 i = 0; i < CountItems(); i++) { 2438 BMenuItem *item = ItemAt(i); 2439 if (item->Trigger() == 0) { 2440 uint32 trigger; 2441 int32 index; 2442 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 2443 item->SetAutomaticTrigger(index, trigger); 2444 } 2445 } 2446 } 2447 2448 2449 bool 2450 BMenu::_ChooseTrigger(const char *title, int32& index, uint32& trigger, 2451 BPrivate::TriggerList& triggers) 2452 { 2453 if (title == NULL) 2454 return false; 2455 2456 uint32 c; 2457 2458 // two runs: first we look out for uppercase letters 2459 // TODO: support Unicode characters correctly! 2460 for (uint32 i = 0; (c = title[i]) != '\0'; i++) { 2461 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) { 2462 index = i; 2463 trigger = tolower(c); 2464 return triggers.AddTrigger(c); 2465 } 2466 } 2467 2468 // then, if we still haven't found anything, we accept them all 2469 index = 0; 2470 while ((c = UTF8ToCharCode(&title)) != 0) { 2471 if (!isspace(c) && !triggers.HasTrigger(c)) { 2472 trigger = tolower(c); 2473 return triggers.AddTrigger(c); 2474 } 2475 2476 index++; 2477 } 2478 2479 return false; 2480 } 2481 2482 2483 void 2484 BMenu::_UpdateWindowViewSize(bool updatePosition) 2485 { 2486 BMenuWindow *window = static_cast<BMenuWindow *>(Window()); 2487 if (window == NULL) 2488 return; 2489 2490 if (dynamic_cast<BMenuBar *>(this) != NULL) 2491 return; 2492 2493 if (!fResizeToFit) 2494 return; 2495 2496 bool scroll; 2497 const BPoint screenLocation = updatePosition ? ScreenLocation() 2498 : window->Frame().LeftTop(); 2499 BRect frame = _CalcFrame(screenLocation, &scroll); 2500 ResizeTo(frame.Width(), frame.Height()); 2501 2502 if (fItems.CountItems() > 0) { 2503 if (!scroll) { 2504 window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2); 2505 } else { 2506 BScreen screen(window); 2507 2508 // If we need scrolling, resize the window to fit the screen and 2509 // attach scrollers to our cached BMenuWindow. 2510 if (dynamic_cast<BMenuBar *>(Supermenu()) == NULL) { 2511 window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom); 2512 frame.top = 0; 2513 } else { 2514 // Or, in case our parent was a BMenuBar enable scrolling with 2515 // normal size. 2516 window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom 2517 - frame.top); 2518 } 2519 2520 window->AttachScrollers(); 2521 } 2522 } else { 2523 _CacheFontInfo(); 2524 window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right, 2525 fFontHeight + fPad.top + fPad.bottom); 2526 } 2527 2528 if (updatePosition) 2529 window->MoveTo(frame.LeftTop()); 2530 } 2531 2532 2533 bool 2534 BMenu::_OkToProceed(BMenuItem* item) 2535 { 2536 BPoint where; 2537 ulong buttons; 2538 GetMouse(&where, &buttons, false); 2539 bool stickyMode = _IsStickyMode(); 2540 // Quit if user clicks the mouse button in sticky mode 2541 // or releases the mouse button in nonsticky mode 2542 // or moves the pointer over another item 2543 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2544 // BeOS seems to do something similar. This could also be a bug in Deskbar, though. 2545 if ((buttons != 0 && stickyMode) 2546 || (dynamic_cast<BMenuBar *>(this) == NULL 2547 && (buttons == 0 && !stickyMode) || _HitTestItems(where) != item)) 2548 return false; 2549 2550 return true; 2551 } 2552 2553 2554 bool 2555 BMenu::_CustomTrackingWantsToQuit() 2556 { 2557 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 2558 && fExtraMenuData->trackingState != NULL) { 2559 return fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState); 2560 } 2561 2562 return false; 2563 } 2564 2565 2566 void 2567 BMenu::_QuitTracking(bool onlyThis) 2568 { 2569 _SelectItem(NULL); 2570 if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this)) 2571 menuBar->_RestoreFocus(); 2572 2573 fChosenItem = NULL; 2574 fState = MENU_STATE_CLOSED; 2575 2576 // Close the whole menu hierarchy 2577 if (!onlyThis && _IsStickyMode()) 2578 _SetStickyMode(false); 2579 2580 _Hide(); 2581 } 2582 2583 2584 // #pragma mark - 2585 2586 2587 // TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp 2588 // In R5, they do all the work client side, we let the app_server handle the details. 2589 status_t 2590 set_menu_info(menu_info *info) 2591 { 2592 if (!info) 2593 return B_BAD_VALUE; 2594 2595 BPrivate::AppServerLink link; 2596 link.StartMessage(AS_SET_MENU_INFO); 2597 link.Attach<menu_info>(*info); 2598 2599 status_t status = B_ERROR; 2600 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2601 BMenu::sMenuInfo = *info; 2602 // Update also the local copy, in case anyone relies on it 2603 2604 return status; 2605 } 2606 2607 2608 status_t 2609 get_menu_info(menu_info *info) 2610 { 2611 if (!info) 2612 return B_BAD_VALUE; 2613 2614 BPrivate::AppServerLink link; 2615 link.StartMessage(AS_GET_MENU_INFO); 2616 2617 status_t status = B_ERROR; 2618 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2619 link.Read<menu_info>(info); 2620 2621 return status; 2622 } 2623 2624 2625 // MenuPrivate 2626 namespace BPrivate { 2627 2628 MenuPrivate::MenuPrivate(BMenu *menu) 2629 : 2630 fMenu(menu) 2631 { 2632 } 2633 2634 2635 menu_layout 2636 MenuPrivate::Layout() const 2637 { 2638 return fMenu->Layout(); 2639 } 2640 2641 2642 void 2643 MenuPrivate::ItemMarked(BMenuItem *item) 2644 { 2645 fMenu->_ItemMarked(item); 2646 } 2647 2648 2649 void 2650 MenuPrivate::CacheFontInfo() 2651 { 2652 fMenu->_CacheFontInfo(); 2653 } 2654 2655 2656 float 2657 MenuPrivate::FontHeight() const 2658 { 2659 return fMenu->fFontHeight; 2660 } 2661 2662 2663 float 2664 MenuPrivate::Ascent() const 2665 { 2666 return fMenu->fAscent; 2667 } 2668 2669 2670 BRect 2671 MenuPrivate::Padding() const 2672 { 2673 return fMenu->fPad; 2674 } 2675 2676 2677 void 2678 MenuPrivate::GetItemMargins(float *left, float *top, 2679 float *right, float *bottom) const 2680 { 2681 fMenu->GetItemMargins(left, top, right, bottom); 2682 } 2683 2684 2685 bool 2686 MenuPrivate::IsAltCommandKey() const 2687 { 2688 return fMenu->sAltAsCommandKey; 2689 } 2690 2691 2692 int 2693 MenuPrivate::State(BMenuItem **item) const 2694 { 2695 return fMenu->_State(item); 2696 } 2697 2698 2699 void 2700 MenuPrivate::Install(BWindow *window) 2701 { 2702 fMenu->_Install(window); 2703 } 2704 2705 2706 void 2707 MenuPrivate::Uninstall() 2708 { 2709 fMenu->_Uninstall(); 2710 } 2711 2712 2713 void 2714 MenuPrivate::SetSuper(BMenu *menu) 2715 { 2716 fMenu->fSuper = menu; 2717 } 2718 2719 2720 void 2721 MenuPrivate::SetSuperItem(BMenuItem *item) 2722 { 2723 fMenu->fSuperitem = item; 2724 } 2725 2726 2727 void 2728 MenuPrivate::InvokeItem(BMenuItem *item, bool now) 2729 { 2730 fMenu->_InvokeItem(item, now); 2731 } 2732 2733 2734 void 2735 MenuPrivate::QuitTracking(bool thisMenuOnly) 2736 { 2737 fMenu->_QuitTracking(thisMenuOnly); 2738 } 2739 2740 } ; 2741