1/*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus, superstippi@gmx.de
7 *		Marc Flerackers, mflerackers@androme.be
8 *		John Scipione, jcipione@gmail.com
9 */
10
11
12#include <BMCPrivate.h>
13
14#include <algorithm>
15
16#include <ControlLook.h>
17#include <LayoutUtils.h>
18#include <MenuField.h>
19#include <MenuItem.h>
20#include <Message.h>
21#include <MessageRunner.h>
22#include <Window.h>
23
24
25static const float kPopUpIndicatorWidth = 13.0f;
26
27
28#if __GNUC__ == 2
29
30
31// This is kept only for binary compatibility with BeOS R5. This class was
32// used in their BMenuField implementation and we may come across some archived
33// BMenuField that needs it.
34class _BMCItem_: public BMenuItem {
35public:
36	_BMCItem_(BMessage* data);
37	static BArchivable* Instantiate(BMessage *data);
38};
39
40
41_BMCItem_::_BMCItem_(BMessage* data)
42	:
43	BMenuItem(data)
44{
45}
46
47
48/*static*/ BArchivable*
49_BMCItem_::Instantiate(BMessage *data) {
50	if (validate_instantiation(data, "_BMCItem_"))
51		return new _BMCItem_(data);
52
53	return NULL;
54}
55
56
57#endif
58
59
60//	#pragma mark - _BMCFilter_
61
62
63_BMCFilter_::_BMCFilter_(BMenuField* menuField, uint32 what)
64	:
65	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, what),
66	fMenuField(menuField)
67{
68}
69
70
71_BMCFilter_::~_BMCFilter_()
72{
73}
74
75
76filter_result
77_BMCFilter_::Filter(BMessage* message, BHandler** handler)
78{
79	if (message->what == B_MOUSE_DOWN) {
80		if (BView* view = dynamic_cast<BView*>(*handler)) {
81			BPoint point;
82			message->FindPoint("be:view_where", &point);
83			view->ConvertToParent(&point);
84			message->ReplacePoint("be:view_where", point);
85			*handler = fMenuField;
86		}
87	}
88
89	return B_DISPATCH_MESSAGE;
90}
91
92
93//	#pragma mark - _BMCMenuBar_
94
95
96_BMCMenuBar_::_BMCMenuBar_(BRect frame, bool fixedSize, BMenuField* menuField)
97	:
98	BMenuBar(frame, "_mc_mb_", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_ITEMS_IN_ROW,
99		!fixedSize),
100	fMenuField(menuField),
101	fFixedSize(fixedSize),
102	fShowPopUpMarker(true)
103{
104	_Init();
105}
106
107
108_BMCMenuBar_::_BMCMenuBar_(BMenuField* menuField)
109	:
110	BMenuBar("_mc_mb_", B_ITEMS_IN_ROW),
111	fMenuField(menuField),
112	fFixedSize(true),
113	fShowPopUpMarker(true)
114{
115	_Init();
116}
117
118
119_BMCMenuBar_::_BMCMenuBar_(BMessage* data)
120	:
121	BMenuBar(data),
122	fMenuField(NULL),
123	fFixedSize(true),
124	fShowPopUpMarker(true)
125{
126	SetFlags(Flags() | B_FRAME_EVENTS);
127
128	bool resizeToFit;
129	if (data->FindBool("_rsize_to_fit", &resizeToFit) == B_OK)
130		fFixedSize = !resizeToFit;
131}
132
133
134_BMCMenuBar_::~_BMCMenuBar_()
135{
136}
137
138
139//	#pragma mark - _BMCMenuBar_ public methods
140
141
142BArchivable*
143_BMCMenuBar_::Instantiate(BMessage* data)
144{
145	if (validate_instantiation(data, "_BMCMenuBar_"))
146		return new _BMCMenuBar_(data);
147
148	return NULL;
149}
150
151
152void
153_BMCMenuBar_::AttachedToWindow()
154{
155	fMenuField = static_cast<BMenuField*>(Parent());
156
157	// Don't cause the KeyMenuBar to change by being attached
158	BMenuBar* menuBar = Window()->KeyMenuBar();
159	BMenuBar::AttachedToWindow();
160	Window()->SetKeyMenuBar(menuBar);
161
162	if (fFixedSize && (Flags() & B_SUPPORTS_LAYOUT) == 0)
163		SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
164
165	if (Parent() != NULL) {
166		color_which which = Parent()->LowUIColor();
167		if (which == B_NO_COLOR)
168			SetLowColor(Parent()->LowColor());
169		else
170			SetLowUIColor(which);
171
172	} else
173		SetLowUIColor(B_MENU_BACKGROUND_COLOR);
174
175	fPreviousWidth = Bounds().Width();
176}
177
178
179void
180_BMCMenuBar_::Draw(BRect updateRect)
181{
182	if ((Flags() & B_SUPPORTS_LAYOUT) == 0) {
183		if (fFixedSize) {
184			// Set the width of the menu bar because the menu bar bounds may have
185			// been expanded by the selected menu item.
186			ResizeTo(fMenuField->_MenuBarWidth(), Bounds().Height());
187		} else {
188			// For compatability with BeOS R5:
189			//  - Set to the minimum of the menu bar width set by the menu frame
190			//    and the selected menu item width.
191			//  - Set the height to the preferred height ignoring the height of the
192			//    menu field.
193			float height;
194			BMenuBar::GetPreferredSize(NULL, &height);
195			ResizeTo(std::min(Bounds().Width(), fMenuField->_MenuBarWidth()),
196				height);
197		}
198	}
199
200	BRect rect(Bounds());
201	rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
202	uint32 flags = 0;
203	if (!IsEnabled())
204		flags |= BControlLook::B_DISABLED;
205	if (IsFocus())
206		flags |= BControlLook::B_FOCUSED;
207
208	be_control_look->DrawMenuFieldBackground(this, rect,
209		updateRect, base, fShowPopUpMarker, flags);
210
211	DrawItems(updateRect);
212}
213
214
215void
216_BMCMenuBar_::FrameResized(float width, float height)
217{
218	// we need to take care of cleaning up the parent menu field
219	float diff = width - fPreviousWidth;
220	fPreviousWidth = width;
221
222	if (Window() != NULL && diff != 0) {
223		BRect dirty(fMenuField->Bounds());
224		if (diff > 0) {
225			// clean up the dirty right border of
226			// the menu field when enlarging
227			dirty.right = Frame().right + kVMargin;
228			dirty.left = dirty.right - diff - kVMargin * 2;
229			fMenuField->Invalidate(dirty);
230		} else if (diff < 0) {
231			// clean up the dirty right line of
232			// the menu field when shrinking
233			dirty.left = Frame().right - kVMargin;
234			fMenuField->Invalidate(dirty);
235		}
236	}
237
238	BMenuBar::FrameResized(width, height);
239}
240
241
242void
243_BMCMenuBar_::MakeFocus(bool focused)
244{
245	if (IsFocus() == focused)
246		return;
247
248	BMenuBar::MakeFocus(focused);
249}
250
251
252void
253_BMCMenuBar_::MessageReceived(BMessage* message)
254{
255	switch (message->what) {
256		case 'TICK':
257		{
258			BMenuItem* item = ItemAt(0);
259
260			if (item != NULL && item->Submenu() != NULL
261				&& item->Submenu()->Window() != NULL) {
262				BMessage message(B_KEY_DOWN);
263
264				message.AddInt8("byte", B_ESCAPE);
265				message.AddInt8("key", B_ESCAPE);
266				message.AddInt32("modifiers", 0);
267				message.AddInt8("raw_char", B_ESCAPE);
268
269				Window()->PostMessage(&message, this, NULL);
270			}
271		}
272		// fall through
273		default:
274			BMenuBar::MessageReceived(message);
275			break;
276	}
277}
278
279
280void
281_BMCMenuBar_::SetMaxContentWidth(float width)
282{
283	float left;
284	float right;
285	GetItemMargins(&left, NULL, &right, NULL);
286
287	BMenuBar::SetMaxContentWidth(width - (left + right));
288}
289
290
291void
292_BMCMenuBar_::SetEnabled(bool enabled)
293{
294	fMenuField->SetEnabled(enabled);
295
296	BMenuBar::SetEnabled(enabled);
297}
298
299
300BSize
301_BMCMenuBar_::MinSize()
302{
303	BSize size;
304	BMenuBar::GetPreferredSize(&size.width, &size.height);
305	if (fShowPopUpMarker) {
306		// account for popup indicator + a few pixels margin
307		size.width += kPopUpIndicatorWidth;
308	}
309
310	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
311}
312
313
314BSize
315_BMCMenuBar_::MaxSize()
316{
317	// The maximum width of a normal BMenuBar is unlimited, but we want it
318	// limited.
319	BSize size;
320	BMenuBar::GetPreferredSize(&size.width, &size.height);
321
322	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
323}
324
325
326//	#pragma mark - _BMCMenuBar_ private methods
327
328
329void
330_BMCMenuBar_::_Init()
331{
332	SetFlags(Flags() | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE);
333	SetBorder(B_BORDER_CONTENTS);
334
335	float left, top, right, bottom;
336	GetItemMargins(&left, &top, &right, &bottom);
337
338#if 0
339	// TODO: Better fix would be to make BMenuItem draw text properly
340	// centered
341	font_height fontHeight;
342	GetFontHeight(&fontHeight);
343	top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent)
344		- ceilf(fontHeight.descent)) / 2) + 1;
345	bottom = top - 1;
346#else
347	// TODO: Fix content location properly. This is just a quick fix to
348	// make the BMenuField label and the super-item of the BMenuBar
349	// align vertically.
350	top++;
351	bottom--;
352#endif
353
354	left = right = be_control_look->DefaultLabelSpacing();
355
356	SetItemMargins(left, top,
357		right + (fShowPopUpMarker ? kPopUpIndicatorWidth : 0), bottom);
358}
359