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