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