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