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 bool option = false; 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 if (item->fModifiers & B_OPTION_KEY) 2049 option = true; 2050 } 2051 2052 item->fBounds.left = 0.0f; 2053 item->fBounds.top = frame.bottom + 1.0f; 2054 item->fBounds.bottom = item->fBounds.top + height + fPad.top 2055 + fPad.bottom; 2056 2057 if (item->fSubmenu != NULL) 2058 width += item->Frame().Height(); 2059 2060 frame.right = max_c(frame.right, width + fPad.left + fPad.right); 2061 frame.bottom = item->fBounds.bottom; 2062 } 2063 2064 if (command) 2065 frame.right += 17; 2066 if (control) 2067 frame.right += 17; 2068 if (option) 2069 frame.right += 17; 2070 if (shift) 2071 frame.right += 22; 2072 2073 if (fMaxContentWidth > 0) 2074 frame.right = min_c(frame.right, fMaxContentWidth); 2075 2076 if (moveItems) { 2077 for (int32 i = 0; i < fItems.CountItems(); i++) 2078 ItemAt(i)->fBounds.right = frame.right; 2079 } 2080 2081 frame.top = 0; 2082 frame.right = ceilf(frame.right); 2083 } 2084 2085 2086 void 2087 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems, 2088 BRect& frame) 2089 { 2090 font_height fh; 2091 GetFontHeight(&fh); 2092 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top 2093 + fPad.bottom)); 2094 2095 for (int32 i = 0; i < fItems.CountItems(); i++) { 2096 BMenuItem *item = ItemAt(i); 2097 2098 float width, height; 2099 item->GetContentSize(&width, &height); 2100 2101 item->fBounds.left = frame.right; 2102 item->fBounds.top = 0.0f; 2103 item->fBounds.right = item->fBounds.left + width + fPad.left 2104 + fPad.right; 2105 2106 frame.right = item->Frame().right + 1.0f; 2107 frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom); 2108 } 2109 2110 if (moveItems) { 2111 for (int32 i = 0; i < fItems.CountItems(); i++) 2112 ItemAt(i)->fBounds.bottom = frame.bottom; 2113 } 2114 2115 if (bestFit) 2116 frame.right = ceilf(frame.right); 2117 else 2118 frame.right = Bounds().right; 2119 } 2120 2121 2122 void 2123 BMenu::_ComputeMatrixLayout(BRect &frame) 2124 { 2125 frame.Set(0, 0, 0, 0); 2126 for (int32 i = 0; i < CountItems(); i++) { 2127 BMenuItem *item = ItemAt(i); 2128 if (item != NULL) { 2129 frame.left = min_c(frame.left, item->Frame().left); 2130 frame.right = max_c(frame.right, item->Frame().right); 2131 frame.top = min_c(frame.top, item->Frame().top); 2132 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 2133 } 2134 } 2135 } 2136 2137 2138 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen()) 2139 BPoint 2140 BMenu::ScreenLocation() 2141 { 2142 BMenu *superMenu = Supermenu(); 2143 BMenuItem *superItem = Superitem(); 2144 2145 if (superMenu == NULL || superItem == NULL) { 2146 debugger("BMenu can't determine where to draw." 2147 "Override BMenu::ScreenLocation() to determine location."); 2148 } 2149 2150 BPoint point; 2151 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 2152 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f); 2153 else 2154 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f); 2155 2156 superMenu->ConvertToScreen(&point); 2157 2158 return point; 2159 } 2160 2161 2162 BRect 2163 BMenu::_CalcFrame(BPoint where, bool *scrollOn) 2164 { 2165 // TODO: Improve me 2166 BRect bounds = Bounds(); 2167 BRect frame = bounds.OffsetToCopy(where); 2168 2169 BScreen screen(Window()); 2170 BRect screenFrame = screen.Frame(); 2171 2172 BMenu *superMenu = Supermenu(); 2173 BMenuItem *superItem = Superitem(); 2174 2175 bool scroll = false; 2176 // TODO: Horrible hack: 2177 // When added to a BMenuField, a BPopUpMenu is the child of 2178 // a _BMCMenuBar_ to "fake" the menu hierarchy 2179 if (superMenu == NULL || superItem == NULL 2180 || dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) { 2181 // just move the window on screen 2182 2183 if (frame.bottom > screenFrame.bottom) 2184 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2185 else if (frame.top < screenFrame.top) 2186 frame.OffsetBy(0, -frame.top); 2187 2188 if (frame.right > screenFrame.right) 2189 frame.OffsetBy(screenFrame.right - frame.right, 0); 2190 else if (frame.left < screenFrame.left) 2191 frame.OffsetBy(-frame.left, 0); 2192 2193 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) { 2194 if (frame.right > screenFrame.right) 2195 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0); 2196 2197 if (frame.left < 0) 2198 frame.OffsetBy(-frame.left + 6, 0); 2199 2200 if (frame.bottom > screenFrame.bottom) 2201 frame.OffsetBy(0, screenFrame.bottom - frame.bottom); 2202 } else { 2203 if (frame.bottom > screenFrame.bottom) { 2204 if (scrollOn != NULL && superMenu != NULL 2205 && dynamic_cast<BMenuBar *>(superMenu) != NULL 2206 && frame.top < (screenFrame.bottom - 80)) { 2207 scroll = true; 2208 } else { 2209 frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3); 2210 } 2211 } 2212 2213 if (frame.right > screenFrame.right) 2214 frame.OffsetBy(screenFrame.right - frame.right, 0); 2215 } 2216 2217 if (!scroll) { 2218 // basically, if this returns false, it means 2219 // that the menu frame won't fit completely inside the screen 2220 // TODO: Scrolling, will currently only work up/down, 2221 // not left/right 2222 scroll = screenFrame.Height() < frame.Height(); 2223 } 2224 2225 if (scrollOn != NULL) 2226 *scrollOn = scroll; 2227 2228 return frame; 2229 } 2230 2231 2232 void 2233 BMenu::_DrawItems(BRect updateRect) 2234 { 2235 int32 itemCount = fItems.CountItems(); 2236 for (int32 i = 0; i < itemCount; i++) { 2237 BMenuItem *item = ItemAt(i); 2238 if (item->Frame().Intersects(updateRect)) 2239 item->Draw(); 2240 } 2241 } 2242 2243 2244 int 2245 BMenu::_State(BMenuItem **item) const 2246 { 2247 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED) 2248 return fState; 2249 2250 if (fSelected != NULL && fSelected->Submenu() != NULL) 2251 return fSelected->Submenu()->_State(item); 2252 2253 return fState; 2254 } 2255 2256 2257 void 2258 BMenu::_InvokeItem(BMenuItem *item, bool now) 2259 { 2260 if (!item->IsEnabled()) 2261 return; 2262 2263 // Do the "selected" animation 2264 // TODO: Doesn't work. This is supposed to highlight 2265 // and dehighlight the item, works on beos but not on haiku. 2266 if (!item->Submenu() && LockLooper()) { 2267 snooze(50000); 2268 item->Select(true); 2269 Sync(); 2270 snooze(50000); 2271 item->Select(false); 2272 Sync(); 2273 snooze(50000); 2274 item->Select(true); 2275 Sync(); 2276 snooze(50000); 2277 item->Select(false); 2278 Sync(); 2279 UnlockLooper(); 2280 } 2281 2282 // Lock the root menu window before calling BMenuItem::Invoke() 2283 BMenu *parent = this; 2284 BMenu *rootMenu = NULL; 2285 do { 2286 rootMenu = parent; 2287 parent = rootMenu->Supermenu(); 2288 } while (parent != NULL); 2289 2290 if (rootMenu->LockLooper()) { 2291 item->Invoke(); 2292 rootMenu->UnlockLooper(); 2293 } 2294 } 2295 2296 2297 bool 2298 BMenu::_OverSuper(BPoint location) 2299 { 2300 if (!Supermenu()) 2301 return false; 2302 2303 return fSuperbounds.Contains(location); 2304 } 2305 2306 2307 bool 2308 BMenu::_OverSubmenu(BMenuItem *item, BPoint loc) 2309 { 2310 if (item == NULL) 2311 return false; 2312 2313 BMenu *subMenu = item->Submenu(); 2314 if (subMenu == NULL || subMenu->Window() == NULL) 2315 return false; 2316 2317 // we assume that loc is in screen coords { 2318 if (subMenu->Window()->Frame().Contains(loc)) 2319 return true; 2320 2321 return subMenu->_OverSubmenu(subMenu->fSelected, loc); 2322 } 2323 2324 2325 BMenuWindow * 2326 BMenu::_MenuWindow() 2327 { 2328 #if USE_CACHED_MENUWINDOW 2329 if (fCachedMenuWindow == NULL) { 2330 char windowName[64]; 2331 snprintf(windowName, 64, "%s cached menu", Name()); 2332 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName); 2333 } 2334 #endif 2335 return fCachedMenuWindow; 2336 } 2337 2338 2339 void 2340 BMenu::_DeleteMenuWindow() 2341 { 2342 if (fCachedMenuWindow != NULL) { 2343 fCachedMenuWindow->Lock(); 2344 fCachedMenuWindow->Quit(); 2345 fCachedMenuWindow = NULL; 2346 } 2347 } 2348 2349 2350 BMenuItem * 2351 BMenu::_HitTestItems(BPoint where, BPoint slop) const 2352 { 2353 // TODO: Take "slop" into account ? 2354 2355 // if the point doesn't lie within the menu's 2356 // bounds, bail out immediately 2357 if (!Bounds().Contains(where)) 2358 return NULL; 2359 2360 int32 itemCount = CountItems(); 2361 for (int32 i = 0; i < itemCount; i++) { 2362 BMenuItem *item = ItemAt(i); 2363 if (item->Frame().Contains(where)) 2364 return item; 2365 } 2366 2367 return NULL; 2368 } 2369 2370 2371 BRect 2372 BMenu::_Superbounds() const 2373 { 2374 return fSuperbounds; 2375 } 2376 2377 2378 void 2379 BMenu::_CacheFontInfo() 2380 { 2381 font_height fh; 2382 GetFontHeight(&fh); 2383 fAscent = fh.ascent; 2384 fDescent = fh.descent; 2385 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading); 2386 } 2387 2388 2389 void 2390 BMenu::_ItemMarked(BMenuItem *item) 2391 { 2392 if (IsRadioMode()) { 2393 for (int32 i = 0; i < CountItems(); i++) { 2394 if (ItemAt(i) != item) 2395 ItemAt(i)->SetMarked(false); 2396 } 2397 InvalidateLayout(); 2398 } 2399 2400 if (IsLabelFromMarked() && Superitem()) 2401 Superitem()->SetLabel(item->Label()); 2402 } 2403 2404 2405 void 2406 BMenu::_Install(BWindow *target) 2407 { 2408 for (int32 i = 0; i < CountItems(); i++) 2409 ItemAt(i)->Install(target); 2410 } 2411 2412 2413 void 2414 BMenu::_Uninstall() 2415 { 2416 for (int32 i = 0; i < CountItems(); i++) 2417 ItemAt(i)->Uninstall(); 2418 } 2419 2420 2421 void 2422 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem) 2423 { 2424 // Avoid deselecting and then reselecting the same item 2425 // which would cause flickering 2426 if (menuItem != fSelected) { 2427 if (fSelected != NULL) { 2428 fSelected->Select(false); 2429 BMenu *subMenu = fSelected->Submenu(); 2430 if (subMenu != NULL && subMenu->Window() != NULL) 2431 subMenu->_Hide(); 2432 } 2433 2434 fSelected = menuItem; 2435 if (fSelected != NULL) 2436 fSelected->Select(true); 2437 } 2438 2439 if (fSelected != NULL && showSubmenu) { 2440 BMenu *subMenu = fSelected->Submenu(); 2441 if (subMenu != NULL && subMenu->Window() == NULL) { 2442 if (!subMenu->_Show(selectFirstItem)) { 2443 // something went wrong, deselect the item 2444 fSelected->Select(false); 2445 fSelected = NULL; 2446 } 2447 } 2448 } 2449 } 2450 2451 2452 bool 2453 BMenu::_SelectNextItem(BMenuItem *item, bool forward) 2454 { 2455 if (CountItems() == 0) // cannot select next item in an empty menu 2456 return false; 2457 2458 BMenuItem *nextItem = _NextItem(item, forward); 2459 if (nextItem == NULL) 2460 return false; 2461 2462 bool openMenu = false; 2463 if (dynamic_cast<BMenuBar *>(this) != NULL) 2464 openMenu = true; 2465 _SelectItem(nextItem, openMenu); 2466 return true; 2467 } 2468 2469 2470 BMenuItem * 2471 BMenu::_NextItem(BMenuItem *item, bool forward) const 2472 { 2473 // go to next item, and skip over disabled items such as separators 2474 int32 index = fItems.IndexOf(item); 2475 const int32 numItems = fItems.CountItems(); 2476 if (index < 0) { 2477 if (forward) 2478 index = -1; 2479 else 2480 index = numItems; 2481 } 2482 int32 startIndex = index; 2483 do { 2484 if (forward) 2485 index++; 2486 else 2487 index--; 2488 2489 // cycle through menu items 2490 if (index < 0) 2491 index = numItems - 1; 2492 else if (index >= numItems) 2493 index = 0; 2494 } while (!ItemAt(index)->IsEnabled() && index != startIndex); 2495 2496 if (index == startIndex) // we are back where we started and no item was enabled 2497 return NULL; 2498 2499 return ItemAt(index); 2500 } 2501 2502 2503 void 2504 BMenu::_SetIgnoreHidden(bool on) 2505 { 2506 fIgnoreHidden = on; 2507 } 2508 2509 2510 void 2511 BMenu::_SetStickyMode(bool on) 2512 { 2513 if (fStickyMode == on) 2514 return; 2515 2516 fStickyMode = on; 2517 2518 // If we are switching to sticky mode, propagate the status 2519 // back to the super menu 2520 if (fSuper != NULL) 2521 fSuper->_SetStickyMode(on); 2522 else { 2523 // TODO: Ugly hack, but it needs to be done right here in this method 2524 BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this); 2525 if (on && menuBar != NULL && menuBar->LockLooper()) { 2526 // Steal the focus from the current focus view 2527 // (needed to handle keyboard navigation) 2528 menuBar->_StealFocus(); 2529 menuBar->UnlockLooper(); 2530 } 2531 } 2532 } 2533 2534 2535 bool 2536 BMenu::_IsStickyMode() const 2537 { 2538 return fStickyMode; 2539 } 2540 2541 2542 void 2543 BMenu::_CalcTriggers() 2544 { 2545 BPrivate::TriggerList triggerList; 2546 2547 // Gathers the existing triggers set by the user 2548 for (int32 i = 0; i < CountItems(); i++) { 2549 char trigger = ItemAt(i)->Trigger(); 2550 if (trigger != 0) 2551 triggerList.AddTrigger(trigger); 2552 } 2553 2554 // Set triggers for items which don't have one yet 2555 for (int32 i = 0; i < CountItems(); i++) { 2556 BMenuItem *item = ItemAt(i); 2557 if (item->Trigger() == 0) { 2558 uint32 trigger; 2559 int32 index; 2560 if (_ChooseTrigger(item->Label(), index, trigger, triggerList)) 2561 item->SetAutomaticTrigger(index, trigger); 2562 } 2563 } 2564 } 2565 2566 2567 bool 2568 BMenu::_ChooseTrigger(const char *title, int32& index, uint32& trigger, 2569 BPrivate::TriggerList& triggers) 2570 { 2571 if (title == NULL) 2572 return false; 2573 2574 uint32 c; 2575 2576 // two runs: first we look out for uppercase letters 2577 // TODO: support Unicode characters correctly! 2578 for (uint32 i = 0; (c = title[i]) != '\0'; i++) { 2579 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) { 2580 index = i; 2581 trigger = tolower(c); 2582 return triggers.AddTrigger(c); 2583 } 2584 } 2585 2586 // then, if we still haven't found anything, we accept them all 2587 index = 0; 2588 while ((c = UTF8ToCharCode(&title)) != 0) { 2589 if (!isspace(c) && !triggers.HasTrigger(c)) { 2590 trigger = tolower(c); 2591 return triggers.AddTrigger(c); 2592 } 2593 2594 index++; 2595 } 2596 2597 return false; 2598 } 2599 2600 2601 void 2602 BMenu::_UpdateWindowViewSize(bool updatePosition) 2603 { 2604 BMenuWindow *window = static_cast<BMenuWindow *>(Window()); 2605 if (window == NULL) 2606 return; 2607 2608 if (dynamic_cast<BMenuBar *>(this) != NULL) 2609 return; 2610 2611 if (!fResizeToFit) 2612 return; 2613 2614 bool scroll = false; 2615 const BPoint screenLocation = updatePosition ? ScreenLocation() 2616 : window->Frame().LeftTop(); 2617 BRect frame = _CalcFrame(screenLocation, &scroll); 2618 ResizeTo(frame.Width(), frame.Height()); 2619 2620 if (fItems.CountItems() > 0) { 2621 if (!scroll) { 2622 window->ResizeTo(Bounds().Width(), Bounds().Height()); 2623 } else { 2624 BScreen screen(window); 2625 2626 // If we need scrolling, resize the window to fit the screen and 2627 // attach scrollers to our cached BMenuWindow. 2628 if (dynamic_cast<BMenuBar *>(Supermenu()) == NULL) { 2629 window->ResizeTo(Bounds().Width(), screen.Frame().bottom); 2630 frame.top = 0; 2631 } else { 2632 // Or, in case our parent was a BMenuBar enable scrolling with 2633 // normal size. 2634 window->ResizeTo(Bounds().Width(), screen.Frame().bottom 2635 - frame.top); 2636 } 2637 2638 window->AttachScrollers(); 2639 } 2640 } else { 2641 _CacheFontInfo(); 2642 window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right, 2643 fFontHeight + fPad.top + fPad.bottom); 2644 } 2645 2646 if (updatePosition) 2647 window->MoveTo(frame.LeftTop()); 2648 } 2649 2650 2651 bool 2652 BMenu::_OkToProceed(BMenuItem* item) 2653 { 2654 BPoint where; 2655 ulong buttons; 2656 GetMouse(&where, &buttons, false); 2657 bool stickyMode = _IsStickyMode(); 2658 // Quit if user clicks the mouse button in sticky mode 2659 // or releases the mouse button in nonsticky mode 2660 // or moves the pointer over another item 2661 // TODO: I added the check for BMenuBar to solve a problem with Deskbar. 2662 // BeOS seems to do something similar. This could also be a bug in Deskbar, though. 2663 if ((buttons != 0 && stickyMode) 2664 || ((dynamic_cast<BMenuBar *>(this) == NULL 2665 && (buttons == 0 && !stickyMode)) || _HitTestItems(where) != item)) 2666 return false; 2667 2668 return true; 2669 } 2670 2671 2672 bool 2673 BMenu::_CustomTrackingWantsToQuit() 2674 { 2675 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL 2676 && fExtraMenuData->trackingState != NULL) { 2677 return fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState); 2678 } 2679 2680 return false; 2681 } 2682 2683 2684 void 2685 BMenu::_QuitTracking(bool onlyThis) 2686 { 2687 _SelectItem(NULL); 2688 if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this)) 2689 menuBar->_RestoreFocus(); 2690 2691 fChosenItem = NULL; 2692 fState = MENU_STATE_CLOSED; 2693 2694 // Close the whole menu hierarchy 2695 if (!onlyThis && _IsStickyMode()) 2696 _SetStickyMode(false); 2697 2698 _Hide(); 2699 } 2700 2701 2702 // #pragma mark - 2703 2704 2705 // TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp 2706 // In R5, they do all the work client side, we let the app_server handle the details. 2707 status_t 2708 set_menu_info(menu_info *info) 2709 { 2710 if (!info) 2711 return B_BAD_VALUE; 2712 2713 BPrivate::AppServerLink link; 2714 link.StartMessage(AS_SET_MENU_INFO); 2715 link.Attach<menu_info>(*info); 2716 2717 status_t status = B_ERROR; 2718 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2719 BMenu::sMenuInfo = *info; 2720 // Update also the local copy, in case anyone relies on it 2721 2722 return status; 2723 } 2724 2725 2726 status_t 2727 get_menu_info(menu_info *info) 2728 { 2729 if (!info) 2730 return B_BAD_VALUE; 2731 2732 BPrivate::AppServerLink link; 2733 link.StartMessage(AS_GET_MENU_INFO); 2734 2735 status_t status = B_ERROR; 2736 if (link.FlushWithReply(status) == B_OK && status == B_OK) 2737 link.Read<menu_info>(info); 2738 2739 return status; 2740 } 2741 2742 2743 // MenuPrivate 2744 namespace BPrivate { 2745 2746 MenuPrivate::MenuPrivate(BMenu *menu) 2747 : 2748 fMenu(menu) 2749 { 2750 } 2751 2752 2753 menu_layout 2754 MenuPrivate::Layout() const 2755 { 2756 return fMenu->Layout(); 2757 } 2758 2759 2760 void 2761 MenuPrivate::ItemMarked(BMenuItem *item) 2762 { 2763 fMenu->_ItemMarked(item); 2764 } 2765 2766 2767 void 2768 MenuPrivate::CacheFontInfo() 2769 { 2770 fMenu->_CacheFontInfo(); 2771 } 2772 2773 2774 float 2775 MenuPrivate::FontHeight() const 2776 { 2777 return fMenu->fFontHeight; 2778 } 2779 2780 2781 float 2782 MenuPrivate::Ascent() const 2783 { 2784 return fMenu->fAscent; 2785 } 2786 2787 2788 BRect 2789 MenuPrivate::Padding() const 2790 { 2791 return fMenu->fPad; 2792 } 2793 2794 2795 void 2796 MenuPrivate::GetItemMargins(float *left, float *top, 2797 float *right, float *bottom) const 2798 { 2799 fMenu->GetItemMargins(left, top, right, bottom); 2800 } 2801 2802 2803 bool 2804 MenuPrivate::IsAltCommandKey() const 2805 { 2806 return fMenu->sAltAsCommandKey; 2807 } 2808 2809 2810 int 2811 MenuPrivate::State(BMenuItem **item) const 2812 { 2813 return fMenu->_State(item); 2814 } 2815 2816 2817 void 2818 MenuPrivate::Install(BWindow *window) 2819 { 2820 fMenu->_Install(window); 2821 } 2822 2823 2824 void 2825 MenuPrivate::Uninstall() 2826 { 2827 fMenu->_Uninstall(); 2828 } 2829 2830 2831 void 2832 MenuPrivate::SetSuper(BMenu *menu) 2833 { 2834 fMenu->fSuper = menu; 2835 } 2836 2837 2838 void 2839 MenuPrivate::SetSuperItem(BMenuItem *item) 2840 { 2841 fMenu->fSuperitem = item; 2842 } 2843 2844 2845 void 2846 MenuPrivate::InvokeItem(BMenuItem *item, bool now) 2847 { 2848 fMenu->_InvokeItem(item, now); 2849 } 2850 2851 2852 void 2853 MenuPrivate::QuitTracking(bool thisMenuOnly) 2854 { 2855 fMenu->_QuitTracking(thisMenuOnly); 2856 } 2857 2858 } // namespace BPrivate 2859