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