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