1 /* 2 * Copyright 2006-2016 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Marc Flerackers, mflerackers@androme.be 8 * John Scipione, jscipione@gmail.com 9 * Ingo Weinhold, bonefish@cs.tu-berlin.de 10 */ 11 12 13 #include <MenuField.h> 14 15 #include <algorithm> 16 17 #include <stdio.h> 18 // for printf in TRACE 19 #include <stdlib.h> 20 #include <string.h> 21 22 #include <AbstractLayoutItem.h> 23 #include <Archivable.h> 24 #include <BMCPrivate.h> 25 #include <ControlLook.h> 26 #include <LayoutUtils.h> 27 #include <MenuBar.h> 28 #include <MenuItem.h> 29 #include <MenuItemPrivate.h> 30 #include <MenuPrivate.h> 31 #include <Message.h> 32 #include <MessageFilter.h> 33 #include <Window.h> 34 35 #include <binary_compatibility/Interface.h> 36 #include <binary_compatibility/Support.h> 37 38 39 #ifdef CALLED 40 # undef CALLED 41 #endif 42 #ifdef TRACE 43 # undef TRACE 44 #endif 45 46 //#define TRACE_MENU_FIELD 47 #ifdef TRACE_MENU_FIELD 48 # include <FunctionTracer.h> 49 static int32 sFunctionDepth = -1; 50 # define CALLED(x...) FunctionTracer _ft("BMenuField", __FUNCTION__, \ 51 sFunctionDepth) 52 # define TRACE(x...) { BString _to; \ 53 _to.Append(' ', (sFunctionDepth + 1) * 2); \ 54 printf("%s", _to.String()); printf(x); } 55 #else 56 # define CALLED(x...) 57 # define TRACE(x...) 58 #endif 59 60 61 static const float kMinMenuBarWidth = 20.0f; 62 // found by experimenting on BeOS R5 63 64 65 namespace { 66 const char* const kFrameField = "BMenuField:layoutItem:frame"; 67 const char* const kMenuBarItemField = "BMenuField:barItem"; 68 const char* const kLabelItemField = "BMenuField:labelItem"; 69 } 70 71 72 // #pragma mark - LabelLayoutItem 73 74 75 class BMenuField::LabelLayoutItem : public BAbstractLayoutItem { 76 public: 77 LabelLayoutItem(BMenuField* parent); 78 LabelLayoutItem(BMessage* archive); 79 80 BRect FrameInParent() const; 81 82 virtual bool IsVisible(); 83 virtual void SetVisible(bool visible); 84 85 virtual BRect Frame(); 86 virtual void SetFrame(BRect frame); 87 88 void SetParent(BMenuField* parent); 89 virtual BView* View(); 90 91 virtual BSize BaseMinSize(); 92 virtual BSize BaseMaxSize(); 93 virtual BSize BasePreferredSize(); 94 virtual BAlignment BaseAlignment(); 95 96 virtual status_t Archive(BMessage* into, bool deep = true) const; 97 static BArchivable* Instantiate(BMessage* from); 98 99 private: 100 BMenuField* fParent; 101 BRect fFrame; 102 }; 103 104 105 // #pragma mark - MenuBarLayoutItem 106 107 108 class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem { 109 public: 110 MenuBarLayoutItem(BMenuField* parent); 111 MenuBarLayoutItem(BMessage* from); 112 113 BRect FrameInParent() const; 114 115 virtual bool IsVisible(); 116 virtual void SetVisible(bool visible); 117 118 virtual BRect Frame(); 119 virtual void SetFrame(BRect frame); 120 121 void SetParent(BMenuField* parent); 122 virtual BView* View(); 123 124 virtual BSize BaseMinSize(); 125 virtual BSize BaseMaxSize(); 126 virtual BSize BasePreferredSize(); 127 virtual BAlignment BaseAlignment(); 128 129 virtual status_t Archive(BMessage* into, bool deep = true) const; 130 static BArchivable* Instantiate(BMessage* from); 131 132 private: 133 BMenuField* fParent; 134 BRect fFrame; 135 }; 136 137 138 // #pragma mark - LayoutData 139 140 141 struct BMenuField::LayoutData { 142 LayoutData() 143 : 144 label_layout_item(NULL), 145 menu_bar_layout_item(NULL), 146 previous_height(-1), 147 valid(false) 148 { 149 } 150 151 LabelLayoutItem* label_layout_item; 152 MenuBarLayoutItem* menu_bar_layout_item; 153 float previous_height; // used in FrameResized() for 154 // invalidation 155 font_height font_info; 156 float label_width; 157 float label_height; 158 BSize min; 159 BSize menu_bar_min; 160 bool valid; 161 }; 162 163 164 // #pragma mark - MouseDownFilter 165 166 167 class MouseDownFilter : public BMessageFilter 168 { 169 public: 170 MouseDownFilter(); 171 virtual ~MouseDownFilter(); 172 173 virtual filter_result Filter(BMessage* message, BHandler** target); 174 }; 175 176 177 MouseDownFilter::MouseDownFilter() 178 : 179 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE) 180 { 181 } 182 183 184 MouseDownFilter::~MouseDownFilter() 185 { 186 } 187 188 189 filter_result 190 MouseDownFilter::Filter(BMessage* message, BHandler** target) 191 { 192 return message->what == B_MOUSE_DOWN ? B_SKIP_MESSAGE : B_DISPATCH_MESSAGE; 193 } 194 195 196 // #pragma mark - BMenuField 197 198 199 BMenuField::BMenuField(BRect frame, const char* name, const char* label, 200 BMenu* menu, uint32 resizingMode, uint32 flags) 201 : 202 BView(frame, name, resizingMode, flags) 203 { 204 CALLED(); 205 206 TRACE("frame.width: %.2f, height: %.2f\n", frame.Width(), frame.Height()); 207 208 InitObject(label); 209 210 frame.OffsetTo(B_ORIGIN); 211 _InitMenuBar(menu, frame, false); 212 213 InitObject2(); 214 } 215 216 217 BMenuField::BMenuField(BRect frame, const char* name, const char* label, 218 BMenu* menu, bool fixedSize, uint32 resizingMode, uint32 flags) 219 : 220 BView(frame, name, resizingMode, flags) 221 { 222 InitObject(label); 223 224 fFixedSizeMB = fixedSize; 225 226 frame.OffsetTo(B_ORIGIN); 227 _InitMenuBar(menu, frame, fixedSize); 228 229 InitObject2(); 230 } 231 232 233 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu, 234 uint32 flags) 235 : 236 BView(name, flags | B_FRAME_EVENTS) 237 { 238 InitObject(label); 239 240 _InitMenuBar(menu, BRect(0, 0, 100, 15), true); 241 242 InitObject2(); 243 } 244 245 246 BMenuField::BMenuField(const char* label, BMenu* menu, uint32 flags) 247 : 248 BView(NULL, flags | B_FRAME_EVENTS) 249 { 250 InitObject(label); 251 252 _InitMenuBar(menu, BRect(0, 0, 100, 15), true); 253 254 InitObject2(); 255 } 256 257 258 //! Copy&Paste error, should be removed at some point (already private) 259 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu, 260 BMessage* message, uint32 flags) 261 : 262 BView(name, flags | B_FRAME_EVENTS) 263 { 264 InitObject(label); 265 266 _InitMenuBar(menu, BRect(0, 0, 100, 15), true); 267 268 InitObject2(); 269 } 270 271 272 //! Copy&Paste error, should be removed at some point (already private) 273 BMenuField::BMenuField(const char* label, BMenu* menu, BMessage* message) 274 : 275 BView(NULL, B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS) 276 { 277 InitObject(label); 278 279 _InitMenuBar(menu, BRect(0, 0, 100, 15), true); 280 281 InitObject2(); 282 } 283 284 285 BMenuField::BMenuField(BMessage* data) 286 : 287 BView(BUnarchiver::PrepareArchive(data)) 288 { 289 BUnarchiver unarchiver(data); 290 const char* label = NULL; 291 data->FindString("_label", &label); 292 293 InitObject(label); 294 295 data->FindFloat("_divide", &fDivider); 296 297 int32 align; 298 if (data->FindInt32("_align", &align) == B_OK) 299 SetAlignment((alignment)align); 300 301 if (!BUnarchiver::IsArchiveManaged(data)) 302 _InitMenuBar(data); 303 304 unarchiver.Finish(); 305 } 306 307 308 BMenuField::~BMenuField() 309 { 310 free(fLabel); 311 312 status_t dummy; 313 if (fMenuTaskID >= 0) 314 wait_for_thread(fMenuTaskID, &dummy); 315 316 delete fLayoutData; 317 delete fMouseDownFilter; 318 } 319 320 321 BArchivable* 322 BMenuField::Instantiate(BMessage* data) 323 { 324 if (validate_instantiation(data, "BMenuField")) 325 return new BMenuField(data); 326 327 return NULL; 328 } 329 330 331 status_t 332 BMenuField::Archive(BMessage* data, bool deep) const 333 { 334 BArchiver archiver(data); 335 status_t ret = BView::Archive(data, deep); 336 337 if (ret == B_OK && Label()) 338 ret = data->AddString("_label", Label()); 339 340 if (ret == B_OK && !IsEnabled()) 341 ret = data->AddBool("_disable", true); 342 343 if (ret == B_OK) 344 ret = data->AddInt32("_align", Alignment()); 345 if (ret == B_OK) 346 ret = data->AddFloat("_divide", Divider()); 347 348 if (ret == B_OK && fFixedSizeMB) 349 ret = data->AddBool("be:fixeds", true); 350 351 bool dmark = false; 352 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) 353 dmark = menuBar->IsPopUpMarkerShown(); 354 355 data->AddBool("be:dmark", dmark); 356 357 return archiver.Finish(ret); 358 } 359 360 361 status_t 362 BMenuField::AllArchived(BMessage* into) const 363 { 364 status_t err; 365 if ((err = BView::AllArchived(into)) != B_OK) 366 return err; 367 368 BArchiver archiver(into); 369 370 BArchivable* menuBarItem = fLayoutData->menu_bar_layout_item; 371 if (archiver.IsArchived(menuBarItem)) 372 err = archiver.AddArchivable(kMenuBarItemField, menuBarItem); 373 374 if (err != B_OK) 375 return err; 376 377 BArchivable* labelBarItem = fLayoutData->label_layout_item; 378 if (archiver.IsArchived(labelBarItem)) 379 err = archiver.AddArchivable(kLabelItemField, labelBarItem); 380 381 return err; 382 } 383 384 385 status_t 386 BMenuField::AllUnarchived(const BMessage* from) 387 { 388 BUnarchiver unarchiver(from); 389 390 status_t err = B_OK; 391 if ((err = BView::AllUnarchived(from)) != B_OK) 392 return err; 393 394 _InitMenuBar(from); 395 396 if (unarchiver.IsInstantiated(kMenuBarItemField)) { 397 MenuBarLayoutItem*& menuItem = fLayoutData->menu_bar_layout_item; 398 err = unarchiver.FindObject(kMenuBarItemField, 399 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, menuItem); 400 401 if (err == B_OK) 402 menuItem->SetParent(this); 403 else 404 return err; 405 } 406 407 if (unarchiver.IsInstantiated(kLabelItemField)) { 408 LabelLayoutItem*& labelItem = fLayoutData->label_layout_item; 409 err = unarchiver.FindObject(kLabelItemField, 410 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem); 411 412 if (err == B_OK) 413 labelItem->SetParent(this); 414 } 415 416 return err; 417 } 418 419 420 void 421 BMenuField::Draw(BRect updateRect) 422 { 423 _DrawLabel(updateRect); 424 _DrawMenuBar(updateRect); 425 } 426 427 428 void 429 BMenuField::AttachedToWindow() 430 { 431 CALLED(); 432 433 // Our low color must match the parent's view color. 434 if (Parent() != NULL) { 435 AdoptParentColors(); 436 437 float tint = B_NO_TINT; 438 color_which which = ViewUIColor(&tint); 439 440 if (which == B_NO_COLOR) 441 SetLowColor(ViewColor()); 442 else 443 SetLowUIColor(which, tint); 444 } else 445 AdoptSystemColors(); 446 } 447 448 449 void 450 BMenuField::AllAttached() 451 { 452 CALLED(); 453 454 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 455 456 float width = Bounds().Width(); 457 if (!fFixedSizeMB && _MenuBarWidth() < kMinMenuBarWidth) { 458 // The menu bar is too narrow, resize it to fit the menu items 459 BMenuItem* item = fMenuBar->ItemAt(0); 460 if (item != NULL) { 461 float right; 462 fMenuBar->GetItemMargins(NULL, NULL, &right, NULL); 463 width = item->Frame().Width() + kVMargin + _MenuBarOffset() + right; 464 } 465 } 466 467 ResizeTo(width, fMenuBar->Bounds().Height() + kVMargin * 2); 468 469 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 470 } 471 472 473 void 474 BMenuField::MouseDown(BPoint where) 475 { 476 BRect bounds = fMenuBar->ConvertFromParent(Bounds()); 477 478 fMenuBar->StartMenuBar(-1, false, true, &bounds); 479 480 fMenuTaskID = spawn_thread((thread_func)_thread_entry, 481 "_m_task_", B_NORMAL_PRIORITY, this); 482 if (fMenuTaskID >= 0 && resume_thread(fMenuTaskID) == B_OK) { 483 if (fMouseDownFilter->Looper() == NULL) 484 Window()->AddCommonFilter(fMouseDownFilter); 485 486 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 487 } 488 } 489 490 491 void 492 BMenuField::KeyDown(const char* bytes, int32 numBytes) 493 { 494 switch (bytes[0]) { 495 case B_SPACE: 496 case B_RIGHT_ARROW: 497 case B_DOWN_ARROW: 498 { 499 if (!IsEnabled()) 500 break; 501 502 BRect bounds = fMenuBar->ConvertFromParent(Bounds()); 503 504 fMenuBar->StartMenuBar(0, true, true, &bounds); 505 506 bounds = Bounds(); 507 bounds.right = fDivider; 508 509 Invalidate(bounds); 510 } 511 512 default: 513 BView::KeyDown(bytes, numBytes); 514 } 515 } 516 517 518 void 519 BMenuField::MakeFocus(bool focused) 520 { 521 if (IsFocus() == focused) 522 return; 523 524 BView::MakeFocus(focused); 525 526 if (Window() != NULL) 527 Invalidate(); // TODO: use fLayoutData->label_width 528 } 529 530 531 void 532 BMenuField::MessageReceived(BMessage* message) 533 { 534 BView::MessageReceived(message); 535 } 536 537 538 void 539 BMenuField::WindowActivated(bool active) 540 { 541 BView::WindowActivated(active); 542 543 if (IsFocus()) 544 Invalidate(); 545 } 546 547 548 void 549 BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage* message) 550 { 551 BView::MouseMoved(point, code, message); 552 } 553 554 555 void 556 BMenuField::MouseUp(BPoint where) 557 { 558 Window()->RemoveCommonFilter(fMouseDownFilter); 559 BView::MouseUp(where); 560 } 561 562 563 void 564 BMenuField::DetachedFromWindow() 565 { 566 BView::DetachedFromWindow(); 567 } 568 569 570 void 571 BMenuField::AllDetached() 572 { 573 BView::AllDetached(); 574 } 575 576 577 void 578 BMenuField::FrameMoved(BPoint newPosition) 579 { 580 BView::FrameMoved(newPosition); 581 } 582 583 584 void 585 BMenuField::FrameResized(float newWidth, float newHeight) 586 { 587 BView::FrameResized(newWidth, newHeight); 588 589 if (fFixedSizeMB) { 590 // we have let the menubar resize itself, but 591 // in fixed size mode, the menubar is supposed to 592 // be at the right end of the view always. Since 593 // the menu bar is in follow left/right mode then, 594 // resizing ourselfs might have caused the menubar 595 // to be outside now 596 fMenuBar->ResizeTo(_MenuBarWidth(), fMenuBar->Frame().Height()); 597 } 598 599 if (newHeight != fLayoutData->previous_height && Label()) { 600 // The height changed, which means the label has to move and we 601 // probably also invalidate a part of the borders around the menu bar. 602 // So don't be shy and invalidate the whole thing. 603 Invalidate(); 604 } 605 606 fLayoutData->previous_height = newHeight; 607 } 608 609 610 BMenu* 611 BMenuField::Menu() const 612 { 613 return fMenu; 614 } 615 616 617 BMenuBar* 618 BMenuField::MenuBar() const 619 { 620 return fMenuBar; 621 } 622 623 624 BMenuItem* 625 BMenuField::MenuItem() const 626 { 627 return fMenuBar->ItemAt(0); 628 } 629 630 631 void 632 BMenuField::SetLabel(const char* label) 633 { 634 if (fLabel) { 635 if (label && strcmp(fLabel, label) == 0) 636 return; 637 638 free(fLabel); 639 } 640 641 fLabel = strdup(label); 642 643 if (Window()) 644 Invalidate(); 645 646 InvalidateLayout(); 647 } 648 649 650 const char* 651 BMenuField::Label() const 652 { 653 return fLabel; 654 } 655 656 657 void 658 BMenuField::SetEnabled(bool on) 659 { 660 if (fEnabled == on) 661 return; 662 663 fEnabled = on; 664 fMenuBar->SetEnabled(on); 665 666 if (Window()) { 667 fMenuBar->Invalidate(fMenuBar->Bounds()); 668 Invalidate(Bounds()); 669 } 670 } 671 672 673 bool 674 BMenuField::IsEnabled() const 675 { 676 return fEnabled; 677 } 678 679 680 void 681 BMenuField::SetAlignment(alignment label) 682 { 683 fAlign = label; 684 } 685 686 687 alignment 688 BMenuField::Alignment() const 689 { 690 return fAlign; 691 } 692 693 694 void 695 BMenuField::SetDivider(float position) 696 { 697 position = roundf(position); 698 699 float delta = fDivider - position; 700 if (delta == 0.0f) 701 return; 702 703 fDivider = position; 704 705 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) { 706 // We should never get here, since layout support means, we also 707 // layout the divider, and don't use this method at all. 708 Relayout(); 709 } else { 710 BRect dirty(fMenuBar->Frame()); 711 712 fMenuBar->MoveTo(_MenuBarOffset(), kVMargin); 713 714 if (fFixedSizeMB) 715 fMenuBar->ResizeTo(_MenuBarWidth(), dirty.Height()); 716 717 dirty = dirty | fMenuBar->Frame(); 718 dirty.InsetBy(-kVMargin, -kVMargin); 719 720 Invalidate(dirty); 721 } 722 } 723 724 725 float 726 BMenuField::Divider() const 727 { 728 return fDivider; 729 } 730 731 732 void 733 BMenuField::ShowPopUpMarker() 734 { 735 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) { 736 menuBar->TogglePopUpMarker(true); 737 menuBar->Invalidate(); 738 } 739 } 740 741 742 void 743 BMenuField::HidePopUpMarker() 744 { 745 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) { 746 menuBar->TogglePopUpMarker(false); 747 menuBar->Invalidate(); 748 } 749 } 750 751 752 BHandler* 753 BMenuField::ResolveSpecifier(BMessage* message, int32 index, 754 BMessage* specifier, int32 form, const char* property) 755 { 756 return BView::ResolveSpecifier(message, index, specifier, form, property); 757 } 758 759 760 status_t 761 BMenuField::GetSupportedSuites(BMessage* data) 762 { 763 return BView::GetSupportedSuites(data); 764 } 765 766 767 void 768 BMenuField::ResizeToPreferred() 769 { 770 CALLED(); 771 772 TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n", 773 fMenuBar->Frame().Width(), fMenuBar->Frame().Height()); 774 775 fMenuBar->ResizeToPreferred(); 776 777 TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n", 778 fMenuBar->Frame().Width(), fMenuBar->Frame().Height()); 779 780 BView::ResizeToPreferred(); 781 782 Invalidate(); 783 } 784 785 786 void 787 BMenuField::GetPreferredSize(float* _width, float* _height) 788 { 789 CALLED(); 790 791 _ValidateLayoutData(); 792 793 if (_width) 794 *_width = fLayoutData->min.width; 795 796 if (_height) 797 *_height = fLayoutData->min.height; 798 } 799 800 801 BSize 802 BMenuField::MinSize() 803 { 804 CALLED(); 805 806 _ValidateLayoutData(); 807 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 808 } 809 810 811 BSize 812 BMenuField::MaxSize() 813 { 814 CALLED(); 815 816 _ValidateLayoutData(); 817 818 BSize max = fLayoutData->min; 819 max.width = B_SIZE_UNLIMITED; 820 821 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max); 822 } 823 824 825 BSize 826 BMenuField::PreferredSize() 827 { 828 CALLED(); 829 830 _ValidateLayoutData(); 831 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min); 832 } 833 834 835 BLayoutItem* 836 BMenuField::CreateLabelLayoutItem() 837 { 838 if (fLayoutData->label_layout_item == NULL) 839 fLayoutData->label_layout_item = new LabelLayoutItem(this); 840 841 return fLayoutData->label_layout_item; 842 } 843 844 845 BLayoutItem* 846 BMenuField::CreateMenuBarLayoutItem() 847 { 848 if (fLayoutData->menu_bar_layout_item == NULL) { 849 // align the menu bar in the full available space 850 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 851 B_ALIGN_VERTICAL_UNSET)); 852 fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this); 853 } 854 855 return fLayoutData->menu_bar_layout_item; 856 } 857 858 859 status_t 860 BMenuField::Perform(perform_code code, void* _data) 861 { 862 switch (code) { 863 case PERFORM_CODE_MIN_SIZE: 864 ((perform_data_min_size*)_data)->return_value 865 = BMenuField::MinSize(); 866 return B_OK; 867 868 case PERFORM_CODE_MAX_SIZE: 869 ((perform_data_max_size*)_data)->return_value 870 = BMenuField::MaxSize(); 871 return B_OK; 872 873 case PERFORM_CODE_PREFERRED_SIZE: 874 ((perform_data_preferred_size*)_data)->return_value 875 = BMenuField::PreferredSize(); 876 return B_OK; 877 878 case PERFORM_CODE_LAYOUT_ALIGNMENT: 879 ((perform_data_layout_alignment*)_data)->return_value 880 = BMenuField::LayoutAlignment(); 881 return B_OK; 882 883 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 884 ((perform_data_has_height_for_width*)_data)->return_value 885 = BMenuField::HasHeightForWidth(); 886 return B_OK; 887 888 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 889 { 890 perform_data_get_height_for_width* data 891 = (perform_data_get_height_for_width*)_data; 892 BMenuField::GetHeightForWidth(data->width, &data->min, &data->max, 893 &data->preferred); 894 return B_OK; 895 } 896 897 case PERFORM_CODE_SET_LAYOUT: 898 { 899 perform_data_set_layout* data = (perform_data_set_layout*)_data; 900 BMenuField::SetLayout(data->layout); 901 return B_OK; 902 } 903 904 case PERFORM_CODE_LAYOUT_INVALIDATED: 905 { 906 perform_data_layout_invalidated* data 907 = (perform_data_layout_invalidated*)_data; 908 BMenuField::LayoutInvalidated(data->descendants); 909 return B_OK; 910 } 911 912 case PERFORM_CODE_DO_LAYOUT: 913 { 914 BMenuField::DoLayout(); 915 return B_OK; 916 } 917 918 case PERFORM_CODE_ALL_UNARCHIVED: 919 { 920 perform_data_all_unarchived* data 921 = (perform_data_all_unarchived*)_data; 922 data->return_value = BMenuField::AllUnarchived(data->archive); 923 return B_OK; 924 } 925 926 case PERFORM_CODE_ALL_ARCHIVED: 927 { 928 perform_data_all_archived* data 929 = (perform_data_all_archived*)_data; 930 data->return_value = BMenuField::AllArchived(data->archive); 931 return B_OK; 932 } 933 } 934 935 return BView::Perform(code, _data); 936 } 937 938 939 void 940 BMenuField::LayoutInvalidated(bool descendants) 941 { 942 CALLED(); 943 944 fLayoutData->valid = false; 945 } 946 947 948 void 949 BMenuField::DoLayout() 950 { 951 // Bail out, if we shan't do layout. 952 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) 953 return; 954 955 CALLED(); 956 957 // If the user set a layout, we let the base class version call its 958 // hook. 959 if (GetLayout() != NULL) { 960 BView::DoLayout(); 961 return; 962 } 963 964 _ValidateLayoutData(); 965 966 // validate current size 967 BSize size(Bounds().Size()); 968 if (size.width < fLayoutData->min.width) 969 size.width = fLayoutData->min.width; 970 971 if (size.height < fLayoutData->min.height) 972 size.height = fLayoutData->min.height; 973 974 // divider 975 float divider = 0; 976 if (fLayoutData->label_layout_item != NULL 977 && fLayoutData->menu_bar_layout_item != NULL 978 && fLayoutData->label_layout_item->Frame().IsValid() 979 && fLayoutData->menu_bar_layout_item->Frame().IsValid()) { 980 // We have valid layout items, they define the divider location. 981 divider = fabs(fLayoutData->menu_bar_layout_item->Frame().left 982 - fLayoutData->label_layout_item->Frame().left); 983 } else if (fLayoutData->label_width > 0) { 984 divider = fLayoutData->label_width 985 + be_control_look->DefaultLabelSpacing(); 986 } 987 988 // menu bar 989 BRect dirty(fMenuBar->Frame()); 990 BRect menuBarFrame(divider + kVMargin, kVMargin, size.width - kVMargin, 991 size.height - kVMargin); 992 993 // place the menu bar and set the divider 994 BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame); 995 996 fDivider = divider; 997 998 // invalidate dirty region 999 dirty = dirty | fMenuBar->Frame(); 1000 dirty.InsetBy(-kVMargin, -kVMargin); 1001 1002 Invalidate(dirty); 1003 } 1004 1005 1006 void BMenuField::_ReservedMenuField1() {} 1007 void BMenuField::_ReservedMenuField2() {} 1008 void BMenuField::_ReservedMenuField3() {} 1009 1010 1011 void 1012 BMenuField::InitObject(const char* label) 1013 { 1014 CALLED(); 1015 1016 fLabel = NULL; 1017 fMenu = NULL; 1018 fMenuBar = NULL; 1019 fAlign = B_ALIGN_LEFT; 1020 fEnabled = true; 1021 fFixedSizeMB = false; 1022 fMenuTaskID = -1; 1023 fLayoutData = new LayoutData; 1024 fMouseDownFilter = new MouseDownFilter(); 1025 1026 SetLabel(label); 1027 1028 if (label) 1029 fDivider = floorf(Frame().Width() / 2.0f); 1030 else 1031 fDivider = 0; 1032 } 1033 1034 1035 void 1036 BMenuField::InitObject2() 1037 { 1038 CALLED(); 1039 1040 if (!fFixedSizeMB) { 1041 float height; 1042 fMenuBar->GetPreferredSize(NULL, &height); 1043 fMenuBar->ResizeTo(_MenuBarWidth(), height); 1044 } 1045 1046 TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n", 1047 fMenuBar->Frame().left, fMenuBar->Frame().top, 1048 fMenuBar->Frame().right, fMenuBar->Frame().bottom, 1049 fMenuBar->Frame().Width(), fMenuBar->Frame().Height()); 1050 1051 fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN)); 1052 } 1053 1054 1055 void 1056 BMenuField::_DrawLabel(BRect updateRect) 1057 { 1058 CALLED(); 1059 1060 _ValidateLayoutData(); 1061 1062 const char* label = Label(); 1063 if (label == NULL) 1064 return; 1065 1066 BRect rect; 1067 if (fLayoutData->label_layout_item != NULL) 1068 rect = fLayoutData->label_layout_item->FrameInParent(); 1069 else { 1070 rect = Bounds(); 1071 rect.right = fDivider; 1072 } 1073 1074 if (!rect.IsValid() || !rect.Intersects(updateRect)) 1075 return; 1076 1077 uint32 flags = 0; 1078 if (!IsEnabled()) 1079 flags |= BControlLook::B_DISABLED; 1080 1081 // save the current low color 1082 PushState(); 1083 rgb_color textColor; 1084 1085 BPrivate::MenuPrivate menuPrivate(fMenuBar); 1086 if (menuPrivate.State() != MENU_STATE_CLOSED) { 1087 // highlight the background of the label grey (like BeOS R5) 1088 SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR)); 1089 BRect fillRect(rect.InsetByCopy(0, kVMargin)); 1090 FillRect(fillRect, B_SOLID_LOW); 1091 textColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR); 1092 } else 1093 textColor = ui_color(B_PANEL_TEXT_COLOR); 1094 1095 be_control_look->DrawLabel(this, label, rect, updateRect, LowColor(), flags, 1096 BAlignment(fAlign, B_ALIGN_MIDDLE), &textColor); 1097 1098 // restore the previous low color 1099 PopState(); 1100 } 1101 1102 1103 void 1104 BMenuField::_DrawMenuBar(BRect updateRect) 1105 { 1106 CALLED(); 1107 1108 BRect rect(fMenuBar->Frame().InsetByCopy(-kVMargin, -kVMargin)); 1109 if (!rect.IsValid() || !rect.Intersects(updateRect)) 1110 return; 1111 1112 uint32 flags = 0; 1113 if (!IsEnabled()) 1114 flags |= BControlLook::B_DISABLED; 1115 1116 if (IsFocus() && Window()->IsActive()) 1117 flags |= BControlLook::B_FOCUSED; 1118 1119 be_control_look->DrawMenuFieldFrame(this, rect, updateRect, 1120 fMenuBar->LowColor(), LowColor(), flags); 1121 } 1122 1123 1124 void 1125 BMenuField::InitMenu(BMenu* menu) 1126 { 1127 menu->SetFont(be_plain_font); 1128 1129 int32 index = 0; 1130 BMenu* subMenu; 1131 1132 while ((subMenu = menu->SubmenuAt(index++)) != NULL) 1133 InitMenu(subMenu); 1134 } 1135 1136 1137 /*static*/ int32 1138 BMenuField::_thread_entry(void* arg) 1139 { 1140 return static_cast<BMenuField*>(arg)->_MenuTask(); 1141 } 1142 1143 1144 int32 1145 BMenuField::_MenuTask() 1146 { 1147 if (!LockLooper()) 1148 return 0; 1149 1150 Invalidate(); 1151 UnlockLooper(); 1152 1153 bool tracking; 1154 do { 1155 snooze(20000); 1156 if (!LockLooper()) 1157 return 0; 1158 1159 tracking = fMenuBar->fTracking; 1160 1161 UnlockLooper(); 1162 } while (tracking); 1163 1164 if (LockLooper()) { 1165 Invalidate(); 1166 UnlockLooper(); 1167 } 1168 1169 return 0; 1170 } 1171 1172 1173 void 1174 BMenuField::_UpdateFrame() 1175 { 1176 CALLED(); 1177 1178 if (fLayoutData->label_layout_item == NULL 1179 || fLayoutData->menu_bar_layout_item == NULL) { 1180 return; 1181 } 1182 1183 BRect labelFrame = fLayoutData->label_layout_item->Frame(); 1184 BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame(); 1185 1186 if (!labelFrame.IsValid() || !menuFrame.IsValid()) 1187 return; 1188 1189 // update divider 1190 fDivider = menuFrame.left - labelFrame.left; 1191 1192 // update our frame 1193 MoveTo(labelFrame.left, labelFrame.top); 1194 BSize oldSize = Bounds().Size(); 1195 ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left, 1196 menuFrame.top + menuFrame.Height() - labelFrame.top); 1197 BSize newSize = Bounds().Size(); 1198 1199 // If the size changes, ResizeTo() will trigger a relayout, otherwise 1200 // we need to do that explicitly. 1201 if (newSize != oldSize) 1202 Relayout(); 1203 } 1204 1205 1206 void 1207 BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize) 1208 { 1209 CALLED(); 1210 1211 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) { 1212 fMenuBar = new _BMCMenuBar_(this); 1213 } else { 1214 frame.left = _MenuBarOffset(); 1215 frame.top = kVMargin; 1216 frame.right -= kVMargin; 1217 frame.bottom -= kVMargin; 1218 1219 TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n", 1220 frame.left, frame.top, frame.right, frame.bottom, 1221 frame.Width(), frame.Height()); 1222 1223 fMenuBar = new _BMCMenuBar_(frame, fixedSize, this); 1224 } 1225 1226 if (fixedSize) { 1227 // align the menu bar in the full available space 1228 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 1229 B_ALIGN_VERTICAL_UNSET)); 1230 } else { 1231 // align the menu bar left in the available space 1232 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 1233 B_ALIGN_VERTICAL_UNSET)); 1234 } 1235 1236 AddChild(fMenuBar); 1237 1238 _AddMenu(menu); 1239 1240 fMenuBar->SetFont(be_plain_font); 1241 } 1242 1243 1244 void 1245 BMenuField::_InitMenuBar(const BMessage* archive) 1246 { 1247 bool fixed; 1248 if (archive->FindBool("be:fixeds", &fixed) == B_OK) 1249 fFixedSizeMB = fixed; 1250 1251 fMenuBar = (BMenuBar*)FindView("_mc_mb_"); 1252 if (fMenuBar == NULL) { 1253 _InitMenuBar(new BMenu(""), BRect(0, 0, 100, 15), fFixedSizeMB); 1254 InitObject2(); 1255 } else { 1256 fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN)); 1257 // this is normally done in InitObject2() 1258 } 1259 1260 _AddMenu(fMenuBar->SubmenuAt(0)); 1261 1262 bool disable; 1263 if (archive->FindBool("_disable", &disable) == B_OK) 1264 SetEnabled(!disable); 1265 1266 bool dmark = false; 1267 archive->FindBool("be:dmark", &dmark); 1268 _BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar); 1269 if (menuBar != NULL) 1270 menuBar->TogglePopUpMarker(dmark); 1271 } 1272 1273 1274 void 1275 BMenuField::_AddMenu(BMenu* menu) 1276 { 1277 if (menu == NULL || fMenuBar == NULL) 1278 return; 1279 1280 fMenu = menu; 1281 InitMenu(menu); 1282 1283 BMenuItem* item = NULL; 1284 if (!menu->IsRadioMode() || (item = menu->FindMarked()) == NULL) { 1285 // find the first enabled non-seperator item 1286 int32 itemCount = menu->CountItems(); 1287 for (int32 i = 0; i < itemCount; i++) { 1288 item = menu->ItemAt((int32)i); 1289 if (item == NULL || !item->IsEnabled() 1290 || dynamic_cast<BSeparatorItem*>(item) != NULL) { 1291 item = NULL; 1292 continue; 1293 } 1294 break; 1295 } 1296 } 1297 1298 if (item == NULL) { 1299 fMenuBar->AddItem(menu); 1300 return; 1301 } 1302 1303 // build an empty copy of item 1304 1305 BMessage data; 1306 status_t result = item->Archive(&data, false); 1307 if (result != B_OK) { 1308 fMenuBar->AddItem(menu); 1309 return; 1310 } 1311 1312 BArchivable* object = instantiate_object(&data); 1313 if (object == NULL) { 1314 fMenuBar->AddItem(menu); 1315 return; 1316 } 1317 1318 BMenuItem* newItem = static_cast<BMenuItem*>(object); 1319 1320 // unset parameters 1321 BPrivate::MenuItemPrivate newMenuItemPrivate(newItem); 1322 newMenuItemPrivate.Uninstall(); 1323 1324 // set the menu 1325 newMenuItemPrivate.SetSubmenu(menu); 1326 fMenuBar->AddItem(newItem); 1327 } 1328 1329 1330 void 1331 BMenuField::_ValidateLayoutData() 1332 { 1333 CALLED(); 1334 1335 if (fLayoutData->valid) 1336 return; 1337 1338 // cache font height 1339 font_height& fh = fLayoutData->font_info; 1340 GetFontHeight(&fh); 1341 1342 const char* label = Label(); 1343 if (label != NULL) { 1344 fLayoutData->label_width = ceilf(StringWidth(label)); 1345 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 1346 } else { 1347 fLayoutData->label_width = 0; 1348 fLayoutData->label_height = 0; 1349 } 1350 1351 // compute the minimal divider 1352 float divider = 0; 1353 if (fLayoutData->label_width > 0) { 1354 divider = fLayoutData->label_width 1355 + be_control_look->DefaultLabelSpacing(); 1356 } 1357 1358 // If we shan't do real layout, we let the current divider take influence. 1359 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) 1360 divider = std::max(divider, fDivider); 1361 1362 // get the minimal (== preferred) menu bar size 1363 // TODO: BMenu::MinSize() is using the ResizeMode() to decide the 1364 // minimum width. If the mode is B_FOLLOW_LEFT_RIGHT, it will use the 1365 // parent's frame width or window's frame width. So at least the returned 1366 // size is wrong, but apparantly it doesn't have much bad effect. 1367 fLayoutData->menu_bar_min = fMenuBar->MinSize(); 1368 1369 TRACE("menu bar min width: %.2f\n", fLayoutData->menu_bar_min.width); 1370 1371 // compute our minimal (== preferred) size 1372 BSize min(fLayoutData->menu_bar_min); 1373 min.width += 2 * kVMargin; 1374 min.height += 2 * kVMargin; 1375 1376 if (divider > 0) 1377 min.width += divider; 1378 1379 if (fLayoutData->label_height > min.height) 1380 min.height = fLayoutData->label_height; 1381 1382 fLayoutData->min = min; 1383 1384 fLayoutData->valid = true; 1385 ResetLayoutInvalidation(); 1386 1387 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 1388 } 1389 1390 1391 float 1392 BMenuField::_MenuBarOffset() const 1393 { 1394 return std::max(fDivider + kVMargin, kVMargin); 1395 } 1396 1397 1398 float 1399 BMenuField::_MenuBarWidth() const 1400 { 1401 return Bounds().Width() - (_MenuBarOffset() + kVMargin); 1402 } 1403 1404 1405 // #pragma mark - BMenuField::LabelLayoutItem 1406 1407 1408 BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent) 1409 : 1410 fParent(parent), 1411 fFrame() 1412 { 1413 } 1414 1415 1416 BMenuField::LabelLayoutItem::LabelLayoutItem(BMessage* from) 1417 : 1418 BAbstractLayoutItem(from), 1419 fParent(NULL), 1420 fFrame() 1421 { 1422 from->FindRect(kFrameField, &fFrame); 1423 } 1424 1425 1426 BRect 1427 BMenuField::LabelLayoutItem::FrameInParent() const 1428 { 1429 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1430 } 1431 1432 1433 bool 1434 BMenuField::LabelLayoutItem::IsVisible() 1435 { 1436 return !fParent->IsHidden(fParent); 1437 } 1438 1439 1440 void 1441 BMenuField::LabelLayoutItem::SetVisible(bool visible) 1442 { 1443 // not allowed 1444 } 1445 1446 1447 BRect 1448 BMenuField::LabelLayoutItem::Frame() 1449 { 1450 return fFrame; 1451 } 1452 1453 1454 void 1455 BMenuField::LabelLayoutItem::SetFrame(BRect frame) 1456 { 1457 fFrame = frame; 1458 fParent->_UpdateFrame(); 1459 } 1460 1461 1462 void 1463 BMenuField::LabelLayoutItem::SetParent(BMenuField* parent) 1464 { 1465 fParent = parent; 1466 } 1467 1468 1469 BView* 1470 BMenuField::LabelLayoutItem::View() 1471 { 1472 return fParent; 1473 } 1474 1475 1476 BSize 1477 BMenuField::LabelLayoutItem::BaseMinSize() 1478 { 1479 fParent->_ValidateLayoutData(); 1480 1481 if (fParent->Label() == NULL) 1482 return BSize(-1, -1); 1483 1484 return BSize(fParent->fLayoutData->label_width 1485 + be_control_look->DefaultLabelSpacing(), 1486 fParent->fLayoutData->label_height); 1487 } 1488 1489 1490 BSize 1491 BMenuField::LabelLayoutItem::BaseMaxSize() 1492 { 1493 return BaseMinSize(); 1494 } 1495 1496 1497 BSize 1498 BMenuField::LabelLayoutItem::BasePreferredSize() 1499 { 1500 return BaseMinSize(); 1501 } 1502 1503 1504 BAlignment 1505 BMenuField::LabelLayoutItem::BaseAlignment() 1506 { 1507 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1508 } 1509 1510 1511 status_t 1512 BMenuField::LabelLayoutItem::Archive(BMessage* into, bool deep) const 1513 { 1514 BArchiver archiver(into); 1515 status_t err = BAbstractLayoutItem::Archive(into, deep); 1516 1517 if (err == B_OK) 1518 err = into->AddRect(kFrameField, fFrame); 1519 1520 return archiver.Finish(err); 1521 } 1522 1523 1524 BArchivable* 1525 BMenuField::LabelLayoutItem::Instantiate(BMessage* from) 1526 { 1527 if (validate_instantiation(from, "BMenuField::LabelLayoutItem")) 1528 return new LabelLayoutItem(from); 1529 1530 return NULL; 1531 } 1532 1533 1534 // #pragma mark - BMenuField::MenuBarLayoutItem 1535 1536 1537 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* parent) 1538 : 1539 fParent(parent), 1540 fFrame() 1541 { 1542 // by default the part right of the divider shall have an unlimited maximum 1543 // width 1544 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1545 } 1546 1547 1548 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMessage* from) 1549 : 1550 BAbstractLayoutItem(from), 1551 fParent(NULL), 1552 fFrame() 1553 { 1554 from->FindRect(kFrameField, &fFrame); 1555 } 1556 1557 1558 BRect 1559 BMenuField::MenuBarLayoutItem::FrameInParent() const 1560 { 1561 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1562 } 1563 1564 1565 bool 1566 BMenuField::MenuBarLayoutItem::IsVisible() 1567 { 1568 return !fParent->IsHidden(fParent); 1569 } 1570 1571 1572 void 1573 BMenuField::MenuBarLayoutItem::SetVisible(bool visible) 1574 { 1575 // not allowed 1576 } 1577 1578 1579 BRect 1580 BMenuField::MenuBarLayoutItem::Frame() 1581 { 1582 return fFrame; 1583 } 1584 1585 1586 void 1587 BMenuField::MenuBarLayoutItem::SetFrame(BRect frame) 1588 { 1589 fFrame = frame; 1590 fParent->_UpdateFrame(); 1591 } 1592 1593 1594 void 1595 BMenuField::MenuBarLayoutItem::SetParent(BMenuField* parent) 1596 { 1597 fParent = parent; 1598 } 1599 1600 1601 BView* 1602 BMenuField::MenuBarLayoutItem::View() 1603 { 1604 return fParent; 1605 } 1606 1607 1608 BSize 1609 BMenuField::MenuBarLayoutItem::BaseMinSize() 1610 { 1611 fParent->_ValidateLayoutData(); 1612 1613 BSize size = fParent->fLayoutData->menu_bar_min; 1614 size.width += 2 * kVMargin; 1615 size.height += 2 * kVMargin; 1616 1617 return size; 1618 } 1619 1620 1621 BSize 1622 BMenuField::MenuBarLayoutItem::BaseMaxSize() 1623 { 1624 BSize size(BaseMinSize()); 1625 size.width = B_SIZE_UNLIMITED; 1626 1627 return size; 1628 } 1629 1630 1631 BSize 1632 BMenuField::MenuBarLayoutItem::BasePreferredSize() 1633 { 1634 return BaseMinSize(); 1635 } 1636 1637 1638 BAlignment 1639 BMenuField::MenuBarLayoutItem::BaseAlignment() 1640 { 1641 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1642 } 1643 1644 1645 status_t 1646 BMenuField::MenuBarLayoutItem::Archive(BMessage* into, bool deep) const 1647 { 1648 BArchiver archiver(into); 1649 status_t err = BAbstractLayoutItem::Archive(into, deep); 1650 1651 if (err == B_OK) 1652 err = into->AddRect(kFrameField, fFrame); 1653 1654 return archiver.Finish(err); 1655 } 1656 1657 1658 BArchivable* 1659 BMenuField::MenuBarLayoutItem::Instantiate(BMessage* from) 1660 { 1661 if (validate_instantiation(from, "BMenuField::MenuBarLayoutItem")) 1662 return new MenuBarLayoutItem(from); 1663 return NULL; 1664 } 1665 1666 1667 extern "C" void 1668 B_IF_GCC_2(InvalidateLayout__10BMenuFieldb, _ZN10BMenuField16InvalidateLayoutEb)( 1669 BMenuField* field, bool descendants) 1670 { 1671 perform_data_layout_invalidated data; 1672 data.descendants = descendants; 1673 1674 field->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 1675 } 1676