1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2005, Haiku, Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: Menu.cpp 23 // Authors: Marc Flerackers (mflerackers@androme.be) 24 // Stefano Ceccherini (burton666@libero.it) 25 // Description: BMenu display a menu of selectable items. 26 //------------------------------------------------------------------------------ 27 #include <Debug.h> 28 #include <File.h> 29 #include <FindDirectory.h> 30 #include <Menu.h> 31 #include <MenuItem.h> 32 #include <Path.h> 33 #include <PropertyInfo.h> 34 #include <Window.h> 35 36 #include <MenuWindow.h> 37 38 #ifndef COMPILE_FOR_R5 39 menu_info BMenu::sMenuInfo; 40 #endif 41 42 class _ExtraMenuData_ { 43 public: 44 menu_tracking_hook trackingHook; 45 void *trackingState; 46 47 _ExtraMenuData_(menu_tracking_hook func, void *state) 48 { 49 trackingHook = func; 50 trackingState = state; 51 }; 52 }; 53 54 55 static property_info 56 sPropList[] = { 57 { "Enabled", { B_GET_PROPERTY, 0 }, 58 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false " 59 "otherwise." }, 60 61 { "Enabled", { B_SET_PROPERTY, 0 }, 62 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item." }, 63 64 { "Label", { B_GET_PROPERTY, 0 }, 65 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item." }, 66 67 { "Label", { B_SET_PROPERTY, 0 }, 68 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item." }, 69 70 { "Mark", { B_GET_PROPERTY, 0 }, 71 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem " 72 "is marked; false otherwise." }, 73 74 { "Mark", { B_SET_PROPERTY, 0 }, 75 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem." }, 76 77 { "Menu", { B_CREATE_PROPERTY, 0 }, 78 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 79 "Adds a new menu item at the specified index with the text label found in \"data\" " 80 "and the int32 command found in \"what\" (used as the what field in the CMessage " 81 "sent by the item)." }, 82 83 { "Menu", { B_DELETE_PROPERTY, 0 }, 84 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 85 "Removes the selected menu or menus." }, 86 87 { "Menu", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 88 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 89 "Directs scripting message to the specified menu, first popping the current " 90 "specifier off the stack." }, 91 92 { "MenuItem", { B_COUNT_PROPERTIES, 0 }, 93 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu." }, 94 95 { "MenuItem", { B_CREATE_PROPERTY, 0 }, 96 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 97 "Adds a new menu item at the specified index with the text label found in \"data\" " 98 "and the int32 command found in \"what\" (used as the what field in the CMessage " 99 "sent by the item)." }, 100 101 { "MenuItem", { B_DELETE_PROPERTY, 0 }, 102 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 103 "Removes the specified menu item from its parent menu." }, 104 105 { "MenuItem", { B_EXECUTE_PROPERTY, 0 }, 106 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 107 "Invokes the specified menu item." }, 108 109 { "MenuItem", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 110 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 }, 111 "Directs scripting message to the specified menu, first popping the current " 112 "specifier off the stack." }, 113 {} 114 }; 115 116 117 BMenu::BMenu(const char *name, menu_layout layout) 118 : BView(BRect(), name, 0, B_WILL_DRAW), 119 fChosenItem(NULL), 120 fPad(14.0f, 2.0f, 20.0f, 0.0f), 121 fSelected(NULL), 122 fCachedMenuWindow(NULL), 123 fSuper(NULL), 124 fSuperitem(NULL), 125 fAscent(-1.0f), 126 fDescent(-1.0f), 127 fFontHeight(-1.0f), 128 fState(0), 129 fLayout(layout), 130 fExtraRect(NULL), 131 fMaxContentWidth(0.0f), 132 fInitMatrixSize(NULL), 133 fExtraMenuData(NULL), 134 fTrigger(0), 135 fResizeToFit(true), 136 fUseCachedMenuLayout(true), 137 fEnabled(true), 138 fDynamicName(false), 139 fRadioMode(false), 140 fTrackNewBounds(false), 141 fStickyMode(false), 142 fIgnoreHidden(true), 143 fTriggerEnabled(true), 144 fRedrawAfterSticky(false), 145 fAttachAborted(false) 146 { 147 InitData(NULL); 148 } 149 150 151 BMenu::BMenu(const char *name, float width, float height) 152 : BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW), 153 fChosenItem(NULL), 154 fSelected(NULL), 155 fCachedMenuWindow(NULL), 156 fSuper(NULL), 157 fSuperitem(NULL), 158 fAscent(-1.0f), 159 fDescent(-1.0f), 160 fFontHeight(-1.0f), 161 fState(0), 162 fLayout(B_ITEMS_IN_MATRIX), 163 fExtraRect(NULL), 164 fMaxContentWidth(0.0f), 165 fInitMatrixSize(NULL), 166 fExtraMenuData(NULL), 167 fTrigger(0), 168 fResizeToFit(true), 169 fUseCachedMenuLayout(true), 170 fEnabled(true), 171 fDynamicName(false), 172 fRadioMode(false), 173 fTrackNewBounds(false), 174 fStickyMode(false), 175 fIgnoreHidden(true), 176 fTriggerEnabled(true), 177 fRedrawAfterSticky(false), 178 fAttachAborted(false) 179 { 180 InitData(NULL); 181 } 182 183 184 BMenu::~BMenu() 185 { 186 RemoveItems(0, CountItems(), true); 187 188 delete fCachedMenuWindow; 189 delete fInitMatrixSize; 190 delete fExtraMenuData; 191 } 192 193 194 BMenu::BMenu(BMessage *archive) 195 : BView(archive) 196 { 197 InitData(archive); 198 } 199 200 201 BArchivable * 202 BMenu::Instantiate(BMessage *data) 203 { 204 if (validate_instantiation(data, "BMenu")) 205 return new BMenu(data); 206 else 207 return NULL; 208 } 209 210 211 status_t 212 BMenu::Archive(BMessage *data, bool deep) const 213 { 214 status_t err = BView::Archive(data, deep); 215 216 if (err < B_OK) 217 return err; 218 219 if (Layout() != B_ITEMS_IN_ROW) { 220 err = data->AddInt32("_layout", Layout()); 221 222 if (err < B_OK) 223 return err; 224 } 225 226 err = data->AddBool("_rsize_to_fit", fResizeToFit); 227 228 if (err < B_OK) 229 return err; 230 231 err = data->AddBool("_disable", !IsEnabled()); 232 233 if (err < B_OK) 234 return err; 235 236 err = data->AddBool("_radio", IsRadioMode()); 237 238 if (err < B_OK) 239 return err; 240 241 err = data->AddBool("_trig_disabled", AreTriggersEnabled()); 242 243 if (err < B_OK) 244 return err; 245 246 err = data->AddBool("_dyn_label", fDynamicName); 247 248 if (err < B_OK) 249 return err; 250 251 err = data->AddFloat("_maxwidth", fMaxContentWidth); 252 253 if (err < B_OK) 254 return err; 255 256 if (deep) { 257 // TODO store items and rects 258 } 259 260 return err; 261 } 262 263 264 void 265 BMenu::AttachedToWindow() 266 { 267 BView::AttachedToWindow(); 268 269 InvalidateLayout(); 270 } 271 272 273 void 274 BMenu::DetachedFromWindow() 275 { 276 BView::DetachedFromWindow(); 277 } 278 279 280 bool 281 BMenu::AddItem(BMenuItem *item) 282 { 283 return AddItem(item, CountItems()); 284 } 285 286 287 bool 288 BMenu::AddItem(BMenuItem *item, int32 index) 289 { 290 if (fLayout == B_ITEMS_IN_MATRIX) 291 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only" 292 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 293 294 return _AddItem(item, index); 295 } 296 297 298 bool 299 BMenu::AddItem(BMenuItem *item, BRect frame) 300 { 301 if (fLayout != B_ITEMS_IN_MATRIX) 302 debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only" 303 " be called if the menu layout is B_ITEMS_IN_MATRIX"); 304 305 if (!item) 306 return false; 307 308 item->fBounds = frame; 309 310 return _AddItem(item, CountItems()); 311 } 312 313 314 bool 315 BMenu::AddItem(BMenu *submenu) 316 { 317 BMenuItem *item = new BMenuItem(submenu); 318 if (!item) 319 return false; 320 321 return _AddItem(item, CountItems()); 322 } 323 324 325 bool 326 BMenu::AddItem(BMenu *submenu, int32 index) 327 { 328 if (fLayout == B_ITEMS_IN_MATRIX) 329 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only" 330 "be called if the menu layout is not B_ITEMS_IN_MATRIX"); 331 332 BMenuItem *item = new BMenuItem(submenu); 333 if (!item) 334 return false; 335 336 return _AddItem(item, index); 337 } 338 339 340 bool 341 BMenu::AddItem(BMenu *submenu, BRect frame) 342 { 343 if (fLayout != B_ITEMS_IN_MATRIX) 344 debugger("BMenu::AddItem(BMenu *, BRect) this method can only" 345 " be called if the menu layout is B_ITEMS_IN_MATRIX"); 346 347 BMenuItem *item = new BMenuItem(submenu); 348 item->fBounds = frame; 349 350 return _AddItem(item, CountItems()); 351 } 352 353 354 bool 355 BMenu::AddList(BList *list, int32 index) 356 { 357 // TODO: test this function, it's not documented in the bebook. 358 int32 numItems = 0; 359 if (list != NULL) 360 numItems = list->CountItems(); 361 362 for (int32 i = 0; i < numItems; i++) { 363 BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i)); 364 if (item != NULL) 365 _AddItem(item, index + i); 366 } 367 368 // TODO: return false if needed 369 return true; 370 } 371 372 373 bool 374 BMenu::AddSeparatorItem() 375 { 376 BMenuItem *item = new BSeparatorItem(); 377 return _AddItem(item, CountItems()); 378 } 379 380 381 bool 382 BMenu::RemoveItem(BMenuItem *item) 383 { 384 // TODO: Check if item is also deleted 385 return RemoveItems(0, 0, item, false); 386 } 387 388 389 BMenuItem * 390 BMenu::RemoveItem(int32 index) 391 { 392 BMenuItem *item = ItemAt(index); 393 RemoveItems(index, 1, NULL, false); 394 return item; 395 } 396 397 398 bool 399 BMenu::RemoveItems(int32 index, int32 count, bool del) 400 { 401 return RemoveItems(index, count, NULL, del); 402 } 403 404 405 bool 406 BMenu::RemoveItem(BMenu *submenu) 407 { 408 for (int i = 0; i < fItems.CountItems(); i++) 409 if (static_cast<BMenuItem *>(fItems.ItemAt(i))->Submenu() == submenu) 410 return RemoveItems(i, 1, NULL, false); 411 412 return false; 413 } 414 415 416 int32 417 BMenu::CountItems() const 418 { 419 return fItems.CountItems(); 420 } 421 422 423 BMenuItem * 424 BMenu::ItemAt(int32 index) const 425 { 426 return static_cast<BMenuItem *>(fItems.ItemAt(index)); 427 } 428 429 430 BMenu * 431 BMenu::SubmenuAt(int32 index) const 432 { 433 return static_cast<BMenuItem *>(fItems.ItemAt(index))->Submenu(); 434 } 435 436 437 int32 438 BMenu::IndexOf(BMenuItem *item) const 439 { 440 return fItems.IndexOf(item); 441 } 442 443 444 int32 445 BMenu::IndexOf(BMenu *submenu) const 446 { 447 for (int32 i = 0; i < fItems.CountItems(); i++) 448 if (static_cast<BMenuItem *>(fItems.ItemAt(i))->Submenu() == submenu) 449 return i; 450 451 return -1; 452 } 453 454 455 BMenuItem * 456 BMenu::FindItem(const char *label) const 457 { 458 BMenuItem *item = NULL; 459 460 for (int32 i = 0; i < CountItems(); i++) { 461 item = ItemAt(i); 462 463 if (item->Label() && strcmp(item->Label(), label) == 0) 464 break; 465 466 if (item->Submenu()) { 467 item = item->Submenu()->FindItem(label); 468 if (item) 469 break; 470 } 471 } 472 473 return item; 474 } 475 476 477 BMenuItem * 478 BMenu::FindItem(uint32 command) const 479 { 480 BMenuItem *item = NULL; 481 482 for (int32 i = 0; i < CountItems(); i++) { 483 item = ItemAt(i); 484 485 if (item->Command() == command) 486 break; 487 488 if (item->Submenu()) { 489 item = item->Submenu()->FindItem(command); 490 if (item) 491 break; 492 } 493 } 494 495 return item; 496 } 497 498 499 status_t 500 BMenu::SetTargetForItems(BHandler *handler) 501 { 502 for (int32 i = 0; i < fItems.CountItems(); i++) 503 if (static_cast<BMenuItem *>(fItems.ItemAt(i))->SetTarget(handler) != B_OK) 504 return B_ERROR; 505 506 return B_OK; 507 } 508 509 510 status_t 511 BMenu::SetTargetForItems(BMessenger messenger) 512 { 513 for (int32 i = 0; i < fItems.CountItems (); i++) 514 if (ItemAt(i)->SetTarget(messenger) != B_OK) 515 return B_ERROR; 516 517 return B_OK; 518 } 519 520 521 void 522 BMenu::SetEnabled(bool enabled) 523 { 524 fEnabled = enabled; 525 526 for (int32 i = 0; i < CountItems(); i++) 527 ItemAt(i)->SetEnabled(enabled); 528 } 529 530 531 void 532 BMenu::SetRadioMode(bool flag) 533 { 534 fRadioMode = flag; 535 } 536 537 538 void 539 BMenu::SetTriggersEnabled(bool flag) 540 { 541 fTriggerEnabled = flag; 542 } 543 544 545 void 546 BMenu::SetMaxContentWidth(float width) 547 { 548 fMaxContentWidth = width; 549 } 550 551 552 void 553 BMenu::SetLabelFromMarked(bool flag) 554 { 555 fDynamicName = flag; 556 } 557 558 559 bool 560 BMenu::IsLabelFromMarked() 561 { 562 return fDynamicName; 563 } 564 565 566 bool 567 BMenu::IsEnabled() const 568 { 569 return fEnabled; 570 } 571 572 573 bool 574 BMenu::IsRadioMode() const 575 { 576 return fRadioMode; 577 } 578 579 580 bool 581 BMenu::AreTriggersEnabled() const 582 { 583 return fTriggerEnabled; 584 } 585 586 587 bool 588 BMenu::IsRedrawAfterSticky() const 589 { 590 return fRedrawAfterSticky; 591 } 592 593 594 float 595 BMenu::MaxContentWidth() const 596 { 597 return fMaxContentWidth; 598 } 599 600 601 BMenuItem * 602 BMenu::FindMarked() 603 { 604 for (int i = 0; i < fItems.CountItems(); i++) 605 if (((BMenuItem*)fItems.ItemAt(i))->IsMarked()) 606 return (BMenuItem*)fItems.ItemAt(i); 607 608 return NULL; 609 } 610 611 612 BMenu * 613 BMenu::Supermenu() const 614 { 615 return fSuper; 616 } 617 618 619 BMenuItem * 620 BMenu::Superitem() const 621 { 622 return fSuperitem; 623 } 624 625 626 void 627 BMenu::MessageReceived(BMessage *msg) 628 { 629 BView::MessageReceived(msg); 630 } 631 632 633 void 634 BMenu::KeyDown(const char *bytes, int32 numBytes) 635 { 636 switch (bytes[0]) { 637 case B_UP_ARROW: 638 { 639 if (fSelected) { 640 fSelected->fSelected = false; 641 642 if (fSelected == fItems.FirstItem()) 643 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 644 else 645 fSelected = ItemAt(IndexOf(fSelected) - 1); 646 } else 647 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 648 649 fSelected->fSelected = true; 650 651 break; 652 } 653 case B_DOWN_ARROW: 654 { 655 if (fSelected) { 656 fSelected->fSelected = false; 657 658 if (fSelected == fItems.LastItem()) 659 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 660 else 661 fSelected = ItemAt(IndexOf(fSelected) + 1); 662 } else 663 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 664 665 fSelected->fSelected = true; 666 667 break; 668 } 669 case B_HOME: 670 { 671 if (fSelected) 672 fSelected->fSelected = false; 673 674 fSelected = static_cast<BMenuItem *>(fItems.FirstItem()); 675 fSelected->fSelected = true; 676 677 break; 678 } 679 case B_END: 680 { 681 if (fSelected) 682 fSelected->fSelected = false; 683 684 fSelected = static_cast<BMenuItem *>(fItems.LastItem()); 685 fSelected->fSelected = true; 686 687 break; 688 } 689 case B_ENTER: 690 case B_SPACE: 691 { 692 if (fSelected) 693 InvokeItem(fSelected); 694 695 break; 696 } 697 default: 698 BView::KeyDown(bytes, numBytes); 699 } 700 } 701 702 703 void 704 BMenu::Draw(BRect updateRect) 705 { 706 DrawBackground(updateRect); 707 DrawItems(updateRect); 708 } 709 710 711 void 712 BMenu::GetPreferredSize(float *width, float *height) 713 { 714 ComputeLayout(0, true, false, width, height); 715 } 716 717 718 void 719 BMenu::ResizeToPreferred() 720 { 721 BView::ResizeToPreferred(); 722 } 723 724 725 void 726 BMenu::FrameMoved(BPoint new_position) 727 { 728 BView::FrameMoved(new_position); 729 } 730 731 732 void 733 BMenu::FrameResized(float new_width, float new_height) 734 { 735 BView::FrameResized(new_width, new_height); 736 } 737 738 739 void 740 BMenu::InvalidateLayout() 741 { 742 CacheFontInfo(); 743 LayoutItems(0); 744 Invalidate(); 745 } 746 747 748 BHandler * 749 BMenu::ResolveSpecifier(BMessage *msg, int32 index, 750 BMessage *specifier, int32 form, 751 const char *property) 752 { 753 BPropertyInfo propInfo(sPropList); 754 BHandler *target = NULL; 755 756 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) { 757 case B_ERROR: 758 break; 759 760 case 0: 761 case 1: 762 case 2: 763 case 3: 764 case 4: 765 case 5: 766 case 6: 767 case 7: 768 target = this; 769 break; 770 case 8: 771 // TODO: redirect to menu 772 target = this; 773 break; 774 case 9: 775 case 10: 776 case 11: 777 case 12: 778 target = this; 779 break; 780 case 13: 781 // TODO: redirect to menuitem 782 target = this; 783 break; 784 } 785 786 if (!target) 787 target = BView::ResolveSpecifier(msg, index, specifier, form, 788 property); 789 790 return target; 791 } 792 793 794 status_t 795 BMenu::GetSupportedSuites(BMessage *data) 796 { 797 status_t err; 798 799 if (data == NULL) 800 return B_BAD_VALUE; 801 802 err = data->AddString("suites", "suite/vnd.Be-menu"); 803 804 if (err < B_OK) 805 return err; 806 807 BPropertyInfo prop_info(sPropList); 808 err = data->AddFlat("messages", &prop_info); 809 810 if (err < B_OK) 811 return err; 812 813 return BView::GetSupportedSuites(data); 814 } 815 816 817 status_t 818 BMenu::Perform(perform_code d, void *arg) 819 { 820 return BView::Perform(d, arg); 821 } 822 823 824 void 825 BMenu::MakeFocus(bool focused) 826 { 827 BView::MakeFocus(focused); 828 } 829 830 831 void 832 BMenu::AllAttached() 833 { 834 BView::AllAttached(); 835 } 836 837 838 void 839 BMenu::AllDetached() 840 { 841 BView::AllDetached(); 842 } 843 844 845 BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags, 846 menu_layout layout, bool resizeToFit) 847 : BView(frame, name, resizingMode, flags), 848 fChosenItem(NULL), 849 fSelected(NULL), 850 fCachedMenuWindow(NULL), 851 fSuper(NULL), 852 fSuperitem(NULL), 853 fAscent(-1.0f), 854 fDescent(-1.0f), 855 fFontHeight(-1.0f), 856 fState(0), 857 fLayout(layout), 858 fExtraRect(NULL), 859 fMaxContentWidth(0.0f), 860 fInitMatrixSize(NULL), 861 fExtraMenuData(NULL), 862 fTrigger(0), 863 fResizeToFit(resizeToFit), 864 fUseCachedMenuLayout(true), 865 fEnabled(true), 866 fDynamicName(false), 867 fRadioMode(false), 868 fTrackNewBounds(false), 869 fStickyMode(false), 870 fIgnoreHidden(true), 871 fTriggerEnabled(true), 872 fRedrawAfterSticky(false), 873 fAttachAborted(false) 874 { 875 InitData(NULL); 876 } 877 878 879 BPoint 880 BMenu::ScreenLocation() 881 { 882 BMenu *superMenu = Supermenu(); 883 BMenuItem *superItem = Superitem(); 884 885 if (superMenu == NULL && superItem == NULL) { 886 debugger("BMenu can't determine where to draw." 887 "Override BMenu::ScreenLocation() to determine location."); 888 } 889 890 BPoint point; 891 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) 892 point = superItem->Frame().RightTop(); 893 else 894 point = superItem->Frame().LeftBottom() + BPoint(0.0f, 1.0f); 895 896 superMenu->ConvertToScreen(&point); 897 898 return point; 899 } 900 901 902 void 903 BMenu::SetItemMargins(float left, float top, float right, float bottom) 904 { 905 fPad.Set(left, top, right, bottom); 906 } 907 908 909 void 910 BMenu::GetItemMargins(float *left, float *top, float *right, 911 float *bottom) const 912 { 913 if (left != NULL) 914 *left = fPad.left; 915 if (top != NULL) 916 *top = fPad.top; 917 if (right != NULL) 918 *right = fPad.right; 919 if (bottom != NULL) 920 *bottom = fPad.bottom; 921 } 922 923 924 menu_layout 925 BMenu::Layout() const 926 { 927 return fLayout; 928 } 929 930 931 void 932 BMenu::Show() 933 { 934 Show(false); 935 } 936 937 938 void 939 BMenu::Show(bool selectFirst) 940 { 941 _show(selectFirst); 942 } 943 944 945 void 946 BMenu::Hide() 947 { 948 _hide(); 949 } 950 951 952 BMenuItem * 953 BMenu::Track(bool openAnyway, BRect *clickToOpenRect) 954 { 955 if (IsStickyPrefOn()) 956 openAnyway = false; 957 958 SetStickyMode(openAnyway); 959 960 if (LockLooper()) { 961 RedrawAfterSticky(Bounds()); 962 UnlockLooper(); 963 } 964 965 if (clickToOpenRect != NULL && LockLooper()) { 966 fExtraRect = clickToOpenRect; 967 ConvertFromScreen(fExtraRect); 968 UnlockLooper(); 969 } 970 971 int action; 972 BMenuItem *menuItem = _track(&action, -1); 973 974 SetStickyMode(false); 975 fExtraRect = NULL; 976 977 return menuItem; 978 } 979 980 981 bool 982 BMenu::AddDynamicItem(add_state s) 983 { 984 // Implemented in subclasses 985 return false; 986 } 987 988 989 void 990 BMenu::DrawBackground(BRect update) 991 { 992 BRect rect = Bounds() & update; 993 rgb_color oldColor = HighColor(); 994 995 SetHighColor(sMenuInfo.background_color); 996 FillRect(rect, B_SOLID_HIGH); 997 998 SetHighColor(oldColor); 999 } 1000 1001 1002 void 1003 BMenu::SetTrackingHook(menu_tracking_hook func, void *state) 1004 { 1005 delete fExtraMenuData; 1006 fExtraMenuData = new _ExtraMenuData_(func, state); 1007 } 1008 1009 1010 void BMenu::_ReservedMenu3() {} 1011 void BMenu::_ReservedMenu4() {} 1012 void BMenu::_ReservedMenu5() {} 1013 void BMenu::_ReservedMenu6() {} 1014 1015 1016 BMenu & 1017 BMenu::operator=(const BMenu &) 1018 { 1019 return *this; 1020 } 1021 1022 1023 void 1024 BMenu::InitData(BMessage *data) 1025 { 1026 // TODO: Get _color, _fname, _fflt from the message, if present 1027 BFont font; 1028 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style); 1029 font.SetSize(sMenuInfo.font_size); 1030 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); 1031 1032 SetLowColor(sMenuInfo.background_color); 1033 SetViewColor(sMenuInfo.background_color); 1034 1035 if (data != NULL) { 1036 data->FindInt32("_layout", (int32 *)&fLayout); 1037 data->FindBool("_rsize_to_fit", &fResizeToFit); 1038 data->FindBool("_disable", &fEnabled); 1039 data->FindBool("_radio", &fRadioMode); 1040 1041 bool disableTrigger = false; 1042 data->FindBool("_trig_disabled", &disableTrigger); 1043 fTriggerEnabled = !disableTrigger; 1044 1045 data->FindBool("_dyn_label", &fDynamicName); 1046 data->FindFloat("_maxwidth", &fMaxContentWidth); 1047 } 1048 } 1049 1050 1051 bool 1052 BMenu::_show(bool selectFirstItem) 1053 { 1054 // Menu windows get the BMenu's handler name 1055 fCachedMenuWindow = new BMenuWindow(Name()); 1056 1057 fCachedMenuWindow->ChildAt(0)->AddChild(this); 1058 1059 // We're doing this here because ConvertToScreen() needs: 1060 // 1. The BView to be attached (see the above line). 1061 // 2. The looper locked or not running (the Show() call below starts the looper) 1062 if (fSuper != NULL) 1063 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds()); 1064 fCachedMenuWindow->ResizeTo(Bounds().Width() + 4, Bounds().Height() + 4); 1065 fCachedMenuWindow->MoveTo(ScreenLocation()); 1066 fCachedMenuWindow->Show(); 1067 1068 return true; 1069 } 1070 1071 1072 void 1073 BMenu::_hide() 1074 { 1075 if (fCachedMenuWindow != NULL) { 1076 fCachedMenuWindow->Lock(); 1077 fCachedMenuWindow->ChildAt(0)->RemoveChild(this); 1078 fCachedMenuWindow->Quit(); 1079 fCachedMenuWindow = NULL; 1080 } 1081 1082 } 1083 1084 1085 BMenuItem * 1086 BMenu::_track(int *action, long start) 1087 { 1088 // TODO: Take Sticky mode into account 1089 BPoint location; 1090 ulong buttons; 1091 BMenuItem *item = NULL; 1092 int localAction = 0; 1093 do { 1094 if (LockLooper()) { 1095 GetMouse(&location, &buttons); 1096 if (OverSuper(location)) { 1097 UnlockLooper(); 1098 break; 1099 } 1100 1101 item = HitTestItems(location, B_ORIGIN); 1102 1103 // TODO: Sometimes the menu flickers a bit. 1104 // try to be smarter and suggest an update area, 1105 // instead of invalidating the whole view. 1106 if (item != NULL) { 1107 if (item != fSelected) { 1108 SelectItem(item); 1109 Invalidate(); 1110 } 1111 1112 int submenuAction = 0; 1113 BMenuItem *submenuItem = NULL; 1114 // TODO: Review this as it doesn't work very well, 1115 // BMenu::_track() isn't always called when needed. 1116 if (item->Submenu() != NULL) { 1117 UnlockLooper(); 1118 1119 submenuItem = item->Submenu()->_track(&submenuAction); 1120 if (submenuAction == 5) { 1121 item = submenuItem; 1122 localAction = submenuAction; 1123 break; 1124 } 1125 1126 if (!LockLooper()) 1127 break; 1128 } 1129 } 1130 1131 UnlockLooper(); 1132 } 1133 1134 snooze(50000); 1135 } while (buttons != 0); 1136 1137 // TODO: A deeper investigation of actions 1138 // would be nice. Consider building an enum 1139 // with the possible actions, and putting it in a 1140 // private, shared header (BMenuBar needs to know about them too). 1141 if (localAction == 0) { 1142 if (buttons != 0) 1143 localAction = 0; 1144 else 1145 localAction = 5; 1146 } 1147 1148 if (action != NULL) 1149 *action = localAction; 1150 1151 if (LockLooper()) { 1152 SelectItem(NULL); 1153 UnlockLooper(); 1154 } 1155 1156 return item; 1157 } 1158 1159 1160 bool 1161 BMenu::_AddItem(BMenuItem *item, int32 index) 1162 { 1163 ASSERT(item != NULL); 1164 1165 item->SetSuper(this); 1166 1167 bool err = fItems.AddItem(item, index); 1168 1169 if (!err) 1170 return err; 1171 1172 // Make sure we update the layout in case we are already attached. 1173 if (Window() && fResizeToFit) { 1174 LayoutItems(index); 1175 Invalidate(); 1176 } 1177 1178 // Find the root menu window, so we can install this item. 1179 BMenu *root = this; 1180 while (root->Supermenu()) 1181 root = root->Supermenu(); 1182 1183 if (root->Window()) 1184 Install(root->Window()); 1185 1186 return err; 1187 } 1188 1189 1190 bool 1191 BMenu::RemoveItems(int32 index, int32 count, BMenuItem *_item, bool del) 1192 { 1193 bool result = false; 1194 1195 // The plan is simple: If we're given a BMenuItem directly, we use it 1196 // and ignore index and count. Otherwise, we use them instead. 1197 if (_item != NULL) { 1198 fItems.RemoveItem(_item); 1199 _item->SetSuper(NULL); 1200 _item->Uninstall(); 1201 if (del) 1202 delete _item; 1203 result = true; 1204 } else { 1205 BMenuItem *item = NULL; 1206 // We iterate backwards because it's simpler 1207 // TODO: We should check if index and count are in bounds. 1208 for (int32 i = index + count - 1; i >= index; i--) { 1209 item = static_cast<BMenuItem *>(fItems.ItemAt(index)); 1210 if (item != NULL) { 1211 fItems.RemoveItem(item); 1212 item->SetSuper(NULL); 1213 item->Uninstall(); 1214 if (del) 1215 delete item; 1216 if (!result) 1217 result = true; 1218 } 1219 } 1220 } 1221 1222 InvalidateLayout(); 1223 1224 return result; 1225 } 1226 1227 1228 void 1229 BMenu::LayoutItems(int32 index) 1230 { 1231 CalcTriggers(); 1232 1233 float width, height; 1234 ComputeLayout(index, true, true, &width, &height); 1235 1236 ResizeTo(width, height); 1237 1238 // Move the BMenu to 2, 2, if it's attached to a BMenuWindow, 1239 // (that means it's a BMenu, BMenuBars are attached to regular BWindows). 1240 // This is needed to be able to draw the frame around the BMenu. 1241 if (dynamic_cast<BMenuWindow *>(Window()) != NULL) 1242 MoveTo(2, 2); 1243 } 1244 1245 1246 void 1247 BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems, 1248 float* width, float* height) 1249 { 1250 // TODO: Take "bestFit", "moveItems", "index" into account. 1251 BRect frame(0, 0, 0, 0); 1252 float iWidth, iHeight; 1253 BMenuItem *item = NULL; 1254 1255 switch (fLayout) { 1256 case B_ITEMS_IN_COLUMN: 1257 { 1258 for (int32 i = 0; i < fItems.CountItems(); i++) { 1259 item = ItemAt(i); 1260 if (item != NULL) { 1261 item->GetContentSize(&iWidth, &iHeight); 1262 1263 if (item->fModifiers && item->fShortcutChar) 1264 iWidth += 25.0f; 1265 1266 item->fBounds.left = 0.0f; 1267 item->fBounds.top = frame.bottom; 1268 item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom; 1269 1270 frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right) + 20; 1271 frame.bottom = item->fBounds.bottom + 1.0f; 1272 } 1273 } 1274 1275 for (int32 i = 0; i < fItems.CountItems(); i++) 1276 ItemAt(i)->fBounds.right = frame.right; 1277 1278 frame.right = (float)ceil(frame.right); 1279 frame.bottom--; 1280 break; 1281 } 1282 1283 case B_ITEMS_IN_ROW: 1284 { 1285 font_height fh; 1286 GetFontHeight(&fh); 1287 frame = BRect(0.0f, 0.0f, 0.0f, 1288 (float)ceil(fh.ascent) + (float)ceil(fh.descent) + fPad.top + fPad.bottom); 1289 1290 for (int32 i = 0; i < fItems.CountItems(); i++) { 1291 item = ItemAt(i); 1292 if (item != NULL) { 1293 item->GetContentSize(&iWidth, &iHeight); 1294 1295 item->fBounds.left = frame.right; 1296 item->fBounds.top = 0.0f; 1297 item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right; 1298 1299 frame.right = item->fBounds.right + 1.0f; 1300 frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom); 1301 } 1302 } 1303 1304 for (int32 i = 0; i < fItems.CountItems(); i++) 1305 ItemAt(i)->fBounds.bottom = frame.bottom; 1306 1307 frame.right = (float)ceil(frame.right) + 8.0f; 1308 break; 1309 } 1310 1311 case B_ITEMS_IN_MATRIX: 1312 { 1313 for (int32 i = 0; i < CountItems(); i++) { 1314 item = ItemAt(i); 1315 if (item != NULL) { 1316 frame.left = min_c(frame.left, item->Frame().left); 1317 frame.right = max_c(frame.right, item->Frame().right); 1318 frame.top = min_c(frame.top, item->Frame().top); 1319 frame.bottom = max_c(frame.bottom, item->Frame().bottom); 1320 } 1321 } 1322 break; 1323 } 1324 1325 default: 1326 break; 1327 } 1328 1329 // This is for BMenuBar. 1330 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) { 1331 if (Parent()) 1332 *width = Parent()->Frame().Width() + 1; 1333 else 1334 *width = Window()->Frame().Width() + 1; 1335 1336 *height = frame.Height(); 1337 } else { 1338 *width = frame.Width(); 1339 *height = frame.Height(); 1340 } 1341 } 1342 1343 1344 BRect 1345 BMenu::Bump(BRect current, BPoint extent, int32 index) const 1346 { 1347 return BRect(); 1348 } 1349 1350 1351 BPoint 1352 BMenu::ItemLocInRect(BRect frame) const 1353 { 1354 return BPoint(); 1355 } 1356 1357 1358 BRect 1359 BMenu::CalcFrame(BPoint where, bool *scrollOn) 1360 { 1361 return BRect(); 1362 } 1363 1364 1365 bool 1366 BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast) 1367 { 1368 return false; 1369 } 1370 1371 1372 void 1373 BMenu::ScrollIntoView(BMenuItem *item) 1374 { 1375 } 1376 1377 1378 void 1379 BMenu::DrawItems(BRect updateRect) 1380 { 1381 for (int32 i = 0; i < fItems.CountItems(); i++) { 1382 if (ItemAt(i)->Frame().Intersects(updateRect)) 1383 ItemAt(i)->Draw(); 1384 } 1385 } 1386 1387 1388 int 1389 BMenu::State(BMenuItem **item) const 1390 { 1391 return 0; 1392 } 1393 1394 1395 void 1396 BMenu::InvokeItem(BMenuItem *item, bool now) 1397 { 1398 if (item->Submenu()) 1399 item->Submenu()->Show(); 1400 else if (IsRadioMode()) 1401 item->SetMarked(true); 1402 1403 item->Invoke(); 1404 } 1405 1406 1407 bool 1408 BMenu::OverSuper(BPoint location) 1409 { 1410 if (!Supermenu()) 1411 return false; 1412 1413 ConvertToScreen(&location); 1414 1415 return fSuperbounds.Contains(location); 1416 } 1417 1418 1419 bool 1420 BMenu::OverSubmenu(BMenuItem *item, BPoint loc) 1421 { 1422 // TODO: we assume that loc is in screen coords 1423 if (!item->Submenu()) 1424 return false; 1425 1426 return item->Submenu()->Window()->Frame().Contains(loc); 1427 } 1428 1429 1430 BMenuWindow * 1431 BMenu::MenuWindow() 1432 { 1433 return fCachedMenuWindow; 1434 } 1435 1436 1437 void 1438 BMenu::DeleteMenuWindow() 1439 { 1440 delete fCachedMenuWindow; 1441 fCachedMenuWindow = NULL; 1442 } 1443 1444 1445 BMenuItem * 1446 BMenu::HitTestItems(BPoint where, BPoint slop) const 1447 { 1448 // TODO: Take "slop" into account ? 1449 int32 itemCount = CountItems(); 1450 for (int32 i = 0; i < itemCount; i++) { 1451 BMenuItem *item = ItemAt(i); 1452 if (item->Frame().Contains(where)) 1453 return item; 1454 } 1455 1456 return NULL; 1457 } 1458 1459 1460 BRect 1461 BMenu::Superbounds() const 1462 { 1463 return fSuperbounds; 1464 } 1465 1466 1467 void 1468 BMenu::CacheFontInfo() 1469 { 1470 font_height fh; 1471 GetFontHeight(&fh); 1472 fAscent = fh.ascent; 1473 fDescent = fh.descent; 1474 fFontHeight = (float)ceil(fh.ascent + fh.descent + fh.leading); 1475 } 1476 1477 1478 void 1479 BMenu::ItemMarked(BMenuItem *item) 1480 { 1481 if (IsRadioMode()) { 1482 for (int32 i = 0; i < CountItems(); i++) 1483 if (ItemAt(i) != item) 1484 ItemAt(i)->SetMarked(false); 1485 } 1486 1487 if (IsLabelFromMarked() && Superitem()) 1488 Superitem()->SetLabel(item->Label()); 1489 } 1490 1491 1492 void 1493 BMenu::Install(BWindow *target) 1494 { 1495 for (int32 i = 0; i < CountItems(); i++) 1496 ItemAt(i)->Install(target); 1497 } 1498 1499 1500 void 1501 BMenu::Uninstall() 1502 { 1503 for (int32 i = 0; i < CountItems(); i++) 1504 ItemAt(i)->Uninstall(); 1505 } 1506 1507 1508 void 1509 BMenu::SelectItem(BMenuItem *menuItem, uint32 showSubmenu, bool selectFirstItem) 1510 { 1511 // TODO: make use of "showSubmenu" and "selectFirstItem". 1512 if (fSelected != NULL) { 1513 fSelected->Select(false); 1514 if (fSelected->Submenu() != NULL) 1515 fSelected->Submenu()->_hide(); 1516 } 1517 1518 if (menuItem != NULL) 1519 menuItem->Select(true); 1520 1521 fSelected = menuItem; 1522 if (fSelected != NULL && fSelected->Submenu() != NULL) 1523 fSelected->Submenu()->_show(); 1524 1525 } 1526 1527 1528 BMenuItem * 1529 BMenu::CurrentSelection() const 1530 { 1531 return fSelected; 1532 } 1533 1534 1535 bool 1536 BMenu::SelectNextItem(BMenuItem *item, bool forward) 1537 { 1538 return false; 1539 } 1540 1541 1542 BMenuItem * 1543 BMenu::NextItem(BMenuItem *item, bool forward) const 1544 { 1545 return NULL; 1546 } 1547 1548 1549 bool 1550 BMenu::IsItemVisible(BMenuItem *item) const 1551 { 1552 return false; 1553 } 1554 1555 1556 void 1557 BMenu::SetIgnoreHidden(bool on) 1558 { 1559 fIgnoreHidden = on; 1560 } 1561 1562 1563 void 1564 BMenu::SetStickyMode(bool on) 1565 { 1566 fStickyMode = on; 1567 } 1568 1569 1570 bool 1571 BMenu::IsStickyMode() const 1572 { 1573 return fStickyMode; 1574 } 1575 1576 1577 void 1578 BMenu::CalcTriggers() 1579 { 1580 BList triggersList; 1581 1582 // Gathers the existing triggers 1583 // TODO: Oh great, reinterpret_cast. 1584 for (int32 i = 0; i < CountItems(); i++) { 1585 char trigger = ItemAt(i)->Trigger(); 1586 if (trigger != 0) 1587 triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger)); 1588 } 1589 1590 // Set triggers for items which don't have one yet 1591 for (int32 i = 0; i < CountItems(); i++) { 1592 BMenuItem *item = ItemAt(i); 1593 if (item->Trigger() == 0) { 1594 const char *newTrigger = ChooseTrigger(item->Label(), &triggersList); 1595 if (newTrigger != NULL) { 1596 item->SetSysTrigger(*newTrigger); 1597 // TODO: This is crap. I'd prefer to have 1598 // BMenuItem::SetSysTrigger(const char *) update fTriggerIndex. 1599 // This isn't the case on beos, but it will probably be like that on haiku. 1600 item->fTriggerIndex = newTrigger - item->Label(); 1601 } 1602 } 1603 } 1604 } 1605 1606 1607 const char * 1608 BMenu::ChooseTrigger(const char *title, BList *chars) 1609 { 1610 ASSERT(chars != NULL); 1611 1612 if (title == NULL) 1613 return NULL; 1614 1615 char *titlePtr = const_cast<char *>(title); 1616 1617 char trigger; 1618 // TODO: Oh great, reinterpret_cast all around 1619 while ((trigger = *titlePtr) != '\0') { 1620 if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger))) { 1621 chars->AddItem(reinterpret_cast<void *>((uint32)trigger)); 1622 return titlePtr; 1623 } 1624 1625 titlePtr++; 1626 } 1627 1628 return NULL; 1629 } 1630 1631 1632 void 1633 BMenu::UpdateWindowViewSize(bool upWind) 1634 { 1635 } 1636 1637 1638 bool 1639 BMenu::IsStickyPrefOn() 1640 { 1641 return sMenuInfo.click_to_open; 1642 } 1643 1644 1645 void 1646 BMenu::RedrawAfterSticky(BRect bounds) 1647 { 1648 } 1649 1650 1651 bool 1652 BMenu::OkToProceed(BMenuItem *) 1653 { 1654 return false; 1655 } 1656 1657 1658 status_t 1659 BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec, 1660 int32 *form, const char **prop, BMenu **tmenu, 1661 BMenuItem **titem, int32 *user_data, 1662 BMessage *reply) const 1663 { 1664 return B_ERROR; 1665 } 1666 1667 1668 status_t 1669 BMenu::DoMenuMsg(BMenuItem **next, BMenu *tar, BMessage *m, 1670 BMessage *r, BMessage *spec, int32 f) const 1671 { 1672 return B_ERROR; 1673 } 1674 1675 1676 status_t 1677 BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *tar, BMessage *m, 1678 BMessage *r, BMessage *spec, int32 f) const 1679 { 1680 return B_ERROR; 1681 } 1682 1683 1684 status_t 1685 BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1686 BMessage *r) const 1687 { 1688 return B_ERROR; 1689 } 1690 1691 1692 status_t 1693 BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1694 BMessage *r) const 1695 { 1696 return B_ERROR; 1697 } 1698 1699 1700 status_t 1701 BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1702 BMessage *r) const 1703 { 1704 return B_ERROR; 1705 } 1706 1707 1708 status_t 1709 BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1710 BMessage *r) const 1711 { 1712 return B_ERROR; 1713 } 1714 1715 1716 status_t 1717 BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m, 1718 BMessage *r, bool menu) const 1719 { 1720 return B_ERROR; 1721 } 1722 1723 1724 status_t 1725 set_menu_info(menu_info *info) 1726 { 1727 if (!info) 1728 return B_BAD_VALUE; 1729 1730 BPath path; 1731 1732 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 1733 return B_OK; 1734 1735 path.Append("menu_settings"); 1736 1737 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE); 1738 1739 if (file.InitCheck() != B_OK) 1740 return B_OK; 1741 1742 file.Write(info, sizeof(menu_info)); 1743 1744 BMenu::sMenuInfo = *info; 1745 1746 return B_OK; 1747 } 1748 1749 1750 status_t 1751 get_menu_info(menu_info *info) 1752 { 1753 if (!info) 1754 return B_BAD_VALUE; 1755 1756 *info = BMenu::sMenuInfo; 1757 1758 return B_OK; 1759 } 1760