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