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