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