xref: /haiku/src/kits/interface/MenuField.cpp (revision b028e77473189065f2baefc6f5e10d451cf591e2)
1 /*
2  * Copyright 2001-2006, 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 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <AbstractLayoutItem.h>
15 #include <LayoutUtils.h>
16 #include <MenuBar.h>
17 #include <MenuField.h>
18 #include <Message.h>
19 #include <BMCPrivate.h>
20 #include <Window.h>
21 
22 
23 class BMenuField::LabelLayoutItem : public BAbstractLayoutItem {
24 public:
25 								LabelLayoutItem(BMenuField* parent);
26 
27 	virtual	bool				IsVisible();
28 	virtual	void				SetVisible(bool visible);
29 
30 	virtual	BRect				Frame();
31 	virtual	void				SetFrame(BRect frame);
32 
33 	virtual	BView*				View();
34 
35 	virtual	BSize				BaseMinSize();
36 	virtual	BSize				BaseMaxSize();
37 	virtual	BSize				BasePreferredSize();
38 	virtual	BAlignment			BaseAlignment();
39 
40 private:
41 			BMenuField*			fParent;
42 			BRect				fFrame;
43 };
44 
45 
46 class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem {
47 public:
48 								MenuBarLayoutItem(BMenuField* parent);
49 
50 	virtual	bool				IsVisible();
51 	virtual	void				SetVisible(bool visible);
52 
53 	virtual	BRect				Frame();
54 	virtual	void				SetFrame(BRect frame);
55 
56 	virtual	BView*				View();
57 
58 	virtual	BSize				BaseMinSize();
59 	virtual	BSize				BaseMaxSize();
60 	virtual	BSize				BasePreferredSize();
61 	virtual	BAlignment			BaseAlignment();
62 
63 private:
64 			BMenuField*			fParent;
65 			BRect				fFrame;
66 };
67 
68 
69 struct BMenuField::LayoutData {
70 	LayoutData()
71 		: label_layout_item(NULL),
72 		  menu_bar_layout_item(NULL),
73 		  previous_height(-1),
74 		  valid(false)
75 	{
76 	}
77 
78 	LabelLayoutItem*	label_layout_item;
79 	MenuBarLayoutItem*	menu_bar_layout_item;
80 	float				previous_height;	// used in FrameResized() for
81 											// invalidation
82 	font_height			font_info;
83 	float				label_width;
84 	float				label_height;
85 	BSize				min;
86 	BSize				menu_bar_min;
87 	bool				valid;
88 };
89 
90 
91 // #pragma mark -
92 
93 
94 static float kVMargin = 2.0f;
95 
96 
97 BMenuField::BMenuField(BRect frame, const char *name, const char *label,
98 	BMenu *menu, uint32 resize, uint32 flags)
99 	: BView(frame, name, resize, flags)
100 {
101 	InitObject(label);
102 
103 	frame.OffsetTo(B_ORIGIN);
104 	_InitMenuBar(menu, frame, false);
105 
106 	InitObject2();
107 }
108 
109 
110 BMenuField::BMenuField(BRect frame, const char *name, const char *label,
111 	BMenu *menu, bool fixedSize, uint32 resize, uint32 flags)
112 	: BView(frame, name, resize, flags)
113 {
114 	InitObject(label);
115 
116 	fFixedSizeMB = fixedSize;
117 
118 	frame.OffsetTo(B_ORIGIN);
119 	_InitMenuBar(menu, frame, fixedSize);
120 
121 	InitObject2();
122 }
123 
124 
125 BMenuField::BMenuField(const char* name, const char* label, BMenu* menu,
126 					   BMessage* message, uint32 flags)
127 	: BView(BRect(0, 0, -1, -1), name, B_FOLLOW_NONE,
128 			flags | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT)
129 {
130 	InitObject(label);
131 
132 	_InitMenuBar(menu, BRect(0, 0, 100, 15), false);
133 
134 	InitObject2();
135 }
136 
137 
138 BMenuField::BMenuField(const char* label,
139 					   BMenu* menu, BMessage* message)
140 	: BView(BRect(0, 0, -1, -1), NULL, B_FOLLOW_NONE,
141 			B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT)
142 {
143 	InitObject(label);
144 
145 	_InitMenuBar(menu, BRect(0, 0, 100, 15), false);
146 
147 	InitObject2();
148 }
149 
150 
151 BMenuField::BMenuField(BMessage *data)
152 	: BView(data)
153 {
154 	const char *label = NULL;
155 	data->FindString("_label", &label);
156 
157 	InitObject(label);
158 
159 	fMenuBar = (BMenuBar*)FindView("_mc_mb_");
160 	fMenu = fMenuBar->SubmenuAt(0);
161 
162 	InitObject2();
163 
164 	bool disable;
165 	if (data->FindBool("_disable", &disable) == B_OK)
166 		SetEnabled(!disable);
167 
168 	int32 align;
169 	data->FindInt32("_align", &align);
170 		SetAlignment((alignment)align);
171 
172 	data->FindFloat("_divide", &fDivider);
173 
174 	bool fixed;
175 	if (data->FindBool("be:fixeds", &fixed) == B_OK)
176 		fFixedSizeMB = fixed;
177 
178 	bool dmark = false;
179 	data->FindBool("be:dmark", &dmark);
180 	if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) {
181 		menuBar->TogglePopUpMarker(dmark);
182 	}
183 }
184 
185 
186 BMenuField::~BMenuField()
187 {
188 	free(fLabel);
189 
190 	status_t dummy;
191 	if (fMenuTaskID >= 0)
192 		wait_for_thread(fMenuTaskID, &dummy);
193 
194 	delete fLayoutData;
195 }
196 
197 
198 BArchivable *
199 BMenuField::Instantiate(BMessage *data)
200 {
201 	if (validate_instantiation(data, "BMenuField"))
202 		return new BMenuField(data);
203 
204 	return NULL;
205 }
206 
207 
208 status_t
209 BMenuField::Archive(BMessage *data, bool deep) const
210 {
211 	status_t ret = BView::Archive(data, deep);
212 
213 	if (ret == B_OK && Label())
214 		ret = data->AddString("_label", Label());
215 
216 	if (ret == B_OK && !IsEnabled())
217 		ret = data->AddBool("_disable", true);
218 
219 	if (ret == B_OK)
220 		ret = data->AddInt32("_align", Alignment());
221 	if (ret == B_OK)
222 		ret = data->AddFloat("_divide", Divider());
223 
224 	if (ret == B_OK && fFixedSizeMB)
225 		ret = data->AddBool("be:fixeds", true);
226 
227 	bool dmark = false;
228 	if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) {
229 		dmark = menuBar->IsPopUpMarkerShown();
230 	}
231 	data->AddBool("be:dmark", dmark);
232 
233 	return ret;
234 }
235 
236 
237 void
238 BMenuField::Draw(BRect update)
239 {
240 	BRect bounds(Bounds());
241 	bool active = false;
242 
243 	if (IsFocus())
244 		active = Window()->IsActive();
245 
246 	DrawLabel(bounds, update);
247 
248 	BRect frame(fMenuBar->Frame());
249 
250 	if (frame.InsetByCopy(-kVMargin, -kVMargin).Intersects(update)) {
251 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
252 		StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f),
253 			BPoint(frame.left - 1.0f, frame.bottom - 1.0f));
254 		StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f),
255 			BPoint(frame.right - 1.0f, frame.top - 1.0f));
256 
257 		StrokeLine(BPoint(frame.left + 1.0f, frame.bottom + 1.0f),
258 			BPoint(frame.right + 1.0f, frame.bottom + 1.0f));
259 		StrokeLine(BPoint(frame.right + 1.0f, frame.top + 1.0f));
260 
261 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_4_TINT));
262 		StrokeLine(BPoint(frame.left - 1.0f, frame.bottom),
263 			BPoint(frame.left - 1.0f, frame.bottom));
264 		StrokeLine(BPoint(frame.right, frame.top - 1.0f),
265 			BPoint(frame.right, frame.top - 1.0f));
266 	}
267 
268 	if (active || fTransition) {
269 		SetHighColor(active ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) :
270 			ViewColor());
271 		StrokeRect(frame.InsetByCopy(-kVMargin, -kVMargin));
272 
273 		fTransition = false;
274 	}
275 }
276 
277 
278 void
279 BMenuField::AttachedToWindow()
280 {
281 	if (Parent()) {
282 		SetViewColor(Parent()->ViewColor());
283 		SetLowColor(Parent()->ViewColor());
284 	}
285 }
286 
287 
288 void
289 BMenuField::AllAttached()
290 {
291 	ResizeTo(Bounds().Width(),
292 		fMenuBar->Bounds().Height() + kVMargin + kVMargin);
293 }
294 
295 
296 void
297 BMenuField::MouseDown(BPoint where)
298 {
299 	if (!fMenuBar->Frame().Contains(where))
300 		return;
301 
302 	BRect bounds = fMenuBar->ConvertFromParent(Bounds());
303 
304 	fMenuBar->StartMenuBar(0, false, true, &bounds);
305 
306 	fMenuTaskID = spawn_thread((thread_func)MenuTask, "_m_task_", B_NORMAL_PRIORITY, this);
307 	if (fMenuTaskID)
308 		resume_thread(fMenuTaskID);
309 }
310 
311 
312 void
313 BMenuField::KeyDown(const char *bytes, int32 numBytes)
314 {
315 	switch (bytes[0]) {
316 		case B_SPACE:
317 		case B_RIGHT_ARROW:
318 		case B_DOWN_ARROW:
319 		{
320 			if (!IsEnabled())
321 				break;
322 
323 			BRect bounds = fMenuBar->ConvertFromParent(Bounds());
324 
325 			fMenuBar->StartMenuBar(0, true, true, &bounds);
326 
327 			fSelected = true;
328 			fTransition = true;
329 
330 			bounds = Bounds();
331 			bounds.right = fDivider;
332 
333 			Invalidate(bounds);
334 		}
335 
336 		default:
337 			BView::KeyDown(bytes, numBytes);
338 	}
339 }
340 
341 
342 void
343 BMenuField::MakeFocus(bool state)
344 {
345 	if (IsFocus() == state)
346 		return;
347 
348 	BView::MakeFocus(state);
349 
350 	if (Window())
351 		Invalidate(); // TODO: use fLayoutData->label_width
352 }
353 
354 
355 void
356 BMenuField::MessageReceived(BMessage *msg)
357 {
358 	BView::MessageReceived(msg);
359 }
360 
361 
362 void
363 BMenuField::WindowActivated(bool state)
364 {
365 	BView::WindowActivated(state);
366 
367 	if (IsFocus())
368 		Invalidate();
369 }
370 
371 
372 void
373 BMenuField::MouseUp(BPoint point)
374 {
375 	BView::MouseUp(point);
376 }
377 
378 
379 void
380 BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage *message)
381 {
382 	BView::MouseMoved(point, code, message);
383 }
384 
385 
386 void
387 BMenuField::DetachedFromWindow()
388 {
389 	BView::DetachedFromWindow();
390 }
391 
392 
393 void
394 BMenuField::AllDetached()
395 {
396 	BView::AllDetached();
397 }
398 
399 
400 void
401 BMenuField::FrameMoved(BPoint newPosition)
402 {
403 	BView::FrameMoved(newPosition);
404 }
405 
406 
407 void
408 BMenuField::FrameResized(float newWidth, float newHeight)
409 {
410 	BView::FrameResized(newWidth, newHeight);
411 
412 	if (newHeight != fLayoutData->previous_height && Label()) {
413 		// The height changed, which means the label has to move and we
414 		// probably also invalidate a part of the borders around the menu bar.
415 		// So don't be shy and invalidate the whole thing.
416 		Invalidate();
417 	}
418 
419 	fLayoutData->previous_height = newHeight;
420 }
421 
422 
423 BMenu *
424 BMenuField::Menu() const
425 {
426 	return fMenu;
427 }
428 
429 
430 BMenuBar *
431 BMenuField::MenuBar() const
432 {
433 	return fMenuBar;
434 }
435 
436 
437 BMenuItem *
438 BMenuField::MenuItem() const
439 {
440 	return fMenuBar->ItemAt(0);
441 }
442 
443 
444 void
445 BMenuField::SetLabel(const char *label)
446 {
447 	if (fLabel) {
448 		if (label && strcmp(fLabel, label) == 0)
449 			return;
450 
451 		free(fLabel);
452 	}
453 
454 	fLabel = strdup(label);
455 
456 	if (Window())
457 		Invalidate();
458 
459 	InvalidateLayout();
460 }
461 
462 
463 const char *
464 BMenuField::Label() const
465 {
466 	return fLabel;
467 }
468 
469 
470 void
471 BMenuField::SetEnabled(bool on)
472 {
473 	if (fEnabled == on)
474 		return;
475 
476 	fEnabled = on;
477 	fMenuBar->SetEnabled(on);
478 
479 	if (Window()) {
480 		fMenuBar->Invalidate(fMenuBar->Bounds());
481 		Invalidate(Bounds());
482 	}
483 }
484 
485 
486 bool
487 BMenuField::IsEnabled() const
488 {
489 	return fEnabled;
490 }
491 
492 
493 void
494 BMenuField::SetAlignment(alignment label)
495 {
496 	fAlign = label;
497 }
498 
499 
500 alignment
501 BMenuField::Alignment() const
502 {
503 	return fAlign;
504 }
505 
506 
507 void
508 BMenuField::SetDivider(float divider)
509 {
510 	divider = floorf(divider + 0.5);
511 
512 	float dx = fDivider - divider;
513 
514 	if (dx == 0.0f)
515 		return;
516 
517 	fDivider = divider;
518 
519 	if (Flags() & B_SUPPORTS_LAYOUT) {
520 		// We should never get here, since layout support means, we also
521 		// layout the divider, and don't use this method at all.
522 		Relayout();
523 	} else {
524 		BRect dirty(fMenuBar->Frame());
525 
526 		fMenuBar->MoveTo(fDivider + 1, kVMargin);
527 
528 		if (fFixedSizeMB) {
529 			fMenuBar->ResizeTo(Bounds().Width() - fDivider + 1 - 2,
530 							   dirty.Height());
531 		}
532 
533 		dirty = dirty | fMenuBar->Frame();
534 		dirty.InsetBy(-kVMargin, -kVMargin);
535 
536 		Invalidate(dirty);
537 	}
538 }
539 
540 
541 float
542 BMenuField::Divider() const
543 {
544 	return fDivider;
545 }
546 
547 
548 void
549 BMenuField::ShowPopUpMarker()
550 {
551 	if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) {
552 		menuBar->TogglePopUpMarker(true);
553 		menuBar->Invalidate();
554 	}
555 }
556 
557 
558 void
559 BMenuField::HidePopUpMarker()
560 {
561 	if (_BMCMenuBar_ *menuBar = dynamic_cast<_BMCMenuBar_ *>(fMenuBar)) {
562 		menuBar->TogglePopUpMarker(false);
563 		menuBar->Invalidate();
564 	}
565 }
566 
567 
568 BHandler *
569 BMenuField::ResolveSpecifier(BMessage *message, int32 index,
570 	BMessage *specifier, int32 form, const char *property)
571 {
572 	return BView::ResolveSpecifier(message, index, specifier, form, property);
573 }
574 
575 
576 status_t
577 BMenuField::GetSupportedSuites(BMessage *data)
578 {
579 	return BView::GetSupportedSuites(data);
580 }
581 
582 
583 void
584 BMenuField::ResizeToPreferred()
585 {
586 	fMenuBar->ResizeToPreferred();
587 
588 	BView::ResizeToPreferred();
589 }
590 
591 
592 void
593 BMenuField::GetPreferredSize(float *_width, float *_height)
594 {
595 	_ValidateLayoutData();
596 
597 	if (_width)
598 		*_width = fLayoutData->min.width;
599 
600 	if (_height)
601 		*_height = fLayoutData->min.height;
602 }
603 
604 
605 BSize
606 BMenuField::MinSize()
607 {
608 	_ValidateLayoutData();
609 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
610 }
611 
612 
613 BSize
614 BMenuField::MaxSize()
615 {
616 	_ValidateLayoutData();
617 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), fLayoutData->min);
618 }
619 
620 
621 BSize
622 BMenuField::PreferredSize()
623 {
624 	_ValidateLayoutData();
625 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
626 }
627 
628 
629 void
630 BMenuField::InvalidateLayout(bool descendants)
631 {
632 	fLayoutData->valid = false;
633 
634 	BView::InvalidateLayout(descendants);
635 }
636 
637 
638 BLayoutItem*
639 BMenuField::CreateLabelLayoutItem()
640 {
641 	if (!fLayoutData->label_layout_item)
642 		fLayoutData->label_layout_item = new LabelLayoutItem(this);
643 	return fLayoutData->label_layout_item;
644 }
645 
646 
647 BLayoutItem*
648 BMenuField::CreateMenuBarLayoutItem()
649 {
650 	if (!fLayoutData->menu_bar_layout_item)
651 		fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this);
652 	return fLayoutData->menu_bar_layout_item;
653 }
654 
655 
656 status_t
657 BMenuField::Perform(perform_code d, void *arg)
658 {
659 	return BView::Perform(d, arg);
660 }
661 
662 
663 void
664 BMenuField::DoLayout()
665 {
666 	// Bail out, if we shan't do layout.
667 	if (!(Flags() & B_SUPPORTS_LAYOUT))
668 		return;
669 
670 	// If the user set a layout, we let the base class version call its
671 	// hook.
672 	if (GetLayout()) {
673 		BView::DoLayout();
674 		return;
675 	}
676 
677 	_ValidateLayoutData();
678 
679 	// validate current size
680 	BSize size(Bounds().Size());
681 	if (size.width < fLayoutData->min.width)
682 		size.width = fLayoutData->min.width;
683 	if (size.height < fLayoutData->min.height)
684 		size.height = fLayoutData->min.height;
685 
686 	// divider
687 	float divider = 0;
688 	if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) {
689 		// We have layout items. They define the divider location.
690 		divider = fLayoutData->menu_bar_layout_item->Frame().left
691 			- fLayoutData->label_layout_item->Frame().left;
692 	} else {
693 		if (fLayoutData->label_width > 0)
694 			divider = fLayoutData->label_width + 5;
695 	}
696 
697 	// menu bar
698 	BRect dirty(fMenuBar->Frame());
699 	BRect menuBarFrame(divider + 1, kVMargin, size.width - 2,
700 		size.height - kVMargin);
701 
702 	// place the menu bar and set the divider
703 	BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame);
704 
705 	fDivider = divider;
706 
707 	// invalidate dirty region
708 	dirty = dirty | fMenuBar->Frame();
709 	dirty.InsetBy(-kVMargin, -kVMargin);
710 
711 	Invalidate(dirty);
712 }
713 
714 
715 void BMenuField::_ReservedMenuField1() {}
716 void BMenuField::_ReservedMenuField2() {}
717 void BMenuField::_ReservedMenuField3() {}
718 
719 
720 BMenuField &
721 BMenuField::operator=(const BMenuField &)
722 {
723 	return *this;
724 }
725 
726 
727 void
728 BMenuField::InitObject(const char *label)
729 {
730 	fLabel = NULL;
731 	fMenu = NULL;
732 	fMenuBar = NULL;
733 	fAlign = B_ALIGN_LEFT;
734 	fEnabled = true;
735 	fSelected = false;
736 	fTransition = false;
737 	fFixedSizeMB = false;
738 	fMenuTaskID = -1;
739 	fLayoutData = new LayoutData;
740 
741 	SetLabel(label);
742 
743 	if (label)
744 		fDivider = (float)floor(Frame().Width() / 2.0f);
745 	else
746 		fDivider = 0;
747 
748 	// default to unlimited maximum width
749 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
750 }
751 
752 
753 void
754 BMenuField::InitObject2()
755 {
756 	font_height fontHeight;
757 	GetFontHeight(&fontHeight);
758 
759 	// TODO: fix this calculation
760 	float height = floorf(fontHeight.ascent + fontHeight.descent
761 		+ fontHeight.leading) + 7;
762 
763 	fMenuBar->ResizeTo(Bounds().Width() - fDivider, height);
764 	fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN));
765 }
766 
767 
768 void
769 BMenuField::DrawLabel(BRect bounds, BRect update)
770 {
771 	_ValidateLayoutData();
772 	font_height& fh = fLayoutData->font_info;
773 
774 	if (Label()) {
775 		SetLowColor(ViewColor());
776 
777 		// horizontal alignment
778 		float x;
779 		switch (fAlign) {
780 			case B_ALIGN_RIGHT:
781 				x = fDivider - fLayoutData->label_width - 3.0f;
782 				break;
783 
784 			case B_ALIGN_CENTER:
785 				x = fDivider - fLayoutData->label_width / 2.0f;
786 				break;
787 
788 			default:
789 				x = 3.0f;
790 				break;
791 		}
792 
793 		// vertical alignment
794 		float y = Bounds().top
795 			+ (Bounds().Height() + 1 - fh.ascent - fh.descent) / 2
796 			+ fh.ascent;
797 		y = floor(y + 0.5);
798 
799 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
800 			IsEnabled() ? B_DARKEN_MAX_TINT : B_DISABLED_LABEL_TINT));
801 		DrawString(Label(), BPoint(x, y));
802 	}
803 }
804 
805 
806 void
807 BMenuField::InitMenu(BMenu *menu)
808 {
809 	menu->SetFont(be_plain_font);
810 
811 	int32 index = 0;
812 	BMenu *subMenu;
813 
814 	while ((subMenu = menu->SubmenuAt(index++)) != NULL)
815 		InitMenu(subMenu);
816 }
817 
818 
819 long
820 BMenuField::MenuTask(void *arg)
821 {
822 	BMenuField *menuField = static_cast<BMenuField *>(arg);
823 
824 	if (!menuField->LockLooper())
825 		return 0;
826 
827 	menuField->fSelected = true;
828 	menuField->fTransition = true;
829 	menuField->Invalidate();
830 	menuField->UnlockLooper();
831 
832 	bool tracking;
833 	do {
834 		snooze(20000);
835 		if (!menuField->LockLooper())
836 			return 0;
837 
838 		tracking = menuField->fMenuBar->fTracking;
839 
840 		menuField->UnlockLooper();
841 	} while (tracking);
842 
843 	if (menuField->LockLooper()) {
844 		menuField->fSelected = false;
845 		menuField->fTransition = true;
846 		menuField->Invalidate();
847 		menuField->UnlockLooper();
848 	}
849 
850 	return 0;
851 }
852 
853 
854 void
855 BMenuField::_UpdateFrame()
856 {
857 	if (fLayoutData->label_layout_item && fLayoutData->menu_bar_layout_item) {
858 		BRect labelFrame = fLayoutData->label_layout_item->Frame();
859 		BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame();
860 
861 		// update divider
862 		fDivider = menuFrame.left - labelFrame.left;
863 
864 		// update our frame
865 		MoveTo(labelFrame.left, labelFrame.top);
866 		BSize oldSize = Bounds().Size();
867 		ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left,
868 			menuFrame.top + menuFrame.Height() - labelFrame.top);
869 		BSize newSize = Bounds().Size();
870 
871 		// If the size changes, ResizeTo() will trigger a relayout, otherwise
872 		// we need to do that explicitly.
873 		if (newSize != oldSize)
874 			Relayout();
875 	}
876 }
877 
878 
879 void
880 BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize)
881 {
882 	fMenu = menu;
883 	InitMenu(menu);
884 
885 	fMenuBar = new _BMCMenuBar_(BRect(frame.left + fDivider + 1,
886 		frame.top + kVMargin, frame.right, frame.bottom - kVMargin),
887 		fixedSize, this);
888 
889 	// by default align the menu bar left in the available space
890 	fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
891 		B_ALIGN_VERTICAL_UNSET));
892 
893 	AddChild(fMenuBar);
894 	fMenuBar->AddItem(menu);
895 
896 	fMenuBar->SetFont(be_plain_font);
897 }
898 
899 
900 void
901 BMenuField::_ValidateLayoutData()
902 {
903 	if (fLayoutData->valid)
904 		return;
905 
906 	// cache font height
907 	font_height& fh = fLayoutData->font_info;
908 	GetFontHeight(&fh);
909 
910 	fLayoutData->label_width = (Label() ? ceilf(StringWidth(Label())) : 0);
911 	fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
912 
913 	// compute the minimal divider
914 	float divider = 0;
915 	if (fLayoutData->label_width > 0)
916 		divider = fLayoutData->label_width + 5;
917 
918 	// If we shan't do real layout, we let the current divider take influence.
919 	if (!(Flags() & B_SUPPORTS_LAYOUT))
920 		divider = max_c(divider, fDivider);
921 
922 	// get the minimal (== preferred) menu bar size
923 	fLayoutData->menu_bar_min = fMenuBar->MinSize();
924 
925 	// compute our minimal (== preferred) size
926 	// TODO: The layout is a bit broken. A one pixel wide border is draw around
927 	// the menu bar to give it it's look. When the view has the focus,
928 	// additionally a one pixel wide blue frame is drawn around it. In order
929 	// to be able to easily visually align the menu bar with the text view of
930 	// a text control, the divider must ignore the focus frame, though. Hence
931 	// we add one less pixel to our width.
932 	BSize min(fLayoutData->menu_bar_min);
933 	min.width += 2 * kVMargin - 1;
934 	min.height += 2 * kVMargin;
935 
936 	if (fLayoutData->label_width > 0)
937 		min.width += fLayoutData->label_width + 5;
938 	if (fLayoutData->label_height > min.height)
939 		min.height = fLayoutData->label_height;
940 
941 	fLayoutData->min = min;
942 
943 	fLayoutData->valid = true;
944 }
945 
946 
947 // #pragma mark -
948 
949 
950 BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent)
951 	: fParent(parent),
952 	  fFrame()
953 {
954 }
955 
956 
957 bool
958 BMenuField::LabelLayoutItem::IsVisible()
959 {
960 	return !fParent->IsHidden(fParent);
961 }
962 
963 
964 void
965 BMenuField::LabelLayoutItem::SetVisible(bool visible)
966 {
967 	// not allowed
968 }
969 
970 
971 BRect
972 BMenuField::LabelLayoutItem::Frame()
973 {
974 	return fFrame;
975 }
976 
977 
978 void
979 BMenuField::LabelLayoutItem::SetFrame(BRect frame)
980 {
981 	fFrame = frame;
982 	fParent->_UpdateFrame();
983 }
984 
985 
986 BView*
987 BMenuField::LabelLayoutItem::View()
988 {
989 	return fParent;
990 }
991 
992 
993 BSize
994 BMenuField::LabelLayoutItem::BaseMinSize()
995 {
996 	fParent->_ValidateLayoutData();
997 
998 	if (!fParent->Label())
999 		return BSize(-1, -1);
1000 
1001 	return BSize(fParent->fLayoutData->label_width + 5,
1002 		fParent->fLayoutData->label_height);
1003 }
1004 
1005 
1006 BSize
1007 BMenuField::LabelLayoutItem::BaseMaxSize()
1008 {
1009 	return BaseMinSize();
1010 }
1011 
1012 
1013 BSize
1014 BMenuField::LabelLayoutItem::BasePreferredSize()
1015 {
1016 	return BaseMinSize();
1017 }
1018 
1019 
1020 BAlignment
1021 BMenuField::LabelLayoutItem::BaseAlignment()
1022 {
1023 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1024 }
1025 
1026 
1027 // #pragma mark -
1028 
1029 
1030 BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* parent)
1031 	: fParent(parent),
1032 	  fFrame()
1033 {
1034 	// by default the part left of the divider shall have an unlimited maximum
1035 	// width
1036 	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
1037 }
1038 
1039 
1040 bool
1041 BMenuField::MenuBarLayoutItem::IsVisible()
1042 {
1043 	return !fParent->IsHidden(fParent);
1044 }
1045 
1046 
1047 void
1048 BMenuField::MenuBarLayoutItem::SetVisible(bool visible)
1049 {
1050 	// not allowed
1051 }
1052 
1053 
1054 BRect
1055 BMenuField::MenuBarLayoutItem::Frame()
1056 {
1057 	return fFrame;
1058 }
1059 
1060 
1061 void
1062 BMenuField::MenuBarLayoutItem::SetFrame(BRect frame)
1063 {
1064 	fFrame = frame;
1065 	fParent->_UpdateFrame();
1066 }
1067 
1068 
1069 BView*
1070 BMenuField::MenuBarLayoutItem::View()
1071 {
1072 	return fParent;
1073 }
1074 
1075 
1076 BSize
1077 BMenuField::MenuBarLayoutItem::BaseMinSize()
1078 {
1079 	fParent->_ValidateLayoutData();
1080 
1081 	// TODO: Cf. the TODO in _ValidateLayoutData().
1082 	BSize size = fParent->fLayoutData->menu_bar_min;
1083 	size.width += 2 * kVMargin - 1;
1084 	size.height += 2 * kVMargin;
1085 
1086 	return size;
1087 }
1088 
1089 
1090 BSize
1091 BMenuField::MenuBarLayoutItem::BaseMaxSize()
1092 {
1093 	return BaseMinSize();
1094 }
1095 
1096 
1097 BSize
1098 BMenuField::MenuBarLayoutItem::BasePreferredSize()
1099 {
1100 	return BaseMinSize();
1101 }
1102 
1103 
1104 BAlignment
1105 BMenuField::MenuBarLayoutItem::BaseAlignment()
1106 {
1107 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
1108 }
1109 
1110