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