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