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