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