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