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