1 /* 2 * Copyright 2001-2006, Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Stefano Ceccherini (burton666@libero.it) 8 */ 9 10 #include <new> 11 #include <string.h> 12 13 #include <Debug.h> 14 #include <File.h> 15 #include <FindDirectory.h> 16 #include <Menu.h> 17 #include <MenuBar.h> 18 #include <MenuItem.h> 19 #include <Path.h> 20 #include <PropertyInfo.h> 21 #include <Screen.h> 22 #include <Window.h> 23 24 #include <AppServerLink.h> 25 #include <BMCPrivate.h> 26 #include <MenuPrivate.h> 27 #include <MenuWindow.h> 28 #include <ServerProtocol.h> 29 30 using std::nothrow; 31 32 class _ExtraMenuData_ { 33 public: 34 menu_tracking_hook trackingHook; 35 void *trackingState; 36 37 _ExtraMenuData_(menu_tracking_hook func, void *state) 38 { 39 trackingHook = func; 40 trackingState = state; 41 }; 42 }; 43 44 45 menu_info BMenu::sMenuInfo; 46 bool BMenu::sAltAsCommandKey; 47 48 49 static property_info 50 sPropList[] = { 51 { "Enabled", { B_GET_PROPERTY, 0 }, 52 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false " 53 "otherwise.", 54 0, { B_BOOL_TYPE } 55 }, 56 57 { "Enabled", { B_SET_PROPERTY, 0 }, 58 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.", 59 0, { B_BOOL_TYPE } 60 }, 61 62 { "Label", { B_GET_PROPERTY, 0 }, 63 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item.", 64 0, { B_STRING_TYPE } 65 }, 66 67 { "Label", { B_SET_PROPERTY, 0 }, 68 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item.", 69 0, { B_STRING_TYPE } 70 }, 71 72 { "Mark", { B_GET_PROPERTY, 0 }, 73 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem " 74 "is marked; false otherwise.", 75 0, { B_BOOL_TYPE } 76 }, 77 78 { "Mark", { B_SET_PROPERTY, 0 }, 79 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem.", 80 0, { B_BOOL_TYPE } 81 }, 82 83 { "Menu", { B_CREATE_PROPERTY, 0 }, 84 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 85 "Adds a new menu item at the specified index with the text label found in \"data\" " 86 "and the int32 command found in \"what\" (used as the what field in the CMessage " 87 "sent by the item)." , 0, {}, 88 { {{{"data", B_STRING_TYPE}}} 89 } 90 }, 91 92 { "Menu", { B_DELETE_PROPERTY, 0 }, 93 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 94 "Removes the selected menu or menus.", 0, {} 95 }, 96 97 { "Menu", { }, 98 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 99 "Directs scripting message to the specified menu, first popping the current " 100 "specifier off the stack.", 0, {} 101 }, 102 103 { "MenuItem", { B_COUNT_PROPERTIES, 0 }, 104 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu.", 105 0, { B_INT32_TYPE } 106 }, 107 108 { "MenuItem", { B_CREATE_PROPERTY, 0 }, 109 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 110 "Adds a new menu item at the specified index with the text label found in \"data\" " 111 "and the int32 command found in \"what\" (used as the what field in the CMessage " 112 "sent by the item).", 0, {}, 113 { { {{"data", B_STRING_TYPE }, 114 {"be:invoke_message", B_MESSAGE_TYPE}, 115 {"what", B_INT32_TYPE}, 116 {"be:target", B_MESSENGER_TYPE}} } 117 } 118 }, 119 120 { "MenuItem", { B_DELETE_PROPERTY, 0 }, 121 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 122 "Removes the specified menu item from its parent menu." 123 }, 124 125 { "MenuItem", { B_EXECUTE_PROPERTY, 0 }, 126 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 127 "Invokes the specified menu item." 128 }, 129 130 { "MenuItem", { }, 131 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 132 "Directs scripting message to the specified menu, first popping the current " 133 "specifier off the stack." 134 }, 135 136 {} 137 }; 138 139 140 const char *kEmptyMenuLabel = "<empty>"; 141 142 143 BMenu::BMenu(const char *name, menu_layout layout) 144 : BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW), 145 fChosenItem(NULL), 146 fPad(14.0f, 2.0f, 20.0f, 0.0f), 147 fSelected(NULL), 148 fCachedMenuWindow(NULL), 149 fSuper(NULL), 150 fSuperitem(NULL), 151 fAscent(-1.0f), 152 fDescent(-1.0f), 153 fFontHeight(-1.0f), 154 fState(0), 155 fLayout(layout), 156 fExtraRect(NULL), 157 fMaxContentWidth(0.0f), 158 fInitMatrixSize(NULL), 159 fExtraMenuData(NULL), 160 fTrigger(0), 161 fResizeToFit(true), 162 fUseCachedMenuLayout(false), 163 fEnabled(true), 164 fDynamicName(false), 165 fRadioMode(false), 166 fTrackNewBounds(false), 167 fStickyMode(false), 168 fIgnoreHidden(true), 169 fTriggerEnabled(true), 170 fRedrawAfterSticky(false), 171 fAttachAborted(false) 172 { 173 InitData(NULL); 174 } 175 176 177 BMenu::BMenu(const char *name, float width, float height) 178 : BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW), 179 fChosenItem(NULL), 180 fSelected(NULL), 181 fCachedMenuWindow(NULL), 182 fSuper(NULL), 183 fSuperitem(NULL), 184 fAscent(-1.0f), 185 fDescent(-1.0f), 186 fFontHeight(-1.0f), 187 fState(0), 188 fLayout(B_ITEMS_IN_MATRIX), 189 fExtraRect(NULL), 190 fMaxContentWidth(0.0f), 191 fInitMatrixSize(NULL), 192 fExtraMenuData(NULL), 193 fTrigger(0), 194 fResizeToFit(true), 195 fUseCachedMenuLayout(false), 196 fEnabled(true), 197 fDynamicName(false), 198 fRadioMode(false), 199 fTrackNewBounds(false), 200 fStickyMode(false), 201 fIgnoreHidden(true), 202 fTriggerEnabled(true), 203 fRedrawAfterSticky(false), 204 fAttachAborted(false) 205 { 206 InitData(NULL); 207 } 208 209 210 BMenu::~BMenu() 211 { 212 DeleteMenuWindow(); 213 214 RemoveItems(0, CountItems(), true); 215 216 delete fInitMatrixSize; 217 delete fExtraMenuData; 218 } 219 220 221 BMenu::BMenu(BMessage *archive) 222 : BView(archive), 223 fChosenItem(NULL), 224 fPad(14.0f, 2.0f, 20.0f, 0.0f), 225 fSelected(NULL), 226 fCachedMenuWindow(NULL), 227 fSuper(NULL), 228 fSuperitem(NULL), 229 fAscent(-1.0f), 230 fDescent(-1.0f), 231 fFontHeight(-1.0f), 232 fState(0), 233 fLayout(B_ITEMS_IN_ROW), 234 fExtraRect(NULL), 235 fMaxContentWidth(0.0f), 236 fInitMatrixSize(NULL), 237 fExtraMenuData(NULL), 238 fTrigger(0), 239 fResizeToFit(true), 240 fUseCachedMenuLayout(false), 241 fEnabled(true), 242 fDynamicName(false), 243 fRadioMode(false), 244 fTrackNewBounds(false), 245 fStickyMode(false), 246 fIgnoreHidden(true), 247 fTriggerEnabled(true), 248 fRedrawAfterSticky(false), 249 fAttachAborted(false) 250 { 251 InitData(archive); 252 } 253 254 255 BArchivable * 256 BMenu::Instantiate(BMessage *data) 257 { 258 if (validate_instantiation(data, "BMenu")) 259 return new (nothrow) BMenu(data); 260 261 return NULL; 262 } 263 264 265 status_t 266 BMenu::Archive(BMessage *data, bool deep) const 267 { 268 status_t err = BView::Archive(data, deep); 269 270 if (err == B_OK && Layout() != B_ITEMS_IN_ROW) 271 err = data->AddInt32("_layout", Layout()); 272 if (err == B_OK) 273 err = data->AddBool("_rsize_to_fit", fResizeToFit); 274 if (err == B_OK) 275 err = data->AddBool("_disable", !IsEnabled()); 276 if (err == B_OK) 277 err = data->AddBool("_radio", IsRadioMode()); 278 if (err == B_OK) 279 err = data->AddBool("_trig_disabled", AreTriggersEnabled()); 280 if (err == B_OK) 281 err = data->AddBool("_dyn_label", fDynamicName); 282 if (err == B_OK) 283 err = data->AddFloat("_maxwidth", fMaxContentWidth); 284 if (err == B_OK && deep) { 285 BMenuItem *item = NULL; 286 int32 index = 0; 287 while ((item = ItemAt(index++)) != NULL) { 288 BMessage itemData; 289 item->Archive(&itemData, deep); 290 err = data->AddMessage("_items", &itemData); 291 if (err != B_OK) 292 break; 293 if (fLayout == B_ITEMS_IN_MATRIX) { 294 err = data->AddRect("_i_frames", item->fBounds); 295 } 296 } 297 } 298 299 return err; 300 } 301 302 303 void 304 BMenu::AttachedToWindow() 305 { 306 BView::AttachedToWindow(); 307 308 sAltAsCommandKey = true; 309 key_map *keys = NULL; 310 char *chars = NULL; 311 get_key_map(&keys, &chars); 312 if (keys == NULL || keys->left_command_key != 0x5d || keys->right_command_key != 0x5f) 313 sAltAsCommandKey = false; 314 free(chars); 315 free(keys); 316 317 BMenuItem *superItem = Superitem(); 318 BMenu *superMenu = Supermenu(); 319 if (AddDynamicItem(B_INITIAL_ADD)) { 320 do { 321 if (superMenu != NULL && !superMenu->OkToProceed(superItem)) { 322 AddDynamicItem(B_ABORT); 323 fAttachAborted = true; 324 break; 325 } 326 } while (AddDynamicItem(B_PROCESSING)); 327 } 328 329 if (!fAttachAborted) { 330 CacheFontInfo(); 331 LayoutItems(0); 332 //UpdateWindowViewSize(); 333 } 334 } 335 336 337 void 338 BMenu::DetachedFromWindow() 339 { 340 BView::DetachedFromWindow(); 341 } 342 343 344 bool 345 BMenu::AddItem(BMenuItem *item) 346 { 347 return AddItem(item, CountItems()); 348 } 349 350 351 bool 352 BMenu::AddItem(BMenuItem *item, int32 index) 353 { 354 if (fLayout == B_ITEMS_IN_MATRIX) 355 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only " 356 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 357 358 if (!item || !_AddItem(item, index)) 359 return false; 360 361 InvalidateLayout(); 362 if (LockLooper()) { 363 if (!Window()->IsHidden()) { 364 LayoutItems(index); 365 Invalidate(); 366 } 367 UnlockLooper(); 368 } 369 return true; 370 } 371 372 373 bool 374 BMenu::AddItem(BMenuItem *item, BRect frame) 375 { 376 if (fLayout != B_ITEMS_IN_MATRIX) 377 debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only " 378 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 379 380 if (!item) 381 return false; 382 383 item->fBounds = frame; 384 385 int32 index = CountItems(); 386 if (!_AddItem(item, index)) { 387 return false; 388 } 389 390 if (LockLooper()) { 391 if (!Window()->IsHidden()) { 392 LayoutItems(index); 393 Invalidate(); 394 } 395 UnlockLooper(); 396 } 397 398 return true; 399 } 400 401 402 bool 403 BMenu::AddItem(BMenu *submenu) 404 { 405 BMenuItem *item = new (nothrow) BMenuItem(submenu); 406 if (!item) 407 return false; 408 409 if (!AddItem(item, CountItems())) { 410 item->fSubmenu = NULL; 411 delete item; 412 return false; 413 } 414 415 return true; 416 } 417 418 419 bool 420 BMenu::AddItem(BMenu *submenu, int32 index) 421 { 422 if (fLayout == B_ITEMS_IN_MATRIX) 423 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only " 424 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 425 426 BMenuItem *item = new (nothrow) BMenuItem(submenu); 427 if (!item) 428 return false; 429 430 if (!AddItem(item, index)) { 431 item->fSubmenu = NULL; 432 delete item; 433 return false; 434 } 435 436 return true; 437 } 438 439 440 bool 441 BMenu::AddItem(BMenu *submenu, BRect frame) 442 { 443 if (fLayout != B_ITEMS_IN_MATRIX) 444 debugger("BMenu::AddItem(BMenu *, BRect) this method can only " 445 "be called if the menu layout is B_ITEMS_IN_MATRIX"); 446 447 BMenuItem *item = new (nothrow) BMenuItem(submenu); 448 if (!item) 449 return false; 450 451 if (!AddItem(item, frame)) { 452 item->fSubmenu = NULL; 453 delete item; 454 return false; 455 } 456 457 return true; 458 } 459 460 461 bool 462 BMenu::AddList(BList *list, int32 index) 463 { 464 // TODO: test this function, it's not documented in the bebook. 465 if (list == NULL) 466 return false; 467 468 bool locked = LockLooper(); 469 470 int32 numItems = list->CountItems(); 471 for (int32 i = 0; i < numItems; i++) { 472 BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i)); 473 if (item != NULL) { 474 if (!_AddItem(item, index + i)) 475 break; 476 } 477 } 478 479 InvalidateLayout(); 480 if (locked && Window() != NULL && !Window()->IsHidden()) { 481 // Make sure we update the layout if needed. 482 LayoutItems(index); 483 //UpdateWindowViewSize(); 484 Invalidate(); 485 } 486 487 if (locked) 488 UnlockLooper(); 489 490 return true; 491 } 492 493 494 bool 495 BMenu::AddSeparatorItem() 496 { 497 BMenuItem *item = new (nothrow) BSeparatorItem(); 498 if (!item || !AddItem(item, CountItems())) { 499 delete item; 500 return false; 501 } 502 503 return true; 504 } 505 506 507 bool 508 BMenu::RemoveItem(BMenuItem *item) 509 { 510 // TODO: Check if item is also deleted 511 return RemoveItems(0, 0, item, false); 512 } 513 514 515 BMenuItem * 516 BMenu::RemoveItem(int32 index) 517 { 518 BMenuItem *item = ItemAt(index); 519 if (item != NULL) 520 RemoveItems(0, 0, item, false); 521 return item; 522 } 523 524 525 bool 526 BMenu::RemoveItems(int32 index, int32 count, bool del) 527 { 528 return RemoveItems(index, count, NULL, del); 529 } 530 531 532 bool 533 BMenu::RemoveItem(BMenu *submenu) 534 { 535 for (int32 i = 0; i < fItems.CountItems(); i++) { 536 if (static_cast<BMenuItem *>(fItems.ItemAtFast(i))->Submenu() == submenu) 537 return RemoveItems(i, 1, NULL, false); 538 } 539 540 return false; 541 } 542 543 544 int32 545 BMenu::CountItems() const 546 { 547 return fItems.CountItems(); 548 } 549 550 551 BMenuItem * 552 BMenu::ItemAt(int32 index) const 553 { 554 return static_cast<BMenuItem *>(fItems.ItemAt(index)); 555 } 556 557 558 BMenu * 559 BMenu::SubmenuAt(int32 index) const 560 { 561 BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index)); 562 return (item != NULL) ? item->Submenu() : NULL; 563 } 564 565 566 int32 567 BMenu::IndexOf(BMenuItem *item) const 568 { 569 return fItems.IndexOf(item); 570 } 571 572 573 int32 574 BMenu::IndexOf(BMenu *submenu) const 575 { 576 for (int32 i = 0; i < fItems.CountItems(); i++) { 577 if (ItemAt(i)->Submenu() == submenu) 578 return i; 579 } 580 581 return -1; 582 } 583 584 585 BMenuItem * 586 BMenu::FindItem(const char *label) const 587 { 588 BMenuItem *item = NULL; 589 590 for (int32 i = 0; i < CountItems(); i++) { 591 item = ItemAt(i); 592 593 if (item->Label() && strcmp(item->Label(), label) == 0) 594 return item; 595 596 if (item->Submenu() != NULL) { 597 item = item->Submenu()->FindItem(label); 598 if (item != NULL) 599 return item; 600 } 601 } 602 603 return NULL; 604 } 605 606 607 BMenuItem * 608 BMenu::FindItem(uint32 command) const 609 { 610 BMenuItem *item = NULL; 611 612 for (int32 i = 0; i < CountItems(); i++) { 613 item = ItemAt(i); 614 615 if (item->Command() == command) 616 return item; 617 618 if (item->Submenu() != NULL) { 619 item = item->Submenu()->FindItem(command); 620 if (item != NULL) 621 return item; 622 } 623 } 624 625 return NULL; 626 } 627 628 629 status_t 630 BMenu::SetTargetForItems(BHandler *handler) 631 { 632 status_t status = B_OK; 633 for (int32 i = 0; i < fItems.CountItems(); i++) { 634 status = ItemAt(i)->SetTarget(handler); 635 if (status < B_OK) 636 break; 637 } 638 639 return status; 640 } 641 642 643 status_t 644 BMenu::SetTargetForItems(BMessenger messenger) 645 { 646 status_t status = B_OK; 647 for (int32 i = 0; i < fItems.CountItems(); i++) { 648 status = ItemAt(i)->SetTarget(messenger); 649 if (status < B_OK) 650 break; 651 } 652 653 return status; 654 } 655 656 657 void 658 BMenu::SetEnabled(bool enabled) 659 { 660 if (fEnabled == enabled) 661 return; 662 663 fEnabled = enabled; 664 665 if (fSuperitem) 666 fSuperitem->SetEnabled(enabled); 667 } 668 669 670 void 671 BMenu::SetRadioMode(bool flag) 672 { 673 fRadioMode = flag; 674 if (!flag) 675 SetLabelFromMarked(false); 676 } 677 678 679 void 680 BMenu::SetTriggersEnabled(bool flag) 681 { 682 fTriggerEnabled = flag; 683 } 684 685 686 void 687 BMenu::SetMaxContentWidth(float width) 688 { 689 fMaxContentWidth = width; 690 } 691 692 693 void 694 BMenu::SetLabelFromMarked(bool flag) 695 { 696 fDynamicName = flag; 697 if (flag) 698 SetRadioMode(true); 699 } 700 701 702 bool 703 BMenu::IsLabelFromMarked() 704 { 705 return fDynamicName; 706 } 707 708 709 bool 710 BMenu::IsEnabled() const 711 { 712 if (!fEnabled) 713 return false; 714 715 return fSuper ? fSuper->IsEnabled() : true ; 716 } 717 718 719 bool 720 BMenu::IsRadioMode() const 721 { 722 return fRadioMode; 723 } 724 725 726 bool 727 BMenu::AreTriggersEnabled() const 728 { 729 return fTriggerEnabled; 730 } 731 732 733 bool 734 BMenu::IsRedrawAfterSticky() const 735 { 736 return fRedrawAfterSticky; 737 } 738 739 740 float 741 BMenu::MaxContentWidth() const 742 { 743 return fMaxContentWidth; 744 } 745 746 747 BMenuItem * 748 BMenu::FindMarked() 749 { 750 for (int32 i = 0; i < fItems.CountItems(); i++) { 751 BMenuItem *item = ItemAt(i); 752 if (item->IsMarked()) 753 return item; 754 } 755 756 return NULL; 757 } 758 759 760 BMenu * 761 BMenu::Supermenu() const 762 { 763 return fSuper; 764 } 765 766 767 BMenuItem * 768 BMenu::Superitem() const 769 { 770 return fSuperitem; 771 } 772 773 774 void 775 BMenu::MessageReceived(BMessage *msg) 776 { 777 BView::MessageReceived(msg); 778 } 779 780 781 void 782 BMenu::KeyDown(const char *bytes, int32 numBytes) 783 { 784 // TODO: Test how it works on beos and implement it correctly 785 switch (bytes[0]) { 786 case B_UP_ARROW: 787 if (fLayout == B_ITEMS_IN_COLUMN) 788 SelectNextItem(fSelected, false); 789 break; 790 791 case B_DOWN_ARROW: 792 if (fLayout == B_ITEMS_IN_COLUMN) 793 SelectNextItem(fSelected, true); 794 break; 795 796 case B_LEFT_ARROW: 797 if (fLayout == B_ITEMS_IN_ROW) 798 SelectNextItem(fSelected, false); 799 break; 800 801 case B_RIGHT_ARROW: 802 if (fLayout == B_ITEMS_IN_ROW) 803 SelectNextItem(fSelected, true); 804 break; 805 806 case B_ENTER: 807 case B_SPACE: 808 if (fSelected) 809 InvokeItem(fSelected); 810 811 break; 812 813 case B_ESCAPE: 814 QuitTracking(); 815 break; 816 817 default: 818 BView::KeyDown(bytes, numBytes); 819 } 820 } 821 822 823 void 824 BMenu::Draw(BRect updateRect) 825 { 826 if (RelayoutIfNeeded()) { 827 Invalidate(); 828 return; 829 } 830 831 DrawBackground(updateRect); 832 DrawItems(updateRect); 833 } 834 835 836 void 837 BMenu::GetPreferredSize(float *_width, float *_height) 838 { 839 ComputeLayout(0, true, false, _width, _height); 840 } 841 842 843 void 844 BMenu::ResizeToPreferred() 845 { 846 BView::ResizeToPreferred(); 847 } 848 849 850 void 851 BMenu::FrameMoved(BPoint new_position) 852 { 853 BView::FrameMoved(new_position); 854 } 855 856 857 void 858 BMenu::FrameResized(float new_width, float new_height) 859 { 860 BView::FrameResized(new_width, new_height); 861 } 862 863 864 void 865 BMenu::InvalidateLayout() 866 { 867 fUseCachedMenuLayout = false; 868 } 869 870 871 BHandler * 872 BMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, 873 int32 form, const char *property) 874 { 875 BPropertyInfo propInfo(sPropList); 876 BHandler *target = NULL; 877 878 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) { 879 case B_ERROR: 880 break; 881 882 case 0: 883 case 1: 884 case 2: 885 case 3: 886 case 4: 887 case 5: 888 case 6: 889 case 7: 890 target = this; 891 break; 892 case 8: 893 // TODO: redirect to menu 894 target = this; 895 break; 896 case 9: 897 case 10: 898 case 11: 899 case 12: 900 target = this; 901 break; 902 case 13: 903 // TODO: redirect to menuitem 904 target = this; 905 break; 906 } 907 908 if (!target) 909 target = BView::ResolveSpecifier(msg, index, specifier, form, 910 property); 911 912 return target; 913 } 914 915 916 status_t 917 BMenu::GetSupportedSuites(BMessage *data) 918 { 919 if (data == NULL) 920 return B_BAD_VALUE; 921 922 status_t err = data->AddString("suites", "suite/vnd.Be-menu"); 923 924 if (err < B_OK) 925 return err; 926 927 BPropertyInfo propertyInfo(sPropList); 928 err = data->AddFlat("messages", &propertyInfo); 929 930 if (err < B_OK) 931 return err; 932 933 return BView::GetSupportedSuites(data); 934 } 935 936 937 status_t 938 BMenu::Perform(perform_code d, void *arg) 939 { 940 return BView::Perform(d, arg); 941 } 942 943 944 void 945 BMenu::MakeFocus(bool focused) 946 { 947 BView::MakeFocus(focused); 948 } 949 950 951 void 952 BMenu::AllAttached() 953 { 954 BView::AllAttached(); 955 } 956 957 958 void 959 BMenu::AllDetached() 960 { 961 BView::AllDetached(); 962 } 963 964 965 BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags, 966 menu_layout layout, bool resizeToFit) 967 : BView(frame, name, resizingMode, flags), 968 fChosenItem(NULL), 969 fSelected(NULL), 970 fCachedMenuWindow(NULL), 971 fSuper(NULL), 972 fSuperitem(NULL), 973 fAscent(-1.0f), 974 fDescent(-1.0f), 975 fFontHeight(-1.0f), 976 fState(0), 977 fLayout(layout), 978 fExtraRect(NULL), 979 fMaxContentWidth(0.0f), 980 fInitMatrixSize(NULL), 981 fExtraMenuData(NULL), 982 fTrigger(0), 983 fResizeToFit(resizeToFit), 984 fUseCachedMenuLayout(false), 985 fEnabled(true), 986 fDynamicName(false), 987 fRadioMode(false), 988 fTrackNewBounds(false), 989 fStickyMode(false), 990 fIgnoreHidden(true), 991 fTriggerEnabled(true), 992 fRedrawAfterSticky(false), 993 fAttachAborted(false) 994 { 995 InitData(NULL); 996 } 997 998 999 void 1000 BMenu::SetItemMargins(float left, float top, float right, float bottom) 1001 { 1002 fPad.Set(left, top, right, bottom); 1003 } 1004 1005 1006 void 1007 BMenu::GetItemMargins(float *left, float *top, float *right, 1008 float *bottom) const 1009 { 1010 if (left != NULL) 1011 *left = fPad.left; 1012 if (top != NULL) 1013 *top = fPad.top; 1014 if (right != NULL) 1015 *right = fPad.right; 1016 if (bottom != NULL) 1017 *bottom = fPad.bottom; 1018 } 1019 1020 1021 menu_layout 1022 BMenu::Layout() const 1023 { 1024 return fLayout; 1025 } 1026 1027 1028 void 1029 BMenu::Show() 1030 { 1031 Show(false); 1032 } 1033 1034 1035 void 1036 BMenu::Show(bool selectFirst) 1037 { 1038 Install(NULL); 1039 _show(selectFirst); 1040 } 1041 1042 1043 void 1044 BMenu::Hide() 1045 { 1046 _hide(); 1047 Uninstall(); 1048 } 1049 1050 1051 BMenuItem * 1052 BMenu::Track(bool sticky, BRect *clickToOpenRect) 1053 { 1054 if (sticky && LockLooper()) { 1055 RedrawAfterSticky(Bounds()); 1056 UnlockLooper(); 1057 } 1058 1059 if (clickToOpenRect != NULL && LockLooper()) { 1060 fExtraRect = clickToOpenRect; 1061 ConvertFromScreen(fExtraRect); 1062 UnlockLooper(); 1063 } 1064 1065 // If sticky is false, pass 0 to the tracking function 1066 // so the menu will stay in nonsticky mode 1067 const bigtime_t trackTime = sticky ? system_time() : 0; 1068 int action; 1069 BMenuItem *menuItem = _track(&action, trackTime); 1070 1071 SetStickyMode(false); 1072 fExtraRect = NULL; 1073 1074 return menuItem; 1075 } 1076 1077 1078 bool 1079 BMenu::AddDynamicItem(add_state s) 1080 { 1081 // Implemented in subclasses 1082 return false; 1083 } 1084 1085 1086 void 1087 BMenu::DrawBackground(BRect update) 1088 { 1089 rgb_color oldColor = HighColor(); 1090 SetHighColor(sMenuInfo.background_color); 1091 FillRect(Bounds() & update, B_SOLID_HIGH); 1092 SetHighColor(oldColor); 1093 } 1094 1095 1096 void 1097 BMenu::SetTrackingHook(menu_tracking_hook func, void *state) 1098 { 1099 delete fExtraMenuData; 1100 fExtraMenuData = new (nothrow) _ExtraMenuData_(func, state); 1101 } 1102 1103 1104 void BMenu::_ReservedMenu3() {} 1105 void BMenu::_ReservedMenu4() {} 1106 void BMenu::_ReservedMenu5() {} 1107 void BMenu::_ReservedMenu6() {} 1108 1109 1110 BMenu & 1111 BMenu::operator=(const BMenu &) 1112 { 1113 return *this; 1114 } 1115 1116 1117 void 1118 BMenu::InitData(BMessage *data) 1119 { 1120 // TODO: Get _color, _fname, _fflt from the message, if present 1121 BFont font; 1122 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1123 font.SetSize(sMenuInfo.font_size); 1124 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1125 1126 SetLowColor(sMenuInfo.background_color); 1127 SetViewColor(sMenuInfo.background_color); 1128 1129 if (data != NULL) { 1130 data->FindInt32("_layout", (int32 *)&fLayout); 1131 data->FindBool("_rsize_to_fit", &fResizeToFit); 1132 bool disabled; 1133 if (data->FindBool("_disable", &disabled) == B_OK) 1134 fEnabled = !disabled; 1135 data->FindBool("_radio", &fRadioMode); 1136 1137 bool disableTrigger = false; 1138 data->FindBool("_trig_disabled", &disableTrigger); 1139 fTriggerEnabled = !disableTrigger; 1140 1141 data->FindBool("_dyn_label", &fDynamicName); 1142 data->FindFloat("_maxwidth", &fMaxContentWidth); 1143 1144 BMessage msg; 1145 for (int32 i = 0; data->FindMessage("_items", i, &msg) == B_OK; i++) { 1146 BArchivable *object = instantiate_object(&msg); 1147 if (BMenuItem *item = dynamic_cast<BMenuItem *>(object)) { 1148 BRect bounds; 1149 if ((fLayout == B_ITEMS_IN_MATRIX) 1150 && (data->FindRect("_i_frames", i, &bounds) == B_OK)) 1151 AddItem(item, bounds); 1152 else 1153 AddItem(item); 1154 } 1155 } 1156 } 1157 } 1158 1159 1160 bool 1161 BMenu::_show(bool selectFirstItem) 1162 { 1163 // See if the supermenu has a cached menuwindow, 1164 // and use that one if possible. 1165 BMenuWindow *window = NULL; 1166 bool ourWindow = false; 1167 if (fSuper != NULL) { 1168 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1169 window = fSuper->MenuWindow(); 1170 } 1171 1172 // Otherwise, create a new one 1173 // This happens for "stand alone" BPopUpMenus 1174 // (i.e. not within a BMenuField) 1175 if (window == NULL) { 1176 // Menu windows get the BMenu's handler name 1177 window = new (nothrow) BMenuWindow(Name()); 1178 ourWindow = true; 1179 } 1180 1181 if (window == NULL) 1182 return false; 1183 1184 if (window->Lock()) { 1185 fAttachAborted = false; 1186 window->AttachMenu(this); 1187 1188 // Menu didn't have the time to add its items: aborting... 1189 if (fAttachAborted) { 1190 window->DetachMenu(); 1191 // TODO: Probably not needed, we can just let _hide() quit the window 1192 if (ourWindow) 1193 window->Quit(); 1194 else 1195 window->Unlock(); 1196 return false; 1197 } 1198 1199 // Move the BMenu to 1, 1, if it's attached to a BMenuWindow, 1200 // (that means it's a BMenu, BMenuBars are attached to regular BWindows). 1201 // This is needed to be able to draw the frame around the BMenu. 1202 if (dynamic_cast<BMenuWindow *>(window) != NULL) 1203 MoveTo(1, 1); 1204 1205 UpdateWindowViewSize(); 1206 window->Show(); 1207 1208 if (selectFirstItem) 1209 SelectItem(ItemAt(0)); 1210 1211 window->Unlock(); 1212 } 1213 1214 return true; 1215 } 1216 1217 1218 void 1219 BMenu::_hide() 1220 { 1221 BMenuWindow *window = static_cast<BMenuWindow *>(Window()); 1222 if (window == NULL || !window->Lock()) 1223 return; 1224 1225 if (fSelected != NULL) 1226 SelectItem(NULL); 1227 1228 window->Hide(); 1229 window->DetachMenu(); 1230 // we don't want to be deleted when the window is removed 1231 1232 // Delete the menu window used by our submenus 1233 DeleteMenuWindow(); 1234 1235 window->Unlock(); 1236 1237 if (fSuper == NULL && window->Lock()) 1238 window->Quit(); 1239 } 1240 1241 1242 const bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed. 1243 1244 1245 BMenuItem * 1246 BMenu::_track(int *action, bigtime_t trackTime, long start) 1247 { 1248 // TODO: cleanup 1249 BMenuItem *item = NULL; 1250 bigtime_t openTime = system_time(); 1251 bigtime_t closeTime = 0; 1252 1253 fState = MENU_STATE_TRACKING; 1254 if (fSuper != NULL) 1255 fSuper->fState = MENU_STATE_TRACKING_SUBMENU; 1256 1257 while (true) { 1258 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 1259 && fExtraMenuData->trackingState != NULL) { 1260 /*bool result =*/ fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState); 1261 //printf("tracking hook returned %s\n", result ? "true" : "false"); 1262 } 1263 1264 bool locked = LockLooper(); 1265 if (!locked) 1266 break; 1267 1268 bigtime_t snoozeAmount = 50000; 1269 BPoint location; 1270 ulong buttons; 1271 GetMouse(&location, &buttons, true); 1272 1273 Window()->UpdateIfNeeded(); 1274 BPoint screenLocation = ConvertToScreen(location); 1275 item = HitTestItems(location, B_ORIGIN); 1276 if (item != NULL) { 1277 if (item != fSelected && system_time() > closeTime + kHysteresis) { 1278 SelectItem(item, -1); 1279 openTime = system_time(); 1280 } else if (system_time() > kHysteresis + openTime && item->Submenu() != NULL 1281 && item->Submenu()->Window() == NULL) { 1282 // Open the submenu if it's not opened yet, but only if 1283 // the mouse pointer stayed over there for some time 1284 // (hysteresis) 1285 SelectItem(item); 1286 closeTime = system_time(); 1287 } 1288 fState = MENU_STATE_TRACKING; 1289 } 1290 1291 // Track the submenu 1292 if (fSelected != NULL && OverSubmenu(fSelected, screenLocation)) { 1293 UnlockLooper(); 1294 locked = false; 1295 int submenuAction = MENU_STATE_TRACKING; 1296 BMenu *submenu = fSelected->Submenu(); 1297 if (IsStickyMode()) 1298 submenu->SetStickyMode(true); 1299 BMenuItem *submenuItem = submenu->_track(&submenuAction, trackTime); 1300 //submenu->Window()->Activate(); 1301 if (submenuAction == MENU_STATE_CLOSED) { 1302 item = submenuItem; 1303 fState = submenuAction; 1304 break; 1305 } 1306 1307 locked = LockLooper(); 1308 if (!locked) 1309 break; 1310 } 1311 1312 if (item == NULL) { 1313 if (OverSuper(screenLocation)) { 1314 fState = MENU_STATE_TRACKING; 1315 UnlockLooper(); 1316 break; 1317 } 1318 1319 if (fSelected != NULL && !OverSubmenu(fSelected, screenLocation) 1320 && system_time() > closeTime + kHysteresis 1321 && fState != MENU_STATE_TRACKING_SUBMENU) { 1322 SelectItem(NULL); 1323 fState = MENU_STATE_TRACKING; 1324 } 1325 1326 if (fSuper != NULL) { 1327 if (locked) 1328 UnlockLooper(); 1329 *action = fState; 1330 return NULL; 1331 } 1332 } 1333 1334 if (locked) 1335 UnlockLooper(); 1336 1337 if (buttons != 0 && IsStickyMode()) 1338 fState = MENU_STATE_CLOSED; 1339 else if (buttons == 0 && !IsStickyMode()) { 1340 if (system_time() < trackTime + 1000000 1341 || (fExtraRect != NULL && fExtraRect->Contains(location))) 1342 SetStickyMode(true); 1343 else 1344 fState = MENU_STATE_CLOSED; 1345 } 1346 1347 if (fState == MENU_STATE_CLOSED) 1348 break; 1349 1350 snooze(snoozeAmount); 1351 } 1352 1353 if (action != NULL) 1354 *action = fState; 1355 1356 if (fSelected != NULL && LockLooper()) { 1357 SelectItem(NULL); 1358 UnlockLooper(); 1359 } 1360 1361 if (IsStickyMode()) 1362 SetStickyMode(false); 1363 1364 // delete the menu window recycled for all the child menus 1365 DeleteMenuWindow(); 1366 1367 return item; 1368 } 1369 1370 1371 bool 1372 BMenu::_AddItem(BMenuItem *item, int32 index) 1373 { 1374 ASSERT(item != NULL); 1375 if (index < 0 || index > fItems.CountItems()) 1376 return false; 1377 1378 if (!fItems.AddItem(item, index)) 1379 return false; 1380 1381 // install the item on the supermenu's window 1382 // or onto our window, if we are a root menu 1383 BWindow* window = NULL; 1384 if (Superitem() != NULL) 1385 window = Superitem()->fWindow; 1386 else 1387 window = Window(); 1388 if (window != NULL) 1389 item->Install(window); 1390 1391 item->SetSuper(this); 1392 1393 return true; 1394 } 1395 1396 1397 bool 1398 BMenu::RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems) 1399 { 1400 bool success = false; 1401 bool invalidateLayout = false; 1402 1403 bool locked = LockLooper(); 1404 BWindow *window = Window(); 1405 1406 // The plan is simple: If we're given a BMenuItem directly, we use it 1407 // and ignore index and count. Otherwise, we use them instead. 1408 if (item != NULL) { 1409 if (fItems.RemoveItem(item)) { 1410 if (item == fSelected && window != NULL) 1411 SelectItem(NULL); 1412 item->Uninstall(); 1413 item->SetSuper(NULL); 1414 if (deleteItems) 1415 delete item; 1416 success = invalidateLayout = true; 1417 } 1418 } else { 1419 // We iterate backwards because it's simpler 1420 int32 i = min_c(index + count - 1, fItems.CountItems() - 1); 1421 // NOTE: the range check for "index" is done after 1422 // calculating the last index to be removed, so 1423 // that the range is not "shifted" unintentionally 1424 index = max_c(0, index); 1425 for (; i >= index; i--) { 1426 item = static_cast<BMenuItem*>(fItems.ItemAt(i)); 1427 if (item != NULL) { 1428 if (fItems.RemoveItem(item)) { 1429 if (item == fSelected && window != NULL) 1430 SelectItem(NULL); 1431 item->Uninstall(); 1432 item->SetSuper(NULL); 1433 if (deleteItems) 1434 delete item; 1435 success = true; 1436 invalidateLayout = true; 1437 } else { 1438 // operation not entirely successful 1439 success = false; 1440 break; 1441 } 1442 } 1443 } 1444 } 1445 1446 if (invalidateLayout && locked && window != NULL) { 1447 LayoutItems(0); 1448 //UpdateWindowViewSize(); 1449 Invalidate(); 1450 } 1451 1452 if (locked) 1453 UnlockLooper(); 1454 1455 return success; 1456 } 1457 1458 1459 bool 1460 BMenu::RelayoutIfNeeded() 1461 { 1462 if (!fUseCachedMenuLayout) { 1463 fUseCachedMenuLayout = true; 1464 CacheFontInfo(); 1465 LayoutItems(0); 1466 return true; 1467 } 1468 return false; 1469 } 1470 1471 1472 void 1473 BMenu::LayoutItems(int32 index) 1474 { 1475 CalcTriggers(); 1476 1477 float width, height; 1478 ComputeLayout(index, fResizeToFit, true, &width, &height); 1479 1480 ResizeTo(width, height); 1481 } 1482 1483 1484 void 1485 BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems, 1486 float* _width, float* _height) 1487 { 1488 // TODO: Take "bestFit", "moveItems", "index" into account, 1489 // Recalculate only the needed items, 1490 // not the whole layout every time 1491 1492 BRect frame(0, 0, 0, 0); 1493 float iWidth, iHeight; 1494 BMenuItem *item = NULL; 1495 1496 BFont font; 1497 GetFont(&font); 1498 switch (fLayout) { 1499 case B_ITEMS_IN_COLUMN: 1500 { 1501 for (int32 i = 0; i < fItems.CountItems(); i++) { 1502 item = ItemAt(i); 1503 if (item != NULL) { 1504 item->GetContentSize(&iWidth, &iHeight); 1505 1506 if (item->fModifiers && item->fShortcutChar) 1507 iWidth += 2 * font.Size(); 1508 if (item->fSubmenu != NULL) 1509 iWidth += 2 * font.Size(); 1510 1511 item->fBounds.left = 0.0f; 1512 item->fBounds.top = frame.bottom; 1513 item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom; 1514 1515 frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right); 1516 frame.bottom = item->fBounds.bottom + 1.0f; 1517 } 1518 } 1519 if (fMaxContentWidth > 0) 1520 frame.right = min_c(frame.right, fMaxContentWidth); 1521 1522 if (moveItems) { 1523 for (int32 i = 0; i < fItems.CountItems(); i++) 1524 ItemAt(i)->fBounds.right = frame.right; 1525 } 1526 frame.right = ceilf(frame.right); 1527 frame.bottom--; 1528 break; 1529 } 1530 1531 case B_ITEMS_IN_ROW: 1532 { 1533 font_height fh; 1534 GetFontHeight(&fh); 1535 frame = BRect(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top + fPad.bottom)); 1536 1537 for (int32 i = 0; i < fItems.CountItems(); i++) { 1538 item = ItemAt(i); 1539 if (item != NULL) { 1540 item->GetContentSize(&iWidth, &iHeight); 1541 1542 item->fBounds.left = frame.right; 1543 item->fBounds.top = 0.0f; 1544 item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right; 1545 1546 frame.right = item->Frame().right + 1.0f; 1547 frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom); 1548 } 1549 } 1550 1551 if (moveItems) { 1552 for (int32 i = 0; i < fItems.CountItems(); i++) 1553 ItemAt(i)->fBounds.bottom = frame.bottom; 1554 } 1555 1556 if (bestFit) 1557 frame.right = ceilf(frame.right); 1558 else 1559 frame.right = Bounds().right; 1560 break; 1561 } 1562 1563 case B_ITEMS_IN_MATRIX: 1564 { 1565 for (int32 i = 0; i < CountItems(); i++) { 1566 item = ItemAt(i); 1567 if (item != NULL) { 1568 frame.left = min_c(frame.left, item->Frame().left); 1569 frame.right = max_c(frame.right, item->Frame().right); 1570 frame.top = min_c(frame.top, item->Frame().top); 1571 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 1572 } 1573 } 1574 break; 1575 } 1576 1577 default: 1578 break; 1579 } 1580 1581 if (_width) { 1582 // change width depending on resize mode 1583 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 1584 if (Parent()) 1585 *_width = Parent()->Frame().Width() + 1; 1586 else if (Window()) 1587 *_width = Window()->Frame().Width() + 1; 1588 else 1589 *_width = Bounds().Width(); 1590 } else 1591 *_width = frame.Width(); 1592 } 1593 1594 if (_height) 1595 *_height = frame.Height(); 1596 1597 if (moveItems) 1598 fUseCachedMenuLayout = true; 1599 } 1600 1601 1602 BRect 1603 BMenu::Bump(BRect current, BPoint extent, int32 index) const 1604 { 1605 // ToDo: what's this? 1606 return BRect(); 1607 } 1608 1609 1610 BPoint 1611 BMenu::ItemLocInRect(BRect frame) const 1612 { 1613 // ToDo: what's this? 1614 return BPoint(); 1615 } 1616 1617 1618 BPoint 1619 BMenu::ScreenLocation() 1620 { 1621 BMenu *superMenu = Supermenu(); 1622 BMenuItem *superItem = Superitem(); 1623 1624 if (superMenu == NULL || superItem == NULL) { 1625 debugger("BMenu can't determine where to draw." 1626 "Override BMenu::ScreenLocation() to determine location."); 1627 } 1628 1629 BPoint point; 1630 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 1631 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 1632 else 1633 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 1634 1635 superMenu->ConvertToScreen(&point); 1636 1637 return point; 1638 } 1639 1640 1641 BRect 1642 BMenu::CalcFrame(BPoint where, bool *scrollOn) 1643 { 1644 // TODO: Improve me 1645 BRect bounds = Bounds(); 1646 BRect frame = bounds.OffsetToCopy(where); 1647 1648 BScreen screen(Window()); 1649 BRect screenFrame = screen.Frame(); 1650 1651 BMenu *superMenu = Supermenu(); 1652 BMenuItem *superItem = Superitem(); 1653 1654 if (scrollOn != NULL) { 1655 // basically, if this returns false, it means 1656 // that the menu frame won't fit completely inside the screen 1657 *scrollOn = !screenFrame.Contains(bounds); 1658 } 1659 1660 // TODO: Horrible hack: 1661 // When added to a BMenuField, a BPopUpMenu is the child of 1662 // a _BMCMenuBar_ to "fake" the menu hierarchy 1663 if (superMenu == NULL || superItem == NULL 1664 || dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) { 1665 // just move the window on screen 1666 1667 if (frame.bottom > screenFrame.bottom) 1668 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 1669 else if (frame.top < screenFrame.top) 1670 frame.OffsetBy(0, -frame.top); 1671 1672 if (frame.right > screenFrame.right) 1673 frame.OffsetBy(screenFrame.right - frame.right, 0); 1674 else if (frame.left < screenFrame.left) 1675 frame.OffsetBy(-frame.left, 0); 1676 1677 return frame; 1678 } 1679 1680 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 1681 if (frame.right > screenFrame.right) 1682 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 1683 1684 if (frame.bottom > screenFrame.bottom) 1685 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 1686 } else { 1687 if (frame.bottom > screenFrame.bottom) 1688 frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3); 1689 1690 if (frame.right > screenFrame.right) 1691 frame.OffsetBy(screenFrame.right - frame.right, 0); 1692 } 1693 1694 return frame; 1695 } 1696 1697 1698 bool 1699 BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast) 1700 { 1701 return false; 1702 } 1703 1704 1705 void 1706 BMenu::ScrollIntoView(BMenuItem *item) 1707 { 1708 } 1709 1710 1711 void 1712 BMenu::DrawItems(BRect updateRect) 1713 { 1714 int32 itemCount = fItems.CountItems(); 1715 for (int32 i = 0; i < itemCount; i++) { 1716 BMenuItem *item = ItemAt(i); 1717 if (item->Frame().Intersects(updateRect)) 1718 item->Draw(); 1719 } 1720 } 1721 1722 1723 int 1724 BMenu::State(BMenuItem **item) const 1725 { 1726 return 0; 1727 } 1728 1729 1730 void 1731 BMenu::InvokeItem(BMenuItem *item, bool now) 1732 { 1733 if (!item->IsEnabled()) 1734 return; 1735 1736 // Do the "selected" animation 1737 if (!item->Submenu() && LockLooper()) { 1738 snooze(50000); 1739 item->Select(true); 1740 Sync(); 1741 snooze(50000); 1742 item->Select(false); 1743 Sync(); 1744 snooze(50000); 1745 item->Select(true); 1746 Sync(); 1747 snooze(50000); 1748 item->Select(false); 1749 Sync(); 1750 UnlockLooper(); 1751 } 1752 1753 item->Invoke(); 1754 } 1755 1756 1757 bool 1758 BMenu::OverSuper(BPoint location) 1759 { 1760 if (!Supermenu()) 1761 return false; 1762 1763 return fSuperbounds.Contains(location); 1764 } 1765 1766 1767 bool 1768 BMenu::OverSubmenu(BMenuItem *item, BPoint loc) 1769 { 1770 // we assume that loc is in screen coords 1771 BMenu *subMenu = item->Submenu(); 1772 if (subMenu == NULL || subMenu->Window() == NULL) 1773 return false; 1774 1775 if (subMenu->Window()->Frame().Contains(loc)) 1776 return true; 1777 1778 if (subMenu->fSelected == NULL) 1779 return false; 1780 1781 return subMenu->OverSubmenu(subMenu->fSelected, loc); 1782 } 1783 1784 1785 BMenuWindow * 1786 BMenu::MenuWindow() 1787 { 1788 if (fCachedMenuWindow == NULL) { 1789 char windowName[64]; 1790 snprintf(windowName, 64, "%s cached menu", Name()); 1791 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 1792 } 1793 1794 return fCachedMenuWindow; 1795 } 1796 1797 1798 void 1799 BMenu::DeleteMenuWindow() 1800 { 1801 if (fCachedMenuWindow != NULL) { 1802 fCachedMenuWindow->Lock(); 1803 fCachedMenuWindow->Quit(); 1804 fCachedMenuWindow = NULL; 1805 } 1806 } 1807 1808 1809 BMenuItem * 1810 BMenu::HitTestItems(BPoint where, BPoint slop) const 1811 { 1812 // TODO: Take "slop" into account ? 1813 1814 // if the point doesn't lie within the menu's 1815 // bounds, bail out immediately 1816 if (!Bounds().Contains(where)) 1817 return NULL; 1818 1819 int32 itemCount = CountItems(); 1820 for (int32 i = 0; i < itemCount; i++) { 1821 BMenuItem *item = ItemAt(i); 1822 if (item->Frame().Contains(where)) 1823 return item; 1824 } 1825 1826 return NULL; 1827 } 1828 1829 1830 BRect 1831 BMenu::Superbounds() const 1832 { 1833 return fSuperbounds; 1834 } 1835 1836 1837 void 1838 BMenu::CacheFontInfo() 1839 { 1840 font_height fh; 1841 GetFontHeight(&fh); 1842 fAscent = fh.ascent; 1843 fDescent = fh.descent; 1844 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 1845 } 1846 1847 1848 void 1849 BMenu::ItemMarked(BMenuItem *item) 1850 { 1851 if (IsRadioMode()) { 1852 for (int32 i = 0; i < CountItems(); i++) 1853 if (ItemAt(i) != item) 1854 ItemAt(i)->SetMarked(false); 1855 InvalidateLayout(); 1856 } 1857 1858 if (IsLabelFromMarked() && Superitem()) 1859 Superitem()->SetLabel(item->Label()); 1860 } 1861 1862 1863 void 1864 BMenu::Install(BWindow *target) 1865 { 1866 for (int32 i = 0; i < CountItems(); i++) 1867 ItemAt(i)->Install(target); 1868 } 1869 1870 1871 void 1872 BMenu::Uninstall() 1873 { 1874 for (int32 i = 0; i < CountItems(); i++) 1875 ItemAt(i)->Uninstall(); 1876 } 1877 1878 1879 void 1880 BMenu::SelectItem(BMenuItem *menuItem, uint32 showSubmenu, bool selectFirstItem) 1881 { 1882 // Avoid deselecting and then reselecting the same item 1883 // which would cause flickering 1884 if (menuItem != fSelected) { 1885 if (fSelected != NULL) { 1886 fSelected->Select(false); 1887 BMenu *subMenu = fSelected->Submenu(); 1888 if (subMenu != NULL && subMenu->Window() != NULL) 1889 subMenu->_hide(); 1890 } 1891 1892 fSelected = menuItem; 1893 if (fSelected != NULL) 1894 fSelected->Select(true); 1895 } 1896 1897 if (fSelected != NULL && showSubmenu == 0) { 1898 BMenu *subMenu = fSelected->Submenu(); 1899 if (subMenu != NULL && subMenu->Window() == NULL) { 1900 subMenu->_show(selectFirstItem); 1901 subMenu->Window()->Activate(); 1902 } 1903 } 1904 } 1905 1906 1907 BMenuItem * 1908 BMenu::CurrentSelection() const 1909 { 1910 return fSelected; 1911 } 1912 1913 1914 bool 1915 BMenu::SelectNextItem(BMenuItem *item, bool forward) 1916 { 1917 BMenuItem *nextItem = NextItem(item, forward); 1918 if (nextItem == NULL) 1919 return false; 1920 1921 SelectItem(nextItem); 1922 return true; 1923 } 1924 1925 1926 BMenuItem * 1927 BMenu::NextItem(BMenuItem *item, bool forward) const 1928 { 1929 if (item == NULL) { 1930 if (forward) 1931 return ItemAt(CountItems() - 1); 1932 else 1933 return ItemAt(0); 1934 } 1935 1936 int32 index = fItems.IndexOf(item); 1937 if (forward) 1938 index++; 1939 else 1940 index--; 1941 1942 if (index < 0 || index >= fItems.CountItems()) 1943 return NULL; 1944 1945 return ItemAt(index); 1946 } 1947 1948 1949 bool 1950 BMenu::IsItemVisible(BMenuItem *item) const 1951 { 1952 BRect itemFrame = item->Frame(); 1953 ConvertToScreen(&itemFrame); 1954 1955 BRect visibilityFrame = Window()->Frame() & BScreen(Window()).Frame(); 1956 1957 return visibilityFrame.Intersects(itemFrame); 1958 } 1959 1960 1961 void 1962 BMenu::SetIgnoreHidden(bool on) 1963 { 1964 fIgnoreHidden = on; 1965 } 1966 1967 1968 void 1969 BMenu::SetStickyMode(bool on) 1970 { 1971 if (fStickyMode != on) { 1972 // TODO: Ugly hack, but it needs to be done right here in this method 1973 BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this); 1974 if (on && menuBar != NULL && menuBar->LockLooper()) { 1975 // Steal the focus from the current focus view 1976 // (needed to handle keyboard navigation) 1977 menuBar->StealFocus(); 1978 menuBar->UnlockLooper(); 1979 } 1980 1981 fStickyMode = on; 1982 } 1983 1984 // If we are switching to sticky mode, propagate the status 1985 // back to the super menu 1986 if (on && fSuper != NULL) 1987 fSuper->SetStickyMode(on); 1988 } 1989 1990 1991 bool 1992 BMenu::IsStickyMode() const 1993 { 1994 return fStickyMode; 1995 } 1996 1997 1998 void 1999 BMenu::CalcTriggers() 2000 { 2001 BList triggersList; 2002 2003 // Gathers the existing triggers 2004 // TODO: Oh great, reinterpret_cast. 2005 for (int32 i = 0; i < CountItems(); i++) { 2006 char trigger = ItemAt(i)->Trigger(); 2007 if (trigger != 0) 2008 triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger)); 2009 } 2010 2011 // Set triggers for items which don't have one yet 2012 for (int32 i = 0; i < CountItems(); i++) { 2013 BMenuItem *item = ItemAt(i); 2014 if (item->Trigger() == 0) { 2015 const char *newTrigger = ChooseTrigger(item->Label(), &triggersList); 2016 if (newTrigger != NULL) 2017 item->SetAutomaticTrigger(*newTrigger); 2018 } 2019 } 2020 } 2021 2022 2023 const char * 2024 BMenu::ChooseTrigger(const char *title, BList *chars) 2025 { 2026 ASSERT(chars != NULL); 2027 2028 if (title == NULL) 2029 return NULL; 2030 2031 char trigger; 2032 // TODO: Oh great, reinterpret_cast all around 2033 while ((trigger = title[0]) != '\0') { 2034 if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger))) { 2035 chars->AddItem(reinterpret_cast<void *>((uint32)trigger)); 2036 return title; 2037 } 2038 2039 title++; 2040 } 2041 2042 return NULL; 2043 } 2044 2045 2046 void 2047 BMenu::UpdateWindowViewSize(bool upWind) 2048 { 2049 BWindow *window = Window(); 2050 if (window == NULL) 2051 return; 2052 2053 bool scroll; 2054 BRect frame = CalcFrame(ScreenLocation(), &scroll); 2055 ResizeTo(frame.Width(), frame.Height()); 2056 2057 if (fItems.CountItems() > 0) 2058 window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2); 2059 else { 2060 CacheFontInfo(); 2061 window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right, 2062 fFontHeight + fPad.top + fPad.bottom); 2063 } 2064 window->MoveTo(frame.LeftTop()); 2065 } 2066 2067 2068 bool 2069 BMenu::IsStickyPrefOn() 2070 { 2071 return true; 2072 } 2073 2074 2075 void 2076 BMenu::RedrawAfterSticky(BRect bounds) 2077 { 2078 } 2079 2080 2081 bool 2082 BMenu::OkToProceed(BMenuItem* item) 2083 { 2084 bool proceed = true; 2085 BPoint where; 2086 ulong buttons; 2087 GetMouse(&where, &buttons, false); 2088 bool stickyMode = IsStickyMode(); 2089 // Quit if user clicks the mouse button in sticky mode 2090 // or releases the mouse button in nonsticky mode 2091 // or moves the pointer over another item 2092 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2093 // Beos seems to do something similar. This could also be a bug in Deskbar, though. 2094 if ((buttons != 0 && stickyMode) 2095 || (dynamic_cast<BMenuBar *>(this) == NULL && (buttons == 0 && !stickyMode) 2096 || HitTestItems(where) != item)) 2097 proceed = false; 2098 2099 2100 return proceed; 2101 } 2102 2103 2104 void 2105 BMenu::QuitTracking() 2106 { 2107 SelectItem(NULL); 2108 if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this)) 2109 menuBar->RestoreFocus(); 2110 2111 fChosenItem = NULL; 2112 fState = MENU_STATE_CLOSED; 2113 } 2114 2115 2116 status_t 2117 BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec, 2118 int32 *form, const char **prop, BMenu **tmenu, 2119 BMenuItem **titem, int32 *user_data, 2120 BMessage *reply) const 2121 { 2122 return B_ERROR; 2123 } 2124 2125 2126 status_t 2127 BMenu::DoMenuMsg(BMenuItem **next, BMenu *menu, BMessage *message, 2128 BMessage *r, BMessage *spec, int32 f) const 2129 { 2130 return B_ERROR; 2131 } 2132 2133 2134 status_t 2135 BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *menu, BMessage *message, 2136 BMessage *r, BMessage *spec, int32 f) const 2137 { 2138 return B_ERROR; 2139 } 2140 2141 2142 status_t 2143 BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 2144 BMessage *r) const 2145 { 2146 return B_ERROR; 2147 } 2148 2149 2150 status_t 2151 BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 2152 BMessage *r) const 2153 { 2154 return B_ERROR; 2155 } 2156 2157 2158 status_t 2159 BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 2160 BMessage *r) const 2161 { 2162 return B_ERROR; 2163 } 2164 2165 2166 status_t 2167 BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 2168 BMessage *r) const 2169 { 2170 return B_ERROR; 2171 } 2172 2173 2174 status_t 2175 BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 2176 BMessage *r, bool menu) const 2177 { 2178 return B_ERROR; 2179 } 2180 2181 2182 // TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp 2183 // In R5, they do all the work client side, we let the app_server handle the details. 2184 status_t 2185 set_menu_info(menu_info *info) 2186 { 2187 if (!info) 2188 return B_BAD_VALUE; 2189 2190 BPrivate::AppServerLink link; 2191 link.StartMessage(AS_SET_MENU_INFO); 2192 link.Attach<menu_info>(*info); 2193 2194 status_t status = B_ERROR; 2195 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2196 BMenu::sMenuInfo = *info; 2197 // Update also the local copy, in case anyone relies on it 2198 2199 return status; 2200 } 2201 2202 2203 status_t 2204 get_menu_info(menu_info *info) 2205 { 2206 if (!info) 2207 return B_BAD_VALUE; 2208 2209 BPrivate::AppServerLink link; 2210 link.StartMessage(AS_GET_MENU_INFO); 2211 2212 status_t status = B_ERROR; 2213 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2214 link.Read<menu_info>(info); 2215 2216 return status; 2217 } 2218