xref: /haiku/src/kits/interface/BMCPrivate.cpp (revision 6c4a44e36ba846c54467103f884d65dfa13e7fcb)
1 /*
2  * Copyright 2001-2009 Haiku, Inc. All rights reserved.
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  */
9 
10 
11 #include <BMCPrivate.h>
12 
13 #include <stdio.h>
14 
15 #include <ControlLook.h>
16 #include <LayoutUtils.h>
17 #include <MenuField.h>
18 #include <MenuItem.h>
19 #include <Message.h>
20 #include <MessageRunner.h>
21 #include <Region.h>
22 #include <Window.h>
23 
24 
25 _BMCFilter_::_BMCFilter_(BMenuField* menuField, uint32 what)
26 	:
27 	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, what),
28 	fMenuField(menuField)
29 {
30 }
31 
32 
33 _BMCFilter_::~_BMCFilter_()
34 {
35 }
36 
37 
38 filter_result
39 _BMCFilter_::Filter(BMessage* message, BHandler** handler)
40 {
41 	if (message->what == B_MOUSE_DOWN) {
42 		if (BView* view = dynamic_cast<BView*>(*handler)) {
43 			BPoint point;
44 			message->FindPoint("be:view_where", &point);
45 			view->ConvertToParent(&point);
46 			message->ReplacePoint("be:view_where", point);
47 			*handler = fMenuField;
48 		}
49 	}
50 
51 	return B_DISPATCH_MESSAGE;
52 }
53 
54 
55 // #pragma mark -
56 
57 
58 _BMCMenuBar_::_BMCMenuBar_(BRect frame, bool fixedSize, BMenuField* menuField)
59 	:
60 	BMenuBar(frame, "_mc_mb_", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_ITEMS_IN_ROW,
61 		!fixedSize),
62 	fMenuField(menuField),
63 	fFixedSize(fixedSize),
64 	fRunner(NULL),
65 	fShowPopUpMarker(true)
66 {
67 	_Init(true);
68 }
69 
70 
71 _BMCMenuBar_::_BMCMenuBar_(bool fixedSize, BMenuField* menuField)
72 	:
73 	BMenuBar("_mc_mb_", B_ITEMS_IN_ROW, !fixedSize),
74 	fMenuField(menuField),
75 	fFixedSize(fixedSize),
76 	fRunner(NULL),
77 	fShowPopUpMarker(true)
78 {
79 	_Init(false);
80 }
81 
82 
83 _BMCMenuBar_::_BMCMenuBar_(BMessage* data)
84 	:	BMenuBar(data),
85 	fMenuField(NULL),
86 	fFixedSize(true),
87 	fRunner(NULL),
88 	fShowPopUpMarker(true)
89 {
90 	SetFlags(Flags() | B_FRAME_EVENTS);
91 
92 	bool resizeToFit;
93 	if (data->FindBool("_rsize_to_fit", &resizeToFit) == B_OK)
94 		fFixedSize = !resizeToFit;
95 }
96 
97 
98 _BMCMenuBar_::~_BMCMenuBar_()
99 {
100 	delete fRunner;
101 }
102 
103 
104 BArchivable*
105 _BMCMenuBar_::Instantiate(BMessage* data)
106 {
107 	if (validate_instantiation(data, "_BMCMenuBar_"))
108 		return new _BMCMenuBar_(data);
109 
110 	return NULL;
111 }
112 
113 
114 void
115 _BMCMenuBar_::AttachedToWindow()
116 {
117 	fMenuField = static_cast<BMenuField*>(Parent());
118 
119 	// Don't cause the KeyMenuBar to change by being attached
120 	BMenuBar* menuBar = Window()->KeyMenuBar();
121 	BMenuBar::AttachedToWindow();
122 	Window()->SetKeyMenuBar(menuBar);
123 
124 	if (fFixedSize && (Flags() & B_SUPPORTS_LAYOUT) == 0)
125 		SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
126 
127 	if (Parent() != NULL)
128 		SetLowColor(Parent()->LowColor());
129 	else
130 		SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
131 }
132 
133 
134 void
135 _BMCMenuBar_::Draw(BRect updateRect)
136 {
137 	if (be_control_look != NULL) {
138 		BRect rect(Bounds());
139 		rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
140 		uint32 flags = 0;
141 		if (!IsEnabled())
142 			flags |= BControlLook::B_DISABLED;
143 		if (IsFocus())
144 			flags |= BControlLook::B_FOCUSED;
145 		be_control_look->DrawMenuFieldBackground(this, rect,
146 			updateRect, base, fShowPopUpMarker, flags);
147 
148 		_DrawItems(updateRect);
149 
150 		return;
151 	}
152 
153 	if (!fShowPopUpMarker) {
154 		BMenuBar::Draw(updateRect);
155 		return;
156 	}
157 
158 	// draw the right side with the popup marker
159 
160 	// prevent the original BMenuBar's Draw from
161 	// drawing in those parts
162 	BRect bounds(Bounds());
163 	bounds.right -= 10.0;
164 	bounds.bottom -= 1.0;
165 
166 	BRegion clipping(bounds);
167 	ConstrainClippingRegion(&clipping);
168 
169 	BMenuBar::Draw(updateRect);
170 
171 	// restore clipping
172 	ConstrainClippingRegion(NULL);
173 	bounds.right += 10.0;
174 	bounds.bottom += 1.0;
175 
176 	// prepare some colors
177 	rgb_color normalNoTint = LowColor();
178 	rgb_color noTint = tint_color(normalNoTint, 0.74);
179 	rgb_color darken4;
180 	rgb_color normalDarken4;
181 	rgb_color darken1;
182 	rgb_color lighten1;
183 	rgb_color lighten2;
184 
185 	if (IsEnabled()) {
186 		darken4 = tint_color(noTint, B_DARKEN_4_TINT);
187 		normalDarken4 = tint_color(normalNoTint, B_DARKEN_4_TINT);
188 		darken1 = tint_color(noTint, B_DARKEN_1_TINT);
189 		lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT);
190 		lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT);
191 	} else {
192 		darken4 = tint_color(noTint, B_DARKEN_2_TINT);
193 		normalDarken4 = tint_color(normalNoTint, B_DARKEN_2_TINT);
194 		darken1 = tint_color(noTint, (B_NO_TINT + B_DARKEN_1_TINT) / 2.0);
195 		lighten1 = tint_color(noTint, (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0);
196 		lighten2 = tint_color(noTint, B_LIGHTEN_1_TINT);
197 	}
198 
199 	BRect r(bounds);
200 	r.left = r.right - 10.0;
201 
202 	BeginLineArray(6);
203 		// bottom below item text, darker then BMenuBar
204 		// would normaly draw it
205 		AddLine(BPoint(bounds.left, r.bottom),
206 				BPoint(r.left - 1.0, r.bottom), normalDarken4);
207 
208 		// bottom below popup marker
209 		AddLine(BPoint(r.left, r.bottom),
210 				BPoint(r.right, r.bottom), darken4);
211 		// right of popup marker
212 		AddLine(BPoint(r.right, r.bottom - 1),
213 				BPoint(r.right, r.top), darken4);
214 		// top above popup marker
215 		AddLine(BPoint(r.left, r.top),
216 				BPoint(r.right - 2, r.top), lighten2);
217 
218 		r.top += 1;
219 		r.bottom -= 1;
220 		r.right -= 1;
221 
222 		// bottom below popup marker
223 		AddLine(BPoint(r.left, r.bottom),
224 				BPoint(r.right, r.bottom), darken1);
225 		// right of popup marker
226 		AddLine(BPoint(r.right, r.bottom - 1),
227 				BPoint(r.right, r.top), darken1);
228 	EndLineArray();
229 
230 	r.bottom -= 1;
231 	r.right -= 1;
232 	SetHighColor(noTint);
233 	FillRect(r);
234 
235 	// popup marker
236 	BPoint center(roundf((r.left + r.right) / 2.0),
237 				  roundf((r.top + r.bottom) / 2.0));
238 	BPoint triangle[3];
239 	triangle[0] = center + BPoint(-2.5, -0.5);
240 	triangle[1] = center + BPoint(2.5, -0.5);
241 	triangle[2] = center + BPoint(0.0, 2.0);
242 
243 	uint32 flags = Flags();
244 	SetFlags(flags | B_SUBPIXEL_PRECISE);
245 
246 	SetHighColor(normalDarken4);
247 	FillTriangle(triangle[0], triangle[1], triangle[2]);
248 
249 	SetFlags(flags);
250 }
251 
252 
253 void
254 _BMCMenuBar_::FrameResized(float width, float height)
255 {
256 	// we need to take care of resizing and cleaning up
257 	// the parent menu field
258 	float diff = width - fPreviousWidth;
259 	fPreviousWidth = width;
260 
261 	if (Window()) {
262 		if (diff > 0) {
263 			// clean up the dirty right border of
264 			// the menu field when enlarging
265 			BRect dirty(fMenuField->Bounds());
266 			dirty.right = Frame().right + 2;
267 			dirty.left = dirty.left - diff - 4;
268 			fMenuField->Invalidate(dirty);
269 
270 			// clean up the arrow part
271 			dirty = Bounds();
272 			dirty.left = dirty.right - diff - 12;
273 			Invalidate(dirty);
274 
275 		} else if (diff < 0) {
276 			// clean up the dirty right line of
277 			// the menu field when shrinking
278 			BRect dirty(fMenuField->Bounds());
279 			dirty.left = Frame().right - 2;
280 			dirty.right = dirty.left - diff + 4;
281 			fMenuField->Invalidate(dirty);
282 
283 			// clean up the arrow part
284 			dirty = Bounds();
285 			dirty.left = dirty.right - 12;
286 			Invalidate(dirty);
287 		}
288 	}
289 
290 	if (!fFixedSize && ResizingMode() == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) {
291 		// we have been shrinked or enlarged and need to take care
292 		// of the size of the parent menu field as well
293 		// NOTE: no worries about follow mode, we follow left and top
294 		// in autosize mode
295 		diff = Frame().right + 2 - fMenuField->Bounds().right;
296 		fMenuField->ResizeBy(diff, 0.0);
297 	}
298 	BMenuBar::FrameResized(width, height);
299 }
300 
301 
302 void
303 _BMCMenuBar_::MessageReceived(BMessage* msg)
304 {
305 	switch (msg->what) {
306 		case 'TICK':
307 		{
308 			BMenuItem* item = ItemAt(0);
309 
310 			if (item && item->Submenu() &&  item->Submenu()->Window()) {
311 				BMessage message(B_KEY_DOWN);
312 
313 				message.AddInt8("byte", B_ESCAPE);
314 				message.AddInt8("key", B_ESCAPE);
315 				message.AddInt32("modifiers", 0);
316 				message.AddInt8("raw_char", B_ESCAPE);
317 
318 				Window()->PostMessage(&message, this, NULL);
319 			}
320 		}
321 		// fall through
322 		default:
323 			BMenuBar::MessageReceived(msg);
324 			break;
325 	}
326 }
327 
328 
329 void
330 _BMCMenuBar_::MakeFocus(bool focused)
331 {
332 	if (IsFocus() == focused)
333 		return;
334 
335 	BMenuBar::MakeFocus(focused);
336 
337 	if (focused) {
338 		BMessage message('TICK');
339 		//fRunner = new BMessageRunner(BMessenger(this, NULL, NULL), &message,
340 		//	50000, -1);
341 	} else if (fRunner) {
342 		//delete fRunner;
343 		fRunner = NULL;
344 	}
345 
346 	if (focused)
347 		return;
348 
349 	fMenuField->fSelected = false;
350 	fMenuField->fTransition = true;
351 
352 	BRect bounds(fMenuField->Bounds());
353 
354 	fMenuField->Invalidate(BRect(bounds.left, bounds.top, fMenuField->fDivider,
355 		bounds.bottom));
356 }
357 
358 
359 BSize
360 _BMCMenuBar_::MinSize()
361 {
362 	BSize size;
363 	BMenuBar::GetPreferredSize(&size.width, &size.height);
364 
365 	if (fShowPopUpMarker) {
366 		// account for popup indicator + a few pixels margin
367 		size.width += 13.0;
368 	}
369 
370 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
371 }
372 
373 
374 BSize
375 _BMCMenuBar_::MaxSize()
376 {
377 	// The maximum width of a normal BMenuBar is unlimited, but we want it
378 	// limited.
379 	BSize size;
380 	BMenuBar::GetPreferredSize(&size.width, &size.height);
381 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
382 }
383 
384 
385 void
386 _BMCMenuBar_::_Init(bool setMaxContentWidth)
387 {
388 	SetFlags(Flags() | B_FRAME_EVENTS);
389 	SetBorder(B_BORDER_CONTENTS);
390 
391 	float left, top, right, bottom;
392 	GetItemMargins(&left, &top, &right, &bottom);
393 
394 #if 0
395 	// TODO: Better fix would be to make BMenuItem draw text properly
396 	// centered
397 	font_height fontHeight;
398 	GetFontHeight(&fontHeight);
399 	top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent)
400 		- ceilf(fontHeight.descent)) / 2) + 1;
401 	bottom = top - 1;
402 #else
403 	// TODO: Fix content location properly. This is just a quick fix to
404 	// make the BMenuField label and the super-item of the BMenuBar
405 	// align vertically.
406 	top++;
407 	bottom--;
408 #endif
409 
410 	if (be_control_look != NULL)
411 		left = right = be_control_look->DefaultLabelSpacing();
412 
413 	SetItemMargins(left, top, right + fShowPopUpMarker ? 10 : 0, bottom);
414 
415 	fPreviousWidth = Bounds().Width();
416 
417 	if (setMaxContentWidth)
418 		SetMaxContentWidth(fPreviousWidth - (left + right));
419 }
420 
421