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