vfs: check userland buffers before reading them.
[haiku.git] / src / kits / interface / MenuField.cpp
blob6f68f36a26d4d16774e5a2ec49870b046726e6a6
1 /*
2 * Copyright 2006-2016 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Marc Flerackers, mflerackers@androme.be
8 * John Scipione, jscipione@gmail.com
9 * Ingo Weinhold, bonefish@cs.tu-berlin.de
13 #include <MenuField.h>
15 #include <algorithm>
17 #include <stdio.h>
18 // for printf in TRACE
19 #include <stdlib.h>
20 #include <string.h>
22 #include <AbstractLayoutItem.h>
23 #include <Archivable.h>
24 #include <BMCPrivate.h>
25 #include <ControlLook.h>
26 #include <LayoutUtils.h>
27 #include <MenuBar.h>
28 #include <MenuItem.h>
29 #include <MenuItemPrivate.h>
30 #include <MenuPrivate.h>
31 #include <Message.h>
32 #include <MessageFilter.h>
33 #include <Thread.h>
34 #include <Window.h>
36 #include <binary_compatibility/Interface.h>
37 #include <binary_compatibility/Support.h>
40 #ifdef CALLED
41 # undef CALLED
42 #endif
43 #ifdef TRACE
44 # undef TRACE
45 #endif
47 //#define TRACE_MENU_FIELD
48 #ifdef TRACE_MENU_FIELD
49 # include <FunctionTracer.h>
50 static int32 sFunctionDepth = -1;
51 # define CALLED(x...) FunctionTracer _ft("BMenuField", __FUNCTION__, \
52 sFunctionDepth)
53 # define TRACE(x...) { BString _to; \
54 _to.Append(' ', (sFunctionDepth + 1) * 2); \
55 printf("%s", _to.String()); printf(x); }
56 #else
57 # define CALLED(x...)
58 # define TRACE(x...)
59 #endif
62 static const float kMinMenuBarWidth = 20.0f;
63 // found by experimenting on BeOS R5
66 namespace {
67 const char* const kFrameField = "BMenuField:layoutItem:frame";
68 const char* const kMenuBarItemField = "BMenuField:barItem";
69 const char* const kLabelItemField = "BMenuField:labelItem";
73 // #pragma mark - LabelLayoutItem
76 class BMenuField::LabelLayoutItem : public BAbstractLayoutItem {
77 public:
78 LabelLayoutItem(BMenuField* parent);
79 LabelLayoutItem(BMessage* archive);
81 BRect FrameInParent() const;
83 virtual bool IsVisible();
84 virtual void SetVisible(bool visible);
86 virtual BRect Frame();
87 virtual void SetFrame(BRect frame);
89 void SetParent(BMenuField* parent);
90 virtual BView* View();
92 virtual BSize BaseMinSize();
93 virtual BSize BaseMaxSize();
94 virtual BSize BasePreferredSize();
95 virtual BAlignment BaseAlignment();
97 virtual status_t Archive(BMessage* into, bool deep = true) const;
98 static BArchivable* Instantiate(BMessage* from);
100 private:
101 BMenuField* fParent;
102 BRect fFrame;
106 // #pragma mark - MenuBarLayoutItem
109 class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem {
110 public:
111 MenuBarLayoutItem(BMenuField* parent);
112 MenuBarLayoutItem(BMessage* from);
114 BRect FrameInParent() const;
116 virtual bool IsVisible();
117 virtual void SetVisible(bool visible);
119 virtual BRect Frame();
120 virtual void SetFrame(BRect frame);
122 void SetParent(BMenuField* parent);
123 virtual BView* View();
125 virtual BSize BaseMinSize();
126 virtual BSize BaseMaxSize();
127 virtual BSize BasePreferredSize();
128 virtual BAlignment BaseAlignment();
130 virtual status_t Archive(BMessage* into, bool deep = true) const;
131 static BArchivable* Instantiate(BMessage* from);
133 private:
134 BMenuField* fParent;
135 BRect fFrame;
139 // #pragma mark - LayoutData
142 struct BMenuField::LayoutData {
143 LayoutData()
145 label_layout_item(NULL),
146 menu_bar_layout_item(NULL),
147 previous_height(-1),
148 valid(false)
152 LabelLayoutItem* label_layout_item;
153 MenuBarLayoutItem* menu_bar_layout_item;
154 float previous_height; // used in FrameResized() for
155 // invalidation
156 font_height font_info;
157 float label_width;
158 float label_height;
159 BSize min;
160 BSize menu_bar_min;
161 bool valid;
165 // #pragma mark - MouseDownFilter
168 class MouseDownFilter : public BMessageFilter
170 public:
171 MouseDownFilter();
172 virtual ~MouseDownFilter();
174 virtual filter_result Filter(BMessage* message, BHandler** target);
178 MouseDownFilter::MouseDownFilter()
180 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE)
185 MouseDownFilter::~MouseDownFilter()
190 filter_result
191 MouseDownFilter::Filter(BMessage* message, BHandler** target)
193 return message->what == B_MOUSE_DOWN ? B_SKIP_MESSAGE : B_DISPATCH_MESSAGE;
197 // #pragma mark - BMenuField
200 BMenuField::BMenuField(BRect frame, const char* name, const char* label,
201 BMenu* menu, uint32 resizingMode, uint32 flags)
203 BView(frame, name, resizingMode, flags)
205 CALLED();
207 TRACE("frame.width: %.2f, height: %.2f\n", frame.Width(), frame.Height());
209 InitObject(label);
211 frame.OffsetTo(B_ORIGIN);
212 _InitMenuBar(menu, frame, false);
214 InitObject2();
218 BMenuField::BMenuField(BRect frame, const char* name, const char* label,
219 BMenu* menu, bool fixedSize, uint32 resizingMode, uint32 flags)
221 BView(frame, name, resizingMode, flags)
223 InitObject(label);
225 fFixedSizeMB = fixedSize;
227 frame.OffsetTo(B_ORIGIN);
228 _InitMenuBar(menu, frame, fixedSize);
230 InitObject2();
234 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu,
235 uint32 flags)
237 BView(name, flags | B_FRAME_EVENTS)
239 InitObject(label);
241 _InitMenuBar(menu, BRect(0, 0, 100, 15), true);
243 InitObject2();
247 BMenuField::BMenuField(const char* label, BMenu* menu, uint32 flags)
249 BView(NULL, flags | B_FRAME_EVENTS)
251 InitObject(label);
253 _InitMenuBar(menu, BRect(0, 0, 100, 15), true);
255 InitObject2();
259 //! Copy&Paste error, should be removed at some point (already private)
260 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu,
261 BMessage* message, uint32 flags)
263 BView(name, flags | B_FRAME_EVENTS)
265 InitObject(label);
267 _InitMenuBar(menu, BRect(0, 0, 100, 15), true);
269 InitObject2();
273 //! Copy&Paste error, should be removed at some point (already private)
274 BMenuField::BMenuField(const char* label, BMenu* menu, BMessage* message)
276 BView(NULL, B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
278 InitObject(label);
280 _InitMenuBar(menu, BRect(0, 0, 100, 15), true);
282 InitObject2();
286 BMenuField::BMenuField(BMessage* data)
288 BView(BUnarchiver::PrepareArchive(data))
290 BUnarchiver unarchiver(data);
291 const char* label = NULL;
292 data->FindString("_label", &label);
294 InitObject(label);
296 data->FindFloat("_divide", &fDivider);
298 int32 align;
299 if (data->FindInt32("_align", &align) == B_OK)
300 SetAlignment((alignment)align);
302 if (!BUnarchiver::IsArchiveManaged(data))
303 _InitMenuBar(data);
305 unarchiver.Finish();
309 BMenuField::~BMenuField()
311 free(fLabel);
313 status_t dummy;
314 if (fMenuTaskID >= 0)
315 wait_for_thread(fMenuTaskID, &dummy);
317 delete fLayoutData;
318 delete fMouseDownFilter;
322 BArchivable*
323 BMenuField::Instantiate(BMessage* data)
325 if (validate_instantiation(data, "BMenuField"))
326 return new BMenuField(data);
328 return NULL;
332 status_t
333 BMenuField::Archive(BMessage* data, bool deep) const
335 BArchiver archiver(data);
336 status_t ret = BView::Archive(data, deep);
338 if (ret == B_OK && Label())
339 ret = data->AddString("_label", Label());
341 if (ret == B_OK && !IsEnabled())
342 ret = data->AddBool("_disable", true);
344 if (ret == B_OK)
345 ret = data->AddInt32("_align", Alignment());
346 if (ret == B_OK)
347 ret = data->AddFloat("_divide", Divider());
349 if (ret == B_OK && fFixedSizeMB)
350 ret = data->AddBool("be:fixeds", true);
352 bool dmark = false;
353 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar))
354 dmark = menuBar->IsPopUpMarkerShown();
356 data->AddBool("be:dmark", dmark);
358 return archiver.Finish(ret);
362 status_t
363 BMenuField::AllArchived(BMessage* into) const
365 status_t err;
366 if ((err = BView::AllArchived(into)) != B_OK)
367 return err;
369 BArchiver archiver(into);
371 BArchivable* menuBarItem = fLayoutData->menu_bar_layout_item;
372 if (archiver.IsArchived(menuBarItem))
373 err = archiver.AddArchivable(kMenuBarItemField, menuBarItem);
375 if (err != B_OK)
376 return err;
378 BArchivable* labelBarItem = fLayoutData->label_layout_item;
379 if (archiver.IsArchived(labelBarItem))
380 err = archiver.AddArchivable(kLabelItemField, labelBarItem);
382 return err;
386 status_t
387 BMenuField::AllUnarchived(const BMessage* from)
389 BUnarchiver unarchiver(from);
391 status_t err = B_OK;
392 if ((err = BView::AllUnarchived(from)) != B_OK)
393 return err;
395 _InitMenuBar(from);
397 if (unarchiver.IsInstantiated(kMenuBarItemField)) {
398 MenuBarLayoutItem*& menuItem = fLayoutData->menu_bar_layout_item;
399 err = unarchiver.FindObject(kMenuBarItemField,
400 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, menuItem);
402 if (err == B_OK)
403 menuItem->SetParent(this);
404 else
405 return err;
408 if (unarchiver.IsInstantiated(kLabelItemField)) {
409 LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
410 err = unarchiver.FindObject(kLabelItemField,
411 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);
413 if (err == B_OK)
414 labelItem->SetParent(this);
417 return err;
421 void
422 BMenuField::Draw(BRect updateRect)
424 _DrawLabel(updateRect);
425 _DrawMenuBar(updateRect);
429 void
430 BMenuField::AttachedToWindow()
432 CALLED();
434 // Our low color must match the parent's view color.
435 if (Parent() != NULL) {
436 AdoptParentColors();
438 float tint = B_NO_TINT;
439 color_which which = ViewUIColor(&tint);
441 if (which == B_NO_COLOR)
442 SetLowColor(ViewColor());
443 else
444 SetLowUIColor(which, tint);
445 } else
446 AdoptSystemColors();
450 void
451 BMenuField::AllAttached()
453 CALLED();
455 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
457 float width = Bounds().Width();
458 if (!fFixedSizeMB && _MenuBarWidth() < kMinMenuBarWidth) {
459 // The menu bar is too narrow, resize it to fit the menu items
460 BMenuItem* item = fMenuBar->ItemAt(0);
461 if (item != NULL) {
462 float right;
463 fMenuBar->GetItemMargins(NULL, NULL, &right, NULL);
464 width = item->Frame().Width() + kVMargin + _MenuBarOffset() + right;
468 ResizeTo(width, fMenuBar->Bounds().Height() + kVMargin * 2);
470 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
474 void
475 BMenuField::MouseDown(BPoint where)
477 BRect bounds = fMenuBar->ConvertFromParent(Bounds());
479 fMenuBar->StartMenuBar(-1, false, true, &bounds);
481 fMenuTaskID = spawn_thread((thread_func)_thread_entry,
482 "_m_task_", B_NORMAL_PRIORITY, this);
483 if (fMenuTaskID >= 0 && resume_thread(fMenuTaskID) == B_OK) {
484 if (fMouseDownFilter->Looper() == NULL)
485 Window()->AddCommonFilter(fMouseDownFilter);
487 MouseDownThread<BMenuField>::TrackMouse(this, &BMenuField::_DoneTracking,
488 &BMenuField::_Track);
493 void
494 BMenuField::KeyDown(const char* bytes, int32 numBytes)
496 switch (bytes[0]) {
497 case B_SPACE:
498 case B_RIGHT_ARROW:
499 case B_DOWN_ARROW:
501 if (!IsEnabled())
502 break;
504 BRect bounds = fMenuBar->ConvertFromParent(Bounds());
506 fMenuBar->StartMenuBar(0, true, true, &bounds);
508 bounds = Bounds();
509 bounds.right = fDivider;
511 Invalidate(bounds);
514 default:
515 BView::KeyDown(bytes, numBytes);
520 void
521 BMenuField::MakeFocus(bool focused)
523 if (IsFocus() == focused)
524 return;
526 BView::MakeFocus(focused);
528 if (Window() != NULL)
529 Invalidate(); // TODO: use fLayoutData->label_width
533 void
534 BMenuField::MessageReceived(BMessage* message)
536 BView::MessageReceived(message);
540 void
541 BMenuField::WindowActivated(bool active)
543 BView::WindowActivated(active);
545 if (IsFocus())
546 Invalidate();
550 void
551 BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage* message)
553 BView::MouseMoved(point, code, message);
557 void
558 BMenuField::MouseUp(BPoint where)
560 BView::MouseUp(where);
564 void
565 BMenuField::DetachedFromWindow()
567 BView::DetachedFromWindow();
571 void
572 BMenuField::AllDetached()
574 BView::AllDetached();
578 void
579 BMenuField::FrameMoved(BPoint newPosition)
581 BView::FrameMoved(newPosition);
585 void
586 BMenuField::FrameResized(float newWidth, float newHeight)
588 BView::FrameResized(newWidth, newHeight);
590 if (fFixedSizeMB) {
591 // we have let the menubar resize itself, but
592 // in fixed size mode, the menubar is supposed to
593 // be at the right end of the view always. Since
594 // the menu bar is in follow left/right mode then,
595 // resizing ourselfs might have caused the menubar
596 // to be outside now
597 fMenuBar->ResizeTo(_MenuBarWidth(), fMenuBar->Frame().Height());
600 if (newHeight != fLayoutData->previous_height && Label()) {
601 // The height changed, which means the label has to move and we
602 // probably also invalidate a part of the borders around the menu bar.
603 // So don't be shy and invalidate the whole thing.
604 Invalidate();
607 fLayoutData->previous_height = newHeight;
611 BMenu*
612 BMenuField::Menu() const
614 return fMenu;
618 BMenuBar*
619 BMenuField::MenuBar() const
621 return fMenuBar;
625 BMenuItem*
626 BMenuField::MenuItem() const
628 return fMenuBar->ItemAt(0);
632 void
633 BMenuField::SetLabel(const char* label)
635 if (fLabel) {
636 if (label && strcmp(fLabel, label) == 0)
637 return;
639 free(fLabel);
642 fLabel = strdup(label);
644 if (Window())
645 Invalidate();
647 InvalidateLayout();
651 const char*
652 BMenuField::Label() const
654 return fLabel;
658 void
659 BMenuField::SetEnabled(bool on)
661 if (fEnabled == on)
662 return;
664 fEnabled = on;
665 fMenuBar->SetEnabled(on);
667 if (Window()) {
668 fMenuBar->Invalidate(fMenuBar->Bounds());
669 Invalidate(Bounds());
674 bool
675 BMenuField::IsEnabled() const
677 return fEnabled;
681 void
682 BMenuField::SetAlignment(alignment label)
684 fAlign = label;
688 alignment
689 BMenuField::Alignment() const
691 return fAlign;
695 void
696 BMenuField::SetDivider(float position)
698 position = roundf(position);
700 float delta = fDivider - position;
701 if (delta == 0.0f)
702 return;
704 fDivider = position;
706 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
707 // We should never get here, since layout support means, we also
708 // layout the divider, and don't use this method at all.
709 Relayout();
710 } else {
711 BRect dirty(fMenuBar->Frame());
713 fMenuBar->MoveTo(_MenuBarOffset(), kVMargin);
715 if (fFixedSizeMB)
716 fMenuBar->ResizeTo(_MenuBarWidth(), dirty.Height());
718 dirty = dirty | fMenuBar->Frame();
719 dirty.InsetBy(-kVMargin, -kVMargin);
721 Invalidate(dirty);
726 float
727 BMenuField::Divider() const
729 return fDivider;
733 void
734 BMenuField::ShowPopUpMarker()
736 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) {
737 menuBar->TogglePopUpMarker(true);
738 menuBar->Invalidate();
743 void
744 BMenuField::HidePopUpMarker()
746 if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) {
747 menuBar->TogglePopUpMarker(false);
748 menuBar->Invalidate();
753 BHandler*
754 BMenuField::ResolveSpecifier(BMessage* message, int32 index,
755 BMessage* specifier, int32 form, const char* property)
757 return BView::ResolveSpecifier(message, index, specifier, form, property);
761 status_t
762 BMenuField::GetSupportedSuites(BMessage* data)
764 return BView::GetSupportedSuites(data);
768 void
769 BMenuField::ResizeToPreferred()
771 CALLED();
773 TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n",
774 fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
776 fMenuBar->ResizeToPreferred();
778 TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n",
779 fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
781 BView::ResizeToPreferred();
783 Invalidate();
787 void
788 BMenuField::GetPreferredSize(float* _width, float* _height)
790 CALLED();
792 _ValidateLayoutData();
794 if (_width)
795 *_width = fLayoutData->min.width;
797 if (_height)
798 *_height = fLayoutData->min.height;
802 BSize
803 BMenuField::MinSize()
805 CALLED();
807 _ValidateLayoutData();
808 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
812 BSize
813 BMenuField::MaxSize()
815 CALLED();
817 _ValidateLayoutData();
819 BSize max = fLayoutData->min;
820 max.width = B_SIZE_UNLIMITED;
822 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
826 BSize
827 BMenuField::PreferredSize()
829 CALLED();
831 _ValidateLayoutData();
832 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
836 BLayoutItem*
837 BMenuField::CreateLabelLayoutItem()
839 if (fLayoutData->label_layout_item == NULL)
840 fLayoutData->label_layout_item = new LabelLayoutItem(this);
842 return fLayoutData->label_layout_item;
846 BLayoutItem*
847 BMenuField::CreateMenuBarLayoutItem()
849 if (fLayoutData->menu_bar_layout_item == NULL) {
850 // align the menu bar in the full available space
851 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
852 B_ALIGN_VERTICAL_UNSET));
853 fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this);
856 return fLayoutData->menu_bar_layout_item;
860 status_t
861 BMenuField::Perform(perform_code code, void* _data)
863 switch (code) {
864 case PERFORM_CODE_MIN_SIZE:
865 ((perform_data_min_size*)_data)->return_value
866 = BMenuField::MinSize();
867 return B_OK;
869 case PERFORM_CODE_MAX_SIZE:
870 ((perform_data_max_size*)_data)->return_value
871 = BMenuField::MaxSize();
872 return B_OK;
874 case PERFORM_CODE_PREFERRED_SIZE:
875 ((perform_data_preferred_size*)_data)->return_value
876 = BMenuField::PreferredSize();
877 return B_OK;
879 case PERFORM_CODE_LAYOUT_ALIGNMENT:
880 ((perform_data_layout_alignment*)_data)->return_value
881 = BMenuField::LayoutAlignment();
882 return B_OK;
884 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
885 ((perform_data_has_height_for_width*)_data)->return_value
886 = BMenuField::HasHeightForWidth();
887 return B_OK;
889 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
891 perform_data_get_height_for_width* data
892 = (perform_data_get_height_for_width*)_data;
893 BMenuField::GetHeightForWidth(data->width, &data->min, &data->max,
894 &data->preferred);
895 return B_OK;
898 case PERFORM_CODE_SET_LAYOUT:
900 perform_data_set_layout* data = (perform_data_set_layout*)_data;
901 BMenuField::SetLayout(data->layout);
902 return B_OK;
905 case PERFORM_CODE_LAYOUT_INVALIDATED:
907 perform_data_layout_invalidated* data
908 = (perform_data_layout_invalidated*)_data;
909 BMenuField::LayoutInvalidated(data->descendants);
910 return B_OK;
913 case PERFORM_CODE_DO_LAYOUT:
915 BMenuField::DoLayout();
916 return B_OK;
919 case PERFORM_CODE_ALL_UNARCHIVED:
921 perform_data_all_unarchived* data
922 = (perform_data_all_unarchived*)_data;
923 data->return_value = BMenuField::AllUnarchived(data->archive);
924 return B_OK;
927 case PERFORM_CODE_ALL_ARCHIVED:
929 perform_data_all_archived* data
930 = (perform_data_all_archived*)_data;
931 data->return_value = BMenuField::AllArchived(data->archive);
932 return B_OK;
936 return BView::Perform(code, _data);
940 void
941 BMenuField::LayoutInvalidated(bool descendants)
943 CALLED();
945 fLayoutData->valid = false;
949 void
950 BMenuField::DoLayout()
952 // Bail out, if we shan't do layout.
953 if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
954 return;
956 CALLED();
958 // If the user set a layout, we let the base class version call its
959 // hook.
960 if (GetLayout() != NULL) {
961 BView::DoLayout();
962 return;
965 _ValidateLayoutData();
967 // validate current size
968 BSize size(Bounds().Size());
969 if (size.width < fLayoutData->min.width)
970 size.width = fLayoutData->min.width;
972 if (size.height < fLayoutData->min.height)
973 size.height = fLayoutData->min.height;
975 // divider
976 float divider = 0;
977 if (fLayoutData->label_layout_item != NULL
978 && fLayoutData->menu_bar_layout_item != NULL
979 && fLayoutData->label_layout_item->Frame().IsValid()
980 && fLayoutData->menu_bar_layout_item->Frame().IsValid()) {
981 // We have valid layout items, they define the divider location.
982 divider = fabs(fLayoutData->menu_bar_layout_item->Frame().left
983 - fLayoutData->label_layout_item->Frame().left);
984 } else if (fLayoutData->label_width > 0) {
985 divider = fLayoutData->label_width
986 + be_control_look->DefaultLabelSpacing();
989 // menu bar
990 BRect dirty(fMenuBar->Frame());
991 BRect menuBarFrame(divider + kVMargin, kVMargin, size.width - kVMargin,
992 size.height - kVMargin);
994 // place the menu bar and set the divider
995 BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame);
997 fDivider = divider;
999 // invalidate dirty region
1000 dirty = dirty | fMenuBar->Frame();
1001 dirty.InsetBy(-kVMargin, -kVMargin);
1003 Invalidate(dirty);
1007 void BMenuField::_ReservedMenuField1() {}
1008 void BMenuField::_ReservedMenuField2() {}
1009 void BMenuField::_ReservedMenuField3() {}
1012 void
1013 BMenuField::InitObject(const char* label)
1015 CALLED();
1017 fLabel = NULL;
1018 fMenu = NULL;
1019 fMenuBar = NULL;
1020 fAlign = B_ALIGN_LEFT;
1021 fEnabled = true;
1022 fFixedSizeMB = false;
1023 fMenuTaskID = -1;
1024 fLayoutData = new LayoutData;
1025 fMouseDownFilter = new MouseDownFilter();
1027 SetLabel(label);
1029 if (label)
1030 fDivider = floorf(Frame().Width() / 2.0f);
1031 else
1032 fDivider = 0;
1036 void
1037 BMenuField::InitObject2()
1039 CALLED();
1041 if (!fFixedSizeMB) {
1042 float height;
1043 fMenuBar->GetPreferredSize(NULL, &height);
1044 fMenuBar->ResizeTo(_MenuBarWidth(), height);
1047 TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n",
1048 fMenuBar->Frame().left, fMenuBar->Frame().top,
1049 fMenuBar->Frame().right, fMenuBar->Frame().bottom,
1050 fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
1052 fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN));
1056 void
1057 BMenuField::_DrawLabel(BRect updateRect)
1059 CALLED();
1061 _ValidateLayoutData();
1063 const char* label = Label();
1064 if (label == NULL)
1065 return;
1067 BRect rect;
1068 if (fLayoutData->label_layout_item != NULL)
1069 rect = fLayoutData->label_layout_item->FrameInParent();
1070 else {
1071 rect = Bounds();
1072 rect.right = fDivider;
1075 if (!rect.IsValid() || !rect.Intersects(updateRect))
1076 return;
1078 uint32 flags = 0;
1079 if (!IsEnabled())
1080 flags |= BControlLook::B_DISABLED;
1082 // save the current low color
1083 PushState();
1084 rgb_color textColor;
1086 BPrivate::MenuPrivate menuPrivate(fMenuBar);
1087 if (menuPrivate.State() != MENU_STATE_CLOSED) {
1088 // highlight the background of the label grey (like BeOS R5)
1089 SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
1090 BRect fillRect(rect.InsetByCopy(0, kVMargin));
1091 FillRect(fillRect, B_SOLID_LOW);
1092 textColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR);
1093 } else
1094 textColor = ui_color(B_PANEL_TEXT_COLOR);
1096 be_control_look->DrawLabel(this, label, rect, updateRect, LowColor(), flags,
1097 BAlignment(fAlign, B_ALIGN_MIDDLE), &textColor);
1099 // restore the previous low color
1100 PopState();
1104 void
1105 BMenuField::_DrawMenuBar(BRect updateRect)
1107 CALLED();
1109 BRect rect(fMenuBar->Frame().InsetByCopy(-kVMargin, -kVMargin));
1110 if (!rect.IsValid() || !rect.Intersects(updateRect))
1111 return;
1113 uint32 flags = 0;
1114 if (!IsEnabled())
1115 flags |= BControlLook::B_DISABLED;
1117 if (IsFocus() && Window()->IsActive())
1118 flags |= BControlLook::B_FOCUSED;
1120 be_control_look->DrawMenuFieldFrame(this, rect, updateRect,
1121 fMenuBar->LowColor(), LowColor(), flags);
1125 void
1126 BMenuField::InitMenu(BMenu* menu)
1128 menu->SetFont(be_plain_font);
1130 int32 index = 0;
1131 BMenu* subMenu;
1133 while ((subMenu = menu->SubmenuAt(index++)) != NULL)
1134 InitMenu(subMenu);
1138 /*static*/ int32
1139 BMenuField::_thread_entry(void* arg)
1141 return static_cast<BMenuField*>(arg)->_MenuTask();
1145 int32
1146 BMenuField::_MenuTask()
1148 if (!LockLooper())
1149 return 0;
1151 Invalidate();
1152 UnlockLooper();
1154 bool tracking;
1155 do {
1156 snooze(20000);
1157 if (!LockLooper())
1158 return 0;
1160 tracking = fMenuBar->fTracking;
1162 UnlockLooper();
1163 } while (tracking);
1165 if (LockLooper()) {
1166 Invalidate();
1167 UnlockLooper();
1170 return 0;
1174 void
1175 BMenuField::_UpdateFrame()
1177 CALLED();
1179 if (fLayoutData->label_layout_item == NULL
1180 || fLayoutData->menu_bar_layout_item == NULL) {
1181 return;
1184 BRect labelFrame = fLayoutData->label_layout_item->Frame();
1185 BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame();
1187 if (!labelFrame.IsValid() || !menuFrame.IsValid())
1188 return;
1190 // update divider
1191 fDivider = menuFrame.left - labelFrame.left;
1193 // update our frame
1194 MoveTo(labelFrame.left, labelFrame.top);
1195 BSize oldSize = Bounds().Size();
1196 ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left,
1197 menuFrame.top + menuFrame.Height() - labelFrame.top);
1198 BSize newSize = Bounds().Size();
1200 // If the size changes, ResizeTo() will trigger a relayout, otherwise
1201 // we need to do that explicitly.
1202 if (newSize != oldSize)
1203 Relayout();
1207 void
1208 BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize)
1210 CALLED();
1212 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
1213 fMenuBar = new _BMCMenuBar_(this);
1214 } else {
1215 frame.left = _MenuBarOffset();
1216 frame.top = kVMargin;
1217 frame.right -= kVMargin;
1218 frame.bottom -= kVMargin;
1220 TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n",
1221 frame.left, frame.top, frame.right, frame.bottom,
1222 frame.Width(), frame.Height());
1224 fMenuBar = new _BMCMenuBar_(frame, fixedSize, this);
1227 if (fixedSize) {
1228 // align the menu bar in the full available space
1229 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
1230 B_ALIGN_VERTICAL_UNSET));
1231 } else {
1232 // align the menu bar left in the available space
1233 fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
1234 B_ALIGN_VERTICAL_UNSET));
1237 AddChild(fMenuBar);
1239 _AddMenu(menu);
1241 fMenuBar->SetFont(be_plain_font);
1245 void
1246 BMenuField::_InitMenuBar(const BMessage* archive)
1248 bool fixed;
1249 if (archive->FindBool("be:fixeds", &fixed) == B_OK)
1250 fFixedSizeMB = fixed;
1252 fMenuBar = (BMenuBar*)FindView("_mc_mb_");
1253 if (fMenuBar == NULL) {
1254 _InitMenuBar(new BMenu(""), BRect(0, 0, 100, 15), fFixedSizeMB);
1255 InitObject2();
1256 } else {
1257 fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN));
1258 // this is normally done in InitObject2()
1261 _AddMenu(fMenuBar->SubmenuAt(0));
1263 bool disable;
1264 if (archive->FindBool("_disable", &disable) == B_OK)
1265 SetEnabled(!disable);
1267 bool dmark = false;
1268 archive->FindBool("be:dmark", &dmark);
1269 _BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar);
1270 if (menuBar != NULL)
1271 menuBar->TogglePopUpMarker(dmark);
1275 void
1276 BMenuField::_AddMenu(BMenu* menu)
1278 if (menu == NULL || fMenuBar == NULL)
1279 return;
1281 fMenu = menu;
1282 InitMenu(menu);
1284 BMenuItem* item = NULL;
1285 if (!menu->IsRadioMode() || (item = menu->FindMarked()) == NULL) {
1286 // find the first enabled non-seperator item
1287 int32 itemCount = menu->CountItems();
1288 for (int32 i = 0; i < itemCount; i++) {
1289 item = menu->ItemAt((int32)i);
1290 if (item == NULL || !item->IsEnabled()
1291 || dynamic_cast<BSeparatorItem*>(item) != NULL) {
1292 item = NULL;
1293 continue;
1295 break;
1299 if (item == NULL) {
1300 fMenuBar->AddItem(menu);
1301 return;
1304 // build an empty copy of item
1306 BMessage data;
1307 status_t result = item->Archive(&data, false);
1308 if (result != B_OK) {
1309 fMenuBar->AddItem(menu);
1310 return;
1313 BArchivable* object = instantiate_object(&data);
1314 if (object == NULL) {
1315 fMenuBar->AddItem(menu);
1316 return;
1319 BMenuItem* newItem = static_cast<BMenuItem*>(object);
1321 // unset parameters
1322 BPrivate::MenuItemPrivate newMenuItemPrivate(newItem);
1323 newMenuItemPrivate.Uninstall();
1325 // set the menu
1326 newMenuItemPrivate.SetSubmenu(menu);
1327 fMenuBar->AddItem(newItem);
1331 void
1332 BMenuField::_ValidateLayoutData()
1334 CALLED();
1336 if (fLayoutData->valid)
1337 return;
1339 // cache font height
1340 font_height& fh = fLayoutData->font_info;
1341 GetFontHeight(&fh);
1343 const char* label = Label();
1344 if (label != NULL) {
1345 fLayoutData->label_width = ceilf(StringWidth(label));
1346 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
1347 } else {
1348 fLayoutData->label_width = 0;
1349 fLayoutData->label_height = 0;
1352 // compute the minimal divider
1353 float divider = 0;
1354 if (fLayoutData->label_width > 0) {
1355 divider = fLayoutData->label_width
1356 + be_control_look->DefaultLabelSpacing();
1359 // If we shan't do real layout, we let the current divider take influence.
1360 if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1361 divider = std::max(divider, fDivider);
1363 // get the minimal (== preferred) menu bar size
1364 // TODO: BMenu::MinSize() is using the ResizeMode() to decide the
1365 // minimum width. If the mode is B_FOLLOW_LEFT_RIGHT, it will use the
1366 // parent's frame width or window's frame width. So at least the returned
1367 // size is wrong, but apparantly it doesn't have much bad effect.
1368 fLayoutData->menu_bar_min = fMenuBar->MinSize();
1370 TRACE("menu bar min width: %.2f\n", fLayoutData->menu_bar_min.width);
1372 // compute our minimal (== preferred) size
1373 BSize min(fLayoutData->menu_bar_min);
1374 min.width += 2 * kVMargin;
1375 min.height += 2 * kVMargin;
1377 if (divider > 0)
1378 min.width += divider;
1380 if (fLayoutData->label_height > min.height)
1381 min.height = fLayoutData->label_height;
1383 fLayoutData->min = min;
1385 fLayoutData->valid = true;
1386 ResetLayoutInvalidation();
1388 TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
1392 float
1393 BMenuField::_MenuBarOffset() const
1395 return std::max(fDivider + kVMargin, kVMargin);
1399 float
1400 BMenuField::_MenuBarWidth() const
1402 return Bounds().Width() - (_MenuBarOffset() + kVMargin);
1406 void
1407 BMenuField::_DoneTracking(BPoint point)
1409 Window()->RemoveCommonFilter(fMouseDownFilter);
1413 void
1414 BMenuField::_Track(BPoint point, uint32)
1419 // #pragma mark - BMenuField::LabelLayoutItem
1422 BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent)
1424 fParent(parent),
1425 fFrame()
1430 BMenuField::LabelLayoutItem::LabelLayoutItem(BMessage* from)
1432 BAbstractLayoutItem(from),
1433 fParent(NULL),
1434 fFrame()
1436 from->FindRect(kFrameField, &fFrame);
1440 BRect
1441 BMenuField::LabelLayoutItem::FrameInParent() const
1443 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1447 bool
1448 BMenuField::LabelLayoutItem::IsVisible()
1450 return !fParent->IsHidden(fParent);
1454 void
1455 BMenuField::LabelLayoutItem::SetVisible(bool visible)
1457 // not allowed
1461 BRect
1462 BMenuField::LabelLayoutItem::Frame()
1464 return fFrame;
1468 void
1469 BMenuField::LabelLayoutItem::SetFrame(BRect frame)
1471 fFrame = frame;
1472 fParent->_UpdateFrame();
1476 void
1477 BMenuField::LabelLayoutItem::SetParent(BMenuField* parent)
1479 fParent = parent;
1483 BView*
1484 BMenuField::LabelLayoutItem::View()
1486 return fParent;
1490 BSize
1491 BMenuField::LabelLayoutItem::BaseMinSize()
1493 fParent->_ValidateLayoutData();
1495 if (fParent->Label() == NULL)
1496 return BSize(-1, -1);
1498 return BSize(fParent->fLayoutData->label_width
1499 + be_control_look->DefaultLabelSpacing(),
1500 fParent->fLayoutData->label_height);
1504 BSize
1505 BMenuField::LabelLayoutItem::BaseMaxSize()
1507 return BaseMinSize();
1511 BSize
1512 BMenuField::LabelLayoutItem::BasePreferredSize()
1514 return BaseMinSize();
1518 BAlignment
1519 BMenuField::LabelLayoutItem::BaseAlignment()
1521 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1525 status_t
1526 BMenuField::LabelLayoutItem::Archive(BMessage* into, bool deep) const
1528 BArchiver archiver(into);
1529 status_t err = BAbstractLayoutItem::Archive(into, deep);
1531 if (err == B_OK)
1532 err = into->AddRect(kFrameField, fFrame);
1534 return archiver.Finish(err);
1538 BArchivable*
1539 BMenuField::LabelLayoutItem::Instantiate(BMessage* from)
1541 if (validate_instantiation(from, "BMenuField::LabelLayoutItem"))
1542 return new LabelLayoutItem(from);
1544 return NULL;
1548 // #pragma mark - BMenuField::MenuBarLayoutItem
1551 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* parent)
1553 fParent(parent),
1554 fFrame()
1556 // by default the part right of the divider shall have an unlimited maximum
1557 // width
1558 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1562 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMessage* from)
1564 BAbstractLayoutItem(from),
1565 fParent(NULL),
1566 fFrame()
1568 from->FindRect(kFrameField, &fFrame);
1572 BRect
1573 BMenuField::MenuBarLayoutItem::FrameInParent() const
1575 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
1579 bool
1580 BMenuField::MenuBarLayoutItem::IsVisible()
1582 return !fParent->IsHidden(fParent);
1586 void
1587 BMenuField::MenuBarLayoutItem::SetVisible(bool visible)
1589 // not allowed
1593 BRect
1594 BMenuField::MenuBarLayoutItem::Frame()
1596 return fFrame;
1600 void
1601 BMenuField::MenuBarLayoutItem::SetFrame(BRect frame)
1603 fFrame = frame;
1604 fParent->_UpdateFrame();
1608 void
1609 BMenuField::MenuBarLayoutItem::SetParent(BMenuField* parent)
1611 fParent = parent;
1615 BView*
1616 BMenuField::MenuBarLayoutItem::View()
1618 return fParent;
1622 BSize
1623 BMenuField::MenuBarLayoutItem::BaseMinSize()
1625 fParent->_ValidateLayoutData();
1627 BSize size = fParent->fLayoutData->menu_bar_min;
1628 size.width += 2 * kVMargin;
1629 size.height += 2 * kVMargin;
1631 return size;
1635 BSize
1636 BMenuField::MenuBarLayoutItem::BaseMaxSize()
1638 BSize size(BaseMinSize());
1639 size.width = B_SIZE_UNLIMITED;
1641 return size;
1645 BSize
1646 BMenuField::MenuBarLayoutItem::BasePreferredSize()
1648 return BaseMinSize();
1652 BAlignment
1653 BMenuField::MenuBarLayoutItem::BaseAlignment()
1655 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1659 status_t
1660 BMenuField::MenuBarLayoutItem::Archive(BMessage* into, bool deep) const
1662 BArchiver archiver(into);
1663 status_t err = BAbstractLayoutItem::Archive(into, deep);
1665 if (err == B_OK)
1666 err = into->AddRect(kFrameField, fFrame);
1668 return archiver.Finish(err);
1672 BArchivable*
1673 BMenuField::MenuBarLayoutItem::Instantiate(BMessage* from)
1675 if (validate_instantiation(from, "BMenuField::MenuBarLayoutItem"))
1676 return new MenuBarLayoutItem(from);
1677 return NULL;
1681 extern "C" void
1682 B_IF_GCC_2(InvalidateLayout__10BMenuFieldb, _ZN10BMenuField16InvalidateLayoutEb)(
1683 BMenuField* field, bool descendants)
1685 perform_data_layout_invalidated data;
1686 data.descendants = descendants;
1688 field->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);