1 /* 2 * Copyright 2001-2006, 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 #include <stdlib.h> 12 #include <string.h> 13 14 #include <AbstractLayoutItem.h> 15 #include <LayoutUtils.h> 16 #include <MenuBar.h> 17 #include <MenuField.h> 18 #include <Message.h> 19 #include <BMCPrivate.h> 20 #include <Window.h> 21 22 23 class BMenuField::LabelLayoutItem : public BAbstractLayoutItem { 24 public: 25 LabelLayoutItem(BMenuField* parent); 26 27 virtual bool IsVisible(); 28 virtual void SetVisible(bool visible); 29 30 virtual BRect Frame(); 31 virtual void SetFrame(BRect frame); 32 33 virtual BView* View(); 34 35 virtual BSize BaseMinSize(); 36 virtual BSize BaseMaxSize(); 37 virtual BSize BasePreferredSize(); 38 virtual BAlignment BaseAlignment(); 39 40 private: 41 BMenuField* fParent; 42 BRect fFrame; 43 }; 44 45 46 class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem { 47 public: 48 MenuBarLayoutItem(BMenuField* parent); 49 50 virtual bool IsVisible(); 51 virtual void SetVisible(bool visible); 52 53 virtual BRect Frame(); 54 virtual void SetFrame(BRect frame); 55 56 virtual BView* View(); 57 58 virtual BSize BaseMinSize(); 59 virtual BSize BaseMaxSize(); 60 virtual BSize BasePreferredSize(); 61 virtual BAlignment BaseAlignment(); 62 63 private: 64 BMenuField* fParent; 65 BRect fFrame; 66 }; 67 68 69 struct BMenuField::LayoutData { 70 LayoutData() 71 : label_layout_item(NULL), 72 menu_bar_layout_item(NULL), 73 previous_height(-1), 74 valid(false) 75 { 76 } 77 78 LabelLayoutItem* label_layout_item; 79 MenuBarLayoutItem* menu_bar_layout_item; 80 float previous_height; // used in FrameResized() for 81 // invalidation 82 font_height font_info; 83 float label_width; 84 float label_height; 85 BSize min; 86 BSize menu_bar_min; 87 bool valid; 88 }; 89 90 91 // #pragma mark - 92 93 94 static float kVMargin = 2.0f; 95 96 97 BMenuField::BMenuField(BRect frame, const char *name, const char *label, 98 BMenu *menu, uint32 resize, uint32 flags) 99 : BView(frame, name, resize, flags) 100 { 101 InitObject(label); 102 103 frame.OffsetTo(B_ORIGIN); 104 _InitMenuBar(menu, frame, false); 105 106 InitObject2(); 107 } 108 109 110 BMenuField::BMenuField(BRect frame, const char *name, const char *label, 111 BMenu *menu, bool fixedSize, uint32 resize, uint32 flags) 112 : BView(frame, name, resize, flags) 113 { 114 InitObject(label); 115 116 fFixedSizeMB = fixedSize; 117 118 frame.OffsetTo(B_ORIGIN); 119 _InitMenuBar(menu, frame, fixedSize); 120 121 InitObject2(); 122 } 123 124 125 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu, 126 BMessage* message, uint32 flags) 127 : BView(BRect(0, 0, -1, -1), name, B_FOLLOW_NONE, 128 flags | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT) 129 { 130 InitObject(label); 131 132 _InitMenuBar(menu, BRect(0, 0, 100, 15), false); 133 134 InitObject2(); 135 } 136 137 138 BMenuField::BMenuField(const char* label, 139 BMenu* menu, BMessage* message) 140 : BView(BRect(0, 0, -1, -1), NULL, B_FOLLOW_NONE, 141 B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT) 142 { 143 InitObject(label); 144 145 _InitMenuBar(menu, BRect(0, 0, 100, 15), false); 146 147 InitObject2(); 148 } 149 150 151 BMenuField::BMenuField(BMessage *data) 152 : BView(data) 153 { 154 const char *label = NULL; 155 data->FindString("_label", &label); 156 157 InitObject(label); 158 159 fMenuBar = (BMenuBar*)FindView("_mc_mb_"); 160 if (!fMenuBar) 161 _InitMenuBar(new BMenu(""), BRect(0, 0, 100, 15), false); 162 fMenu = fMenuBar->SubmenuAt(0); 163 164 InitObject2(); 165 166 bool disable; 167 if (data->FindBool("_disable", &disable) == B_OK) 168 SetEnabled(!disable); 169 170 int32 align; 171 data->FindInt32("_align", &align); 172 SetAlignment((alignment)align); 173 174 data->FindFloat("_divide", &fDivider); 175 176 bool fixed; 177 if (data->FindBool("be:fixeds", &fixed) == B_OK) 178 fFixedSizeMB = fixed; 179 180 bool dmark = false; 181 data->FindBool("be:dmark", &dmark); 182 if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) { 183 menuBar->TogglePopUpMarker(dmark); 184 } 185 } 186 187 188 BMenuField::~BMenuField() 189 { 190 free(fLabel); 191 192 status_t dummy; 193 if (fMenuTaskID >= 0) 194 wait_for_thread(fMenuTaskID, &dummy); 195 196 delete fLayoutData; 197 } 198 199 200 BArchivable * 201 BMenuField::Instantiate(BMessage *data) 202 { 203 if (validate_instantiation(data, "BMenuField")) 204 return new BMenuField(data); 205 206 return NULL; 207 } 208 209 210 status_t 211 BMenuField::Archive(BMessage *data, bool deep) const 212 { 213 status_t ret = BView::Archive(data, deep); 214 215 if (ret == B_OK && Label()) 216 ret = data->AddString("_label", Label()); 217 218 if (ret == B_OK && !IsEnabled()) 219 ret = data->AddBool("_disable", true); 220 221 if (ret == B_OK) 222 ret = data->AddInt32("_align", Alignment()); 223 if (ret == B_OK) 224 ret = data->AddFloat("_divide", Divider()); 225 226 if (ret == B_OK && fFixedSizeMB) 227 ret = data->AddBool("be:fixeds", true); 228 229 bool dmark = false; 230 if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) { 231 dmark = menuBar->IsPopUpMarkerShown(); 232 } 233 data->AddBool("be:dmark", dmark); 234 235 return ret; 236 } 237 238 239 void 240 BMenuField::Draw(BRect update) 241 { 242 BRect bounds(Bounds()); 243 bool active = false; 244 245 if (IsFocus()) 246 active = Window()->IsActive(); 247 248 DrawLabel(bounds, update); 249 250 BRect frame(fMenuBar->Frame()); 251 252 if (frame.InsetByCopy(-kVMargin, -kVMargin).Intersects(update)) { 253 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 254 StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f), 255 BPoint(frame.left - 1.0f, frame.bottom - 1.0f)); 256 StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f), 257 BPoint(frame.right - 1.0f, frame.top - 1.0f)); 258 259 StrokeLine(BPoint(frame.left + 1.0f, frame.bottom + 1.0f), 260 BPoint(frame.right + 1.0f, frame.bottom + 1.0f)); 261 StrokeLine(BPoint(frame.right + 1.0f, frame.top + 1.0f)); 262 263 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_4_TINT)); 264 StrokeLine(BPoint(frame.left - 1.0f, frame.bottom), 265 BPoint(frame.left - 1.0f, frame.bottom)); 266 StrokeLine(BPoint(frame.right, frame.top - 1.0f), 267 BPoint(frame.right, frame.top - 1.0f)); 268 } 269 270 if (active || fTransition) { 271 SetHighColor(active ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) : 272 ViewColor()); 273 StrokeRect(frame.InsetByCopy(-kVMargin, -kVMargin)); 274 275 fTransition = false; 276 } 277 } 278 279 280 void 281 BMenuField::AttachedToWindow() 282 { 283 BView* parent = Parent(); 284 if (parent != NULL) { 285 // inherit the color from parent 286 rgb_color color = parent->ViewColor(); 287 if (color == B_TRANSPARENT_COLOR) 288 color = ui_color(B_PANEL_BACKGROUND_COLOR); 289 290 SetViewColor(color); 291 SetLowColor(color); 292 } 293 } 294 295 296 void 297 BMenuField::AllAttached() 298 { 299 ResizeTo(Bounds().Width(), 300 fMenuBar->Bounds().Height() + kVMargin + kVMargin); 301 } 302 303 304 void 305 BMenuField::MouseDown(BPoint where) 306 { 307 if (!fMenuBar->Frame().Contains(where)) 308 return; 309 310 BRect bounds = fMenuBar->ConvertFromParent(Bounds()); 311 312 fMenuBar->StartMenuBar(-1, false, true, &bounds); 313 314 fMenuTaskID = spawn_thread((thread_func)_thread_entry, 315 "_m_task_", B_NORMAL_PRIORITY, this); 316 if (fMenuTaskID >= 0) 317 resume_thread(fMenuTaskID); 318 } 319 320 321 void 322 BMenuField::KeyDown(const char *bytes, int32 numBytes) 323 { 324 switch (bytes[0]) { 325 case B_SPACE: 326 case B_RIGHT_ARROW: 327 case B_DOWN_ARROW: 328 { 329 if (!IsEnabled()) 330 break; 331 332 BRect bounds = fMenuBar->ConvertFromParent(Bounds()); 333 334 fMenuBar->StartMenuBar(0, true, true, &bounds); 335 336 fSelected = true; 337 fTransition = true; 338 339 bounds = Bounds(); 340 bounds.right = fDivider; 341 342 Invalidate(bounds); 343 } 344 345 default: 346 BView::KeyDown(bytes, numBytes); 347 } 348 } 349 350 351 void 352 BMenuField::MakeFocus(bool state) 353 { 354 if (IsFocus() == state) 355 return; 356 357 BView::MakeFocus(state); 358 359 if (Window()) 360 Invalidate(); // TODO: use fLayoutData->label_width 361 } 362 363 364 void 365 BMenuField::MessageReceived(BMessage *msg) 366 { 367 BView::MessageReceived(msg); 368 } 369 370 371 void 372 BMenuField::WindowActivated(bool state) 373 { 374 BView::WindowActivated(state); 375 376 if (IsFocus()) 377 Invalidate(); 378 } 379 380 381 void 382 BMenuField::MouseUp(BPoint point) 383 { 384 BView::MouseUp(point); 385 } 386 387 388 void 389 BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage *message) 390 { 391 BView::MouseMoved(point, code, message); 392 } 393 394 395 void 396 BMenuField::DetachedFromWindow() 397 { 398 BView::DetachedFromWindow(); 399 } 400 401 402 void 403 BMenuField::AllDetached() 404 { 405 BView::AllDetached(); 406 } 407 408 409 void 410 BMenuField::FrameMoved(BPoint newPosition) 411 { 412 BView::FrameMoved(newPosition); 413 } 414 415 416 void 417 BMenuField::FrameResized(float newWidth, float newHeight) 418 { 419 BView::FrameResized(newWidth, newHeight); 420 421 if (newHeight != fLayoutData->previous_height && Label()) { 422 // The height changed, which means the label has to move and we 423 // probably also invalidate a part of the borders around the menu bar. 424 // So don't be shy and invalidate the whole thing. 425 Invalidate(); 426 } 427 428 fLayoutData->previous_height = newHeight; 429 } 430 431 432 BMenu * 433 BMenuField::Menu() const 434 { 435 return fMenu; 436 } 437 438 439 BMenuBar * 440 BMenuField::MenuBar() const 441 { 442 return fMenuBar; 443 } 444 445 446 BMenuItem * 447 BMenuField::MenuItem() const 448 { 449 return fMenuBar->ItemAt(0); 450 } 451 452 453 void 454 BMenuField::SetLabel(const char *label) 455 { 456 if (fLabel) { 457 if (label && strcmp(fLabel, label) == 0) 458 return; 459 460 free(fLabel); 461 } 462 463 fLabel = strdup(label); 464 465 if (Window()) 466 Invalidate(); 467 468 InvalidateLayout(); 469 } 470 471 472 const char * 473 BMenuField::Label() const 474 { 475 return fLabel; 476 } 477 478 479 void 480 BMenuField::SetEnabled(bool on) 481 { 482 if (fEnabled == on) 483 return; 484 485 fEnabled = on; 486 fMenuBar->SetEnabled(on); 487 488 if (Window()) { 489 fMenuBar->Invalidate(fMenuBar->Bounds()); 490 Invalidate(Bounds()); 491 } 492 } 493 494 495 bool 496 BMenuField::IsEnabled() const 497 { 498 return fEnabled; 499 } 500 501 502 void 503 BMenuField::SetAlignment(alignment label) 504 { 505 fAlign = label; 506 } 507 508 509 alignment 510 BMenuField::Alignment() const 511 { 512 return fAlign; 513 } 514 515 516 void 517 BMenuField::SetDivider(float divider) 518 { 519 divider = floorf(divider + 0.5); 520 521 float dx = fDivider - divider; 522 523 if (dx == 0.0f) 524 return; 525 526 fDivider = divider; 527 528 if (Flags() & B_SUPPORTS_LAYOUT) { 529 // We should never get here, since layout support means, we also 530 // layout the divider, and don't use this method at all. 531 Relayout(); 532 } else { 533 BRect dirty(fMenuBar->Frame()); 534 535 fMenuBar->MoveTo(fDivider + 1, kVMargin); 536 537 if (fFixedSizeMB) { 538 fMenuBar->ResizeTo(Bounds().Width() - fDivider - 2, 539 dirty.Height()); 540 } 541 542 dirty = dirty | fMenuBar->Frame(); 543 dirty.InsetBy(-kVMargin, -kVMargin); 544 545 Invalidate(dirty); 546 } 547 } 548 549 550 float 551 BMenuField::Divider() const 552 { 553 return fDivider; 554 } 555 556 557 void 558 BMenuField::ShowPopUpMarker() 559 { 560 if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) { 561 menuBar->TogglePopUpMarker(true); 562 menuBar->Invalidate(); 563 } 564 } 565 566 567 void 568 BMenuField::HidePopUpMarker() 569 { 570 if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) { 571 menuBar->TogglePopUpMarker(false); 572 menuBar->Invalidate(); 573 } 574 } 575 576 577 BHandler * 578 BMenuField::ResolveSpecifier(BMessage *message, int32 index, 579 BMessage *specifier, int32 form, const char *property) 580 { 581 return BView::ResolveSpecifier(message, index, specifier, form, property); 582 } 583 584 585 status_t 586 BMenuField::GetSupportedSuites(BMessage *data) 587 { 588 return BView::GetSupportedSuites(data); 589 } 590 591 592 void 593 BMenuField::ResizeToPreferred() 594 { 595 fMenuBar->ResizeToPreferred(); 596 597 BView::ResizeToPreferred(); 598 599 if (fFixedSizeMB) { 600 // we have let the menubar resize itsself, but 601 // in fixed size mode, the menubar is supposed to 602 // be at the right end of the view always. Since 603 // the menu bar is in follow left/right mode then, 604 // resizing ourselfs might have caused the menubar 605 // to be outside now 606 fMenuBar->ResizeTo(Bounds().Width() - fDivider - 2, 607 fMenuBar->Frame().Height()); 608 } 609 } 610 611 612 void 613 BMenuField::GetPreferredSize(float *_width, float *_height) 614 { 615 _ValidateLayoutData(); 616 617 if (_width) 618 *_width = fLayoutData->min.width; 619 620 if (_height) 621 *_height = fLayoutData->min.height; 622 } 623 624 625 BSize 626 BMenuField::MinSize() 627 { 628 _ValidateLayoutData(); 629 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 630 } 631 632 633 BSize 634 BMenuField::MaxSize() 635 { 636 _ValidateLayoutData(); 637 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), fLayoutData->min); 638 } 639 640 641 BSize 642 BMenuField::PreferredSize() 643 { 644 _ValidateLayoutData(); 645 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min); 646 } 647 648 649 void 650 BMenuField::InvalidateLayout(bool descendants) 651 { 652 fLayoutData->valid = false; 653 654 BView::InvalidateLayout(descendants); 655 } 656 657 658 BLayoutItem* 659 BMenuField::CreateLabelLayoutItem() 660 { 661 if (!fLayoutData->label_layout_item) 662 fLayoutData->label_layout_item = new LabelLayoutItem(this); 663 return fLayoutData->label_layout_item; 664 } 665 666 667 BLayoutItem* 668 BMenuField::CreateMenuBarLayoutItem() 669 { 670 if (!fLayoutData->menu_bar_layout_item) 671 fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this); 672 return fLayoutData->menu_bar_layout_item; 673 } 674 675 676 status_t 677 BMenuField::Perform(perform_code d, void *arg) 678 { 679 return BView::Perform(d, arg); 680 } 681 682 683 void 684 BMenuField::DoLayout() 685 { 686 // Bail out, if we shan't do layout. 687 if (!(Flags() & B_SUPPORTS_LAYOUT)) 688 return; 689 690 // If the user set a layout, we let the base class version call its 691 // hook. 692 if (GetLayout()) { 693 BView::DoLayout(); 694 return; 695 } 696 697 _ValidateLayoutData(); 698 699 // validate current size 700 BSize size(Bounds().Size()); 701 if (size.width < fLayoutData->min.width) 702 size.width = fLayoutData->min.width; 703 if (size.height < fLayoutData->min.height) 704 size.height = fLayoutData->min.height; 705 706 // divider 707 float divider = 0; 708 if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) { 709 // We have layout items. They define the divider location. 710 divider = fLayoutData->menu_bar_layout_item->Frame().left 711 - fLayoutData->label_layout_item->Frame().left; 712 } else { 713 if (fLayoutData->label_width > 0) 714 divider = fLayoutData->label_width + 5; 715 } 716 717 // menu bar 718 BRect dirty(fMenuBar->Frame()); 719 BRect menuBarFrame(divider + 1, kVMargin, size.width - 2, 720 size.height - kVMargin); 721 722 // place the menu bar and set the divider 723 BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame); 724 725 fDivider = divider; 726 727 // invalidate dirty region 728 dirty = dirty | fMenuBar->Frame(); 729 dirty.InsetBy(-kVMargin, -kVMargin); 730 731 Invalidate(dirty); 732 } 733 734 735 void BMenuField::_ReservedMenuField1() {} 736 void BMenuField::_ReservedMenuField2() {} 737 void BMenuField::_ReservedMenuField3() {} 738 739 740 BMenuField & 741 BMenuField::operator=(const BMenuField &) 742 { 743 return *this; 744 } 745 746 747 void 748 BMenuField::InitObject(const char *label) 749 { 750 fLabel = NULL; 751 fMenu = NULL; 752 fMenuBar = NULL; 753 fAlign = B_ALIGN_LEFT; 754 fEnabled = true; 755 fSelected = false; 756 fTransition = false; 757 fFixedSizeMB = false; 758 fMenuTaskID = -1; 759 fLayoutData = new LayoutData; 760 761 SetLabel(label); 762 763 if (label) 764 fDivider = (float)floor(Frame().Width() / 2.0f); 765 else 766 fDivider = 0; 767 768 // default to unlimited maximum width 769 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 770 } 771 772 773 void 774 BMenuField::InitObject2() 775 { 776 font_height fontHeight; 777 GetFontHeight(&fontHeight); 778 779 // TODO: fix this calculation 780 float height = floorf(fontHeight.ascent + fontHeight.descent 781 + fontHeight.leading) + 7; 782 783 fMenuBar->ResizeTo(Bounds().Width() - fDivider - 2, height); 784 fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN)); 785 } 786 787 788 void 789 BMenuField::DrawLabel(BRect bounds, BRect update) 790 { 791 _ValidateLayoutData(); 792 font_height& fh = fLayoutData->font_info; 793 794 if (Label()) { 795 SetLowColor(ViewColor()); 796 797 // horizontal alignment 798 float x; 799 switch (fAlign) { 800 case B_ALIGN_RIGHT: 801 x = fDivider - fLayoutData->label_width - 3.0; 802 break; 803 804 case B_ALIGN_CENTER: 805 x = fDivider - fLayoutData->label_width / 2.0; 806 break; 807 808 default: 809 x = 0.0; 810 break; 811 } 812 813 // vertical alignment 814 float y = Bounds().top 815 + (Bounds().Height() + 1 - fh.ascent - fh.descent) / 2 816 + fh.ascent; 817 y = floor(y + 0.5); 818 819 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 820 IsEnabled() ? B_DARKEN_MAX_TINT : B_DISABLED_LABEL_TINT)); 821 DrawString(Label(), BPoint(x, y)); 822 } 823 } 824 825 826 void 827 BMenuField::InitMenu(BMenu *menu) 828 { 829 menu->SetFont(be_plain_font); 830 831 int32 index = 0; 832 BMenu *subMenu; 833 834 while ((subMenu = menu->SubmenuAt(index++)) != NULL) 835 InitMenu(subMenu); 836 } 837 838 839 /* static */ 840 int32 841 BMenuField::_thread_entry(void *arg) 842 { 843 return static_cast<BMenuField *>(arg)->_MenuTask(); 844 } 845 846 847 int32 848 BMenuField::_MenuTask() 849 { 850 if (!LockLooper()) 851 return 0; 852 853 fSelected = true; 854 fTransition = true; 855 Invalidate(); 856 UnlockLooper(); 857 858 bool tracking; 859 do { 860 snooze(20000); 861 if (!LockLooper()) 862 return 0; 863 864 tracking = fMenuBar->fTracking; 865 866 UnlockLooper(); 867 } while (tracking); 868 869 if (LockLooper()) { 870 fSelected = false; 871 fTransition = true; 872 Invalidate(); 873 UnlockLooper(); 874 } 875 876 return 0; 877 } 878 879 880 void 881 BMenuField::_UpdateFrame() 882 { 883 if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) { 884 BRect labelFrame = fLayoutData->label_layout_item->Frame(); 885 BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame(); 886 887 // update divider 888 fDivider = menuFrame.left - labelFrame.left; 889 890 // update our frame 891 MoveTo(labelFrame.left, labelFrame.top); 892 BSize oldSize = Bounds().Size(); 893 ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left, 894 menuFrame.top + menuFrame.Height() - labelFrame.top); 895 BSize newSize = Bounds().Size(); 896 897 // If the size changes, ResizeTo() will trigger a relayout, otherwise 898 // we need to do that explicitly. 899 if (newSize != oldSize) 900 Relayout(); 901 } 902 } 903 904 905 void 906 BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize) 907 { 908 fMenu = menu; 909 InitMenu(menu); 910 911 fMenuBar = new _BMCMenuBar_(BRect(frame.left + fDivider + 1, 912 frame.top + kVMargin, frame.right - 2, frame.bottom - kVMargin), 913 fixedSize, this); 914 915 // by default align the menu bar left in the available space 916 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 917 B_ALIGN_VERTICAL_UNSET)); 918 919 AddChild(fMenuBar); 920 fMenuBar->AddItem(menu); 921 922 fMenuBar->SetFont(be_plain_font); 923 } 924 925 926 void 927 BMenuField::_ValidateLayoutData() 928 { 929 if (fLayoutData->valid) 930 return; 931 932 // cache font height 933 font_height& fh = fLayoutData->font_info; 934 GetFontHeight(&fh); 935 936 fLayoutData->label_width = (Label() ? ceilf(StringWidth(Label())) : 0); 937 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 938 939 // compute the minimal divider 940 float divider = 0; 941 if (fLayoutData->label_width > 0) 942 divider = fLayoutData->label_width + 5; 943 944 // If we shan't do real layout, we let the current divider take influence. 945 if (!(Flags() & B_SUPPORTS_LAYOUT)) 946 divider = max_c(divider, fDivider); 947 948 // get the minimal (== preferred) menu bar size 949 fLayoutData->menu_bar_min = fMenuBar->MinSize(); 950 951 // compute our minimal (== preferred) size 952 // TODO: The layout is a bit broken. A one pixel wide border is draw around 953 // the menu bar to give it it's look. When the view has the focus, 954 // additionally a one pixel wide blue frame is drawn around it. In order 955 // to be able to easily visually align the menu bar with the text view of 956 // a text control, the divider must ignore the focus frame, though. Hence 957 // we add one less pixel to our width. 958 BSize min(fLayoutData->menu_bar_min); 959 min.width += 2 * kVMargin - 1; 960 min.height += 2 * kVMargin; 961 962 if (fLayoutData->label_width > 0) 963 min.width += fLayoutData->label_width + 5; 964 if (fLayoutData->label_height > min.height) 965 min.height = fLayoutData->label_height; 966 967 fLayoutData->min = min; 968 969 fLayoutData->valid = true; 970 } 971 972 973 // #pragma mark - 974 975 976 BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent) 977 : fParent(parent), 978 fFrame() 979 { 980 } 981 982 983 bool 984 BMenuField::LabelLayoutItem::IsVisible() 985 { 986 return !fParent->IsHidden(fParent); 987 } 988 989 990 void 991 BMenuField::LabelLayoutItem::SetVisible(bool visible) 992 { 993 // not allowed 994 } 995 996 997 BRect 998 BMenuField::LabelLayoutItem::Frame() 999 { 1000 return fFrame; 1001 } 1002 1003 1004 void 1005 BMenuField::LabelLayoutItem::SetFrame(BRect frame) 1006 { 1007 fFrame = frame; 1008 fParent->_UpdateFrame(); 1009 } 1010 1011 1012 BView* 1013 BMenuField::LabelLayoutItem::View() 1014 { 1015 return fParent; 1016 } 1017 1018 1019 BSize 1020 BMenuField::LabelLayoutItem::BaseMinSize() 1021 { 1022 fParent->_ValidateLayoutData(); 1023 1024 if (!fParent->Label()) 1025 return BSize(-1, -1); 1026 1027 return BSize(fParent->fLayoutData->label_width + 5, 1028 fParent->fLayoutData->label_height); 1029 } 1030 1031 1032 BSize 1033 BMenuField::LabelLayoutItem::BaseMaxSize() 1034 { 1035 return BaseMinSize(); 1036 } 1037 1038 1039 BSize 1040 BMenuField::LabelLayoutItem::BasePreferredSize() 1041 { 1042 return BaseMinSize(); 1043 } 1044 1045 1046 BAlignment 1047 BMenuField::LabelLayoutItem::BaseAlignment() 1048 { 1049 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1050 } 1051 1052 1053 // #pragma mark - 1054 1055 1056 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* parent) 1057 : fParent(parent), 1058 fFrame() 1059 { 1060 // by default the part left of the divider shall have an unlimited maximum 1061 // width 1062 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1063 } 1064 1065 1066 bool 1067 BMenuField::MenuBarLayoutItem::IsVisible() 1068 { 1069 return !fParent->IsHidden(fParent); 1070 } 1071 1072 1073 void 1074 BMenuField::MenuBarLayoutItem::SetVisible(bool visible) 1075 { 1076 // not allowed 1077 } 1078 1079 1080 BRect 1081 BMenuField::MenuBarLayoutItem::Frame() 1082 { 1083 return fFrame; 1084 } 1085 1086 1087 void 1088 BMenuField::MenuBarLayoutItem::SetFrame(BRect frame) 1089 { 1090 fFrame = frame; 1091 fParent->_UpdateFrame(); 1092 } 1093 1094 1095 BView* 1096 BMenuField::MenuBarLayoutItem::View() 1097 { 1098 return fParent; 1099 } 1100 1101 1102 BSize 1103 BMenuField::MenuBarLayoutItem::BaseMinSize() 1104 { 1105 fParent->_ValidateLayoutData(); 1106 1107 // TODO: Cf. the TODO in _ValidateLayoutData(). 1108 BSize size = fParent->fLayoutData->menu_bar_min; 1109 size.width += 2 * kVMargin - 1; 1110 size.height += 2 * kVMargin; 1111 1112 return size; 1113 } 1114 1115 1116 BSize 1117 BMenuField::MenuBarLayoutItem::BaseMaxSize() 1118 { 1119 return BaseMinSize(); 1120 } 1121 1122 1123 BSize 1124 BMenuField::MenuBarLayoutItem::BasePreferredSize() 1125 { 1126 return BaseMinSize(); 1127 } 1128 1129 1130 BAlignment 1131 BMenuField::MenuBarLayoutItem::BaseAlignment() 1132 { 1133 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1134 } 1135 1136