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