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