xref: /haiku/src/kits/interface/BMCPrivate.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
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 
25 static 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.
34 class _BMCItem_: public BMenuItem {
35 public:
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 
76 filter_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 
142 BArchivable*
143 _BMCMenuBar_::Instantiate(BMessage* data)
144 {
145 	if (validate_instantiation(data, "_BMCMenuBar_"))
146 		return new _BMCMenuBar_(data);
147 
148 	return NULL;
149 }
150 
151 
152 void
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 
179 void
180 _BMCMenuBar_::Draw(BRect updateRect)
181 {
182 	if (fFixedSize) {
183 		// Set the width of the menu bar because the menu bar bounds may have
184 		// been expanded by the selected menu item.
185 		ResizeTo(fMenuField->_MenuBarWidth(), Bounds().Height());
186 	} else {
187 		// For compatability with BeOS R5:
188 		//  - Set to the minimum of the menu bar width set by the menu frame
189 		//    and the selected menu item width.
190 		//  - Set the height to the preferred height ignoring the height of the
191 		//    menu field.
192 		float height;
193 		BMenuBar::GetPreferredSize(NULL, &height);
194 		ResizeTo(std::min(Bounds().Width(), fMenuField->_MenuBarWidth()),
195 			height);
196 	}
197 
198 	BRect rect(Bounds());
199 	rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
200 	uint32 flags = 0;
201 	if (!IsEnabled())
202 		flags |= BControlLook::B_DISABLED;
203 	if (IsFocus())
204 		flags |= BControlLook::B_FOCUSED;
205 
206 	be_control_look->DrawMenuFieldBackground(this, rect,
207 		updateRect, base, fShowPopUpMarker, flags);
208 
209 	DrawItems(updateRect);
210 }
211 
212 
213 void
214 _BMCMenuBar_::FrameResized(float width, float height)
215 {
216 	// we need to take care of cleaning up the parent menu field
217 	float diff = width - fPreviousWidth;
218 	fPreviousWidth = width;
219 
220 	if (Window() != NULL && diff != 0) {
221 		BRect dirty(fMenuField->Bounds());
222 		if (diff > 0) {
223 			// clean up the dirty right border of
224 			// the menu field when enlarging
225 			dirty.right = Frame().right + kVMargin;
226 			dirty.left = dirty.right - diff - kVMargin * 2;
227 			fMenuField->Invalidate(dirty);
228 		} else if (diff < 0) {
229 			// clean up the dirty right line of
230 			// the menu field when shrinking
231 			dirty.left = Frame().right - kVMargin;
232 			fMenuField->Invalidate(dirty);
233 		}
234 	}
235 
236 	BMenuBar::FrameResized(width, height);
237 }
238 
239 
240 void
241 _BMCMenuBar_::MakeFocus(bool focused)
242 {
243 	if (IsFocus() == focused)
244 		return;
245 
246 	BMenuBar::MakeFocus(focused);
247 }
248 
249 
250 void
251 _BMCMenuBar_::MessageReceived(BMessage* message)
252 {
253 	switch (message->what) {
254 		case 'TICK':
255 		{
256 			BMenuItem* item = ItemAt(0);
257 
258 			if (item != NULL && item->Submenu() != NULL
259 				&& item->Submenu()->Window() != NULL) {
260 				BMessage message(B_KEY_DOWN);
261 
262 				message.AddInt8("byte", B_ESCAPE);
263 				message.AddInt8("key", B_ESCAPE);
264 				message.AddInt32("modifiers", 0);
265 				message.AddInt8("raw_char", B_ESCAPE);
266 
267 				Window()->PostMessage(&message, this, NULL);
268 			}
269 		}
270 		// fall through
271 		default:
272 			BMenuBar::MessageReceived(message);
273 			break;
274 	}
275 }
276 
277 
278 void
279 _BMCMenuBar_::SetMaxContentWidth(float width)
280 {
281 	float left;
282 	float right;
283 	GetItemMargins(&left, NULL, &right, NULL);
284 
285 	BMenuBar::SetMaxContentWidth(width - (left + right));
286 }
287 
288 
289 void
290 _BMCMenuBar_::SetEnabled(bool enabled)
291 {
292 	fMenuField->SetEnabled(enabled);
293 
294 	BMenuBar::SetEnabled(enabled);
295 }
296 
297 
298 BSize
299 _BMCMenuBar_::MinSize()
300 {
301 	BSize size;
302 	BMenuBar::GetPreferredSize(&size.width, &size.height);
303 	if (fShowPopUpMarker) {
304 		// account for popup indicator + a few pixels margin
305 		size.width += kPopUpIndicatorWidth;
306 	}
307 
308 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
309 }
310 
311 
312 BSize
313 _BMCMenuBar_::MaxSize()
314 {
315 	// The maximum width of a normal BMenuBar is unlimited, but we want it
316 	// limited.
317 	BSize size;
318 	BMenuBar::GetPreferredSize(&size.width, &size.height);
319 
320 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
321 }
322 
323 
324 //	#pragma mark - _BMCMenuBar_ private methods
325 
326 
327 void
328 _BMCMenuBar_::_Init()
329 {
330 	SetFlags(Flags() | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE);
331 	SetBorder(B_BORDER_CONTENTS);
332 
333 	float left, top, right, bottom;
334 	GetItemMargins(&left, &top, &right, &bottom);
335 
336 #if 0
337 	// TODO: Better fix would be to make BMenuItem draw text properly
338 	// centered
339 	font_height fontHeight;
340 	GetFontHeight(&fontHeight);
341 	top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent)
342 		- ceilf(fontHeight.descent)) / 2) + 1;
343 	bottom = top - 1;
344 #else
345 	// TODO: Fix content location properly. This is just a quick fix to
346 	// make the BMenuField label and the super-item of the BMenuBar
347 	// align vertically.
348 	top++;
349 	bottom--;
350 #endif
351 
352 	left = right = be_control_look->DefaultLabelSpacing();
353 
354 	SetItemMargins(left, top,
355 		right + (fShowPopUpMarker ? kPopUpIndicatorWidth : 0), bottom);
356 }
357