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