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