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