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