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