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