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 61static const float kMinMenuBarWidth = 20.0f; 62 // found by experimenting on BeOS R5 63 64 65namespace { 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 75class BMenuField::LabelLayoutItem : public BAbstractLayoutItem { 76public: 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 99private: 100 BMenuField* fParent; 101 BRect fFrame; 102}; 103 104 105// #pragma mark - MenuBarLayoutItem 106 107 108class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem { 109public: 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 132private: 133 BMenuField* fParent; 134 BRect fFrame; 135}; 136 137 138// #pragma mark - LayoutData 139 140 141struct 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 166namespace { 167 168class MouseDownFilter : public BMessageFilter 169{ 170public: 171 MouseDownFilter(); 172 virtual ~MouseDownFilter(); 173 174 virtual filter_result Filter(BMessage* message, BHandler** target); 175}; 176 177 178MouseDownFilter::MouseDownFilter() 179 : 180 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE) 181{ 182} 183 184 185MouseDownFilter::~MouseDownFilter() 186{ 187} 188 189 190filter_result 191MouseDownFilter::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 203BMenuField::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 221BMenuField::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 237BMenuField::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 250BMenuField::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 265BMenuField::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 277BMenuField::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 300BMenuField::~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 313BArchivable* 314BMenuField::Instantiate(BMessage* data) 315{ 316 if (validate_instantiation(data, "BMenuField")) 317 return new BMenuField(data); 318 319 return NULL; 320} 321 322 323status_t 324BMenuField::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 353status_t 354BMenuField::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 377status_t 378BMenuField::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 412void 413BMenuField::Draw(BRect updateRect) 414{ 415 _DrawLabel(updateRect); 416 _DrawMenuBar(updateRect); 417} 418 419 420void 421BMenuField::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 441void 442BMenuField::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 465void 466BMenuField::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 483void 484BMenuField::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 510void 511BMenuField::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 523void 524BMenuField::MessageReceived(BMessage* message) 525{ 526 BView::MessageReceived(message); 527} 528 529 530void 531BMenuField::WindowActivated(bool active) 532{ 533 BView::WindowActivated(active); 534 535 if (IsFocus()) 536 Invalidate(); 537} 538 539 540void 541BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage* message) 542{ 543 BView::MouseMoved(point, code, message); 544} 545 546 547void 548BMenuField::MouseUp(BPoint where) 549{ 550 Window()->RemoveCommonFilter(fMouseDownFilter); 551 BView::MouseUp(where); 552} 553 554 555void 556BMenuField::DetachedFromWindow() 557{ 558 BView::DetachedFromWindow(); 559} 560 561 562void 563BMenuField::AllDetached() 564{ 565 BView::AllDetached(); 566} 567 568 569void 570BMenuField::FrameMoved(BPoint newPosition) 571{ 572 BView::FrameMoved(newPosition); 573} 574 575 576void 577BMenuField::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 602BMenu* 603BMenuField::Menu() const 604{ 605 return fMenu; 606} 607 608 609BMenuBar* 610BMenuField::MenuBar() const 611{ 612 return fMenuBar; 613} 614 615 616BMenuItem* 617BMenuField::MenuItem() const 618{ 619 return fMenuBar->ItemAt(0); 620} 621 622 623void 624BMenuField::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 642const char* 643BMenuField::Label() const 644{ 645 return fLabel; 646} 647 648 649void 650BMenuField::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 665bool 666BMenuField::IsEnabled() const 667{ 668 return fEnabled; 669} 670 671 672void 673BMenuField::SetAlignment(alignment label) 674{ 675 fAlign = label; 676} 677 678 679alignment 680BMenuField::Alignment() const 681{ 682 return fAlign; 683} 684 685 686void 687BMenuField::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 717float 718BMenuField::Divider() const 719{ 720 return fDivider; 721} 722 723 724void 725BMenuField::ShowPopUpMarker() 726{ 727 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) { 728 menuBar->TogglePopUpMarker(true); 729 menuBar->Invalidate(); 730 } 731} 732 733 734void 735BMenuField::HidePopUpMarker() 736{ 737 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) { 738 menuBar->TogglePopUpMarker(false); 739 menuBar->Invalidate(); 740 } 741} 742 743 744BHandler* 745BMenuField::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 752status_t 753BMenuField::GetSupportedSuites(BMessage* data) 754{ 755 return BView::GetSupportedSuites(data); 756} 757 758 759void 760BMenuField::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 778void 779BMenuField::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 793BSize 794BMenuField::MinSize() 795{ 796 CALLED(); 797 798 _ValidateLayoutData(); 799 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 800} 801 802 803BSize 804BMenuField::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 817BSize 818BMenuField::PreferredSize() 819{ 820 CALLED(); 821 822 _ValidateLayoutData(); 823 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min); 824} 825 826 827BLayoutItem* 828BMenuField::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 837BLayoutItem* 838BMenuField::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 851status_t 852BMenuField::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 931void 932BMenuField::LayoutInvalidated(bool descendants) 933{ 934 CALLED(); 935 936 fLayoutData->valid = false; 937} 938 939 940void 941BMenuField::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 998void BMenuField::_ReservedMenuField1() {} 999void BMenuField::_ReservedMenuField2() {} 1000void BMenuField::_ReservedMenuField3() {} 1001 1002 1003void 1004BMenuField::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 1027void 1028BMenuField::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 1047void 1048BMenuField::_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 1095void 1096BMenuField::_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 1116void 1117BMenuField::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 1130BMenuField::_thread_entry(void* arg) 1131{ 1132 return static_cast<BMenuField*>(arg)->_MenuTask(); 1133} 1134 1135 1136int32 1137BMenuField::_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 1165void 1166BMenuField::_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 1198void 1199BMenuField::_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 1236void 1237BMenuField::_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 1266void 1267BMenuField::_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 1322void 1323BMenuField::_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 1383float 1384BMenuField::_MenuBarOffset() const 1385{ 1386 return std::max(fDivider + kVMargin, kVMargin); 1387} 1388 1389 1390float 1391BMenuField::_MenuBarWidth() const 1392{ 1393 return Bounds().Width() - (_MenuBarOffset() + kVMargin); 1394} 1395 1396 1397// #pragma mark - BMenuField::LabelLayoutItem 1398 1399 1400BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent) 1401 : 1402 fParent(parent), 1403 fFrame() 1404{ 1405} 1406 1407 1408BMenuField::LabelLayoutItem::LabelLayoutItem(BMessage* from) 1409 : 1410 BAbstractLayoutItem(from), 1411 fParent(NULL), 1412 fFrame() 1413{ 1414 from->FindRect(kFrameField, &fFrame); 1415} 1416 1417 1418BRect 1419BMenuField::LabelLayoutItem::FrameInParent() const 1420{ 1421 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1422} 1423 1424 1425bool 1426BMenuField::LabelLayoutItem::IsVisible() 1427{ 1428 return !fParent->IsHidden(fParent); 1429} 1430 1431 1432void 1433BMenuField::LabelLayoutItem::SetVisible(bool visible) 1434{ 1435 // not allowed 1436} 1437 1438 1439BRect 1440BMenuField::LabelLayoutItem::Frame() 1441{ 1442 return fFrame; 1443} 1444 1445 1446void 1447BMenuField::LabelLayoutItem::SetFrame(BRect frame) 1448{ 1449 fFrame = frame; 1450 fParent->_UpdateFrame(); 1451} 1452 1453 1454void 1455BMenuField::LabelLayoutItem::SetParent(BMenuField* parent) 1456{ 1457 fParent = parent; 1458} 1459 1460 1461BView* 1462BMenuField::LabelLayoutItem::View() 1463{ 1464 return fParent; 1465} 1466 1467 1468BSize 1469BMenuField::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 1482BSize 1483BMenuField::LabelLayoutItem::BaseMaxSize() 1484{ 1485 return BaseMinSize(); 1486} 1487 1488 1489BSize 1490BMenuField::LabelLayoutItem::BasePreferredSize() 1491{ 1492 return BaseMinSize(); 1493} 1494 1495 1496BAlignment 1497BMenuField::LabelLayoutItem::BaseAlignment() 1498{ 1499 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1500} 1501 1502 1503status_t 1504BMenuField::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 1516BArchivable* 1517BMenuField::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 1529BMenuField::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 1540BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMessage* from) 1541 : 1542 BAbstractLayoutItem(from), 1543 fParent(NULL), 1544 fFrame() 1545{ 1546 from->FindRect(kFrameField, &fFrame); 1547} 1548 1549 1550BRect 1551BMenuField::MenuBarLayoutItem::FrameInParent() const 1552{ 1553 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1554} 1555 1556 1557bool 1558BMenuField::MenuBarLayoutItem::IsVisible() 1559{ 1560 return !fParent->IsHidden(fParent); 1561} 1562 1563 1564void 1565BMenuField::MenuBarLayoutItem::SetVisible(bool visible) 1566{ 1567 // not allowed 1568} 1569 1570 1571BRect 1572BMenuField::MenuBarLayoutItem::Frame() 1573{ 1574 return fFrame; 1575} 1576 1577 1578void 1579BMenuField::MenuBarLayoutItem::SetFrame(BRect frame) 1580{ 1581 fFrame = frame; 1582 fParent->_UpdateFrame(); 1583} 1584 1585 1586void 1587BMenuField::MenuBarLayoutItem::SetParent(BMenuField* parent) 1588{ 1589 fParent = parent; 1590} 1591 1592 1593BView* 1594BMenuField::MenuBarLayoutItem::View() 1595{ 1596 return fParent; 1597} 1598 1599 1600BSize 1601BMenuField::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 1613BSize 1614BMenuField::MenuBarLayoutItem::BaseMaxSize() 1615{ 1616 BSize size(BaseMinSize()); 1617 size.width = B_SIZE_UNLIMITED; 1618 1619 return size; 1620} 1621 1622 1623BSize 1624BMenuField::MenuBarLayoutItem::BasePreferredSize() 1625{ 1626 return BaseMinSize(); 1627} 1628 1629 1630BAlignment 1631BMenuField::MenuBarLayoutItem::BaseAlignment() 1632{ 1633 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1634} 1635 1636 1637status_t 1638BMenuField::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 1650BArchivable* 1651BMenuField::MenuBarLayoutItem::Instantiate(BMessage* from) 1652{ 1653 if (validate_instantiation(from, "BMenuField::MenuBarLayoutItem")) 1654 return new MenuBarLayoutItem(from); 1655 return NULL; 1656} 1657 1658 1659extern "C" void 1660B_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