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