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