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