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