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