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