xref: /haiku/src/kits/interface/MenuField.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2  * Copyright 2001-2005, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  */
8 
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <MenuBar.h>
15 #include <MenuField.h>
16 #include <Message.h>
17 #include <BMCPrivate.h>
18 #include <Window.h>
19 
20 
21 static float kVMargin = 2.0f;
22 
23 
24 BMenuField::BMenuField(BRect frame, const char *name, const char *label,
25 	BMenu *menu, uint32 resize, uint32 flags)
26 	: BView(frame, name, resize, flags)
27 {
28 	InitObject(label);
29 	SetFont(be_plain_font);
30 	fMenu = menu;
31 	InitMenu(menu);
32 
33 	frame.OffsetTo(0.0f, 0.0f);
34 	fMenuBar = new _BMCMenuBar_(BRect(frame.left + fDivider + 1.0,
35 		frame.top + kVMargin, frame.right - 2.0f, frame.bottom - kVMargin),
36 		false, this);
37 
38 	AddChild(fMenuBar);
39 	fMenuBar->AddItem(menu);
40 
41 	fMenuBar->SetFont(be_plain_font);
42 
43 	InitObject2();
44 }
45 
46 
47 BMenuField::BMenuField(BRect frame, const char *name, const char *label,
48 	BMenu *menu, bool fixedSize, uint32 resize, uint32 flags)
49 	: BView(frame, name, resize, flags)
50 {
51 	InitObject(label);
52 	SetFont(be_plain_font);
53 	fMenu = menu;
54 	InitMenu(menu);
55 
56 	fFixedSizeMB = fixedSize;
57 
58 	frame.OffsetTo(0.0f, 0.0f);
59 	fMenuBar = new _BMCMenuBar_(BRect(frame.left + fDivider + 1.0,
60 		frame.top + kVMargin, frame.right - 2.0f, frame.bottom - kVMargin),
61 		fixedSize, this);
62 
63 	AddChild(fMenuBar);
64 	fMenuBar->AddItem(new _BMCItem_(menu));
65 
66 	fMenuBar->SetFont(be_plain_font);
67 
68 	InitObject2();
69 }
70 
71 
72 BMenuField::BMenuField(BMessage *data)
73 	: BView(data)
74 {
75 	const char *label = NULL;
76 	data->FindString("_label", &label);
77 
78 	InitObject(label);
79 
80 	fMenuBar = (BMenuBar*)FindView("_mc_mb_");
81 	fMenu = fMenuBar->SubmenuAt(0);
82 
83 	InitObject2();
84 
85 	bool disable;
86 	if (data->FindBool("_disable", &disable) == B_OK)
87 		SetEnabled(!disable);
88 
89 	int32 align;
90 	data->FindInt32("_align", &align);
91 		SetAlignment((alignment)align);
92 
93 	data->FindFloat("_divide", &fDivider);
94 
95 	bool fixed;
96 	if (data->FindBool("be:fixeds", &fixed) == B_OK)
97 		fFixedSizeMB = fixed;
98 
99 	BMenuItem *item = fMenuBar->ItemAt(0);
100 	if (!item)
101 		return;
102 
103 	_BMCItem_ *bmcitem = dynamic_cast<_BMCItem_*>(item);
104 	if (!bmcitem)
105 		return;
106 
107 	bool dmark;
108 	if (data->FindBool("be:dmark", &dmark))
109 		bmcitem->fShowPopUpMarker = dmark;
110 }
111 
112 
113 BMenuField::~BMenuField()
114 {
115 	free(fLabel);
116 
117 	if (fMenuTaskID >= 0)
118 		kill_thread(fMenuTaskID);
119 }
120 
121 
122 BArchivable *
123 BMenuField::Instantiate(BMessage *data)
124 {
125 	if (validate_instantiation(data, "BMenuField"))
126 		return new BMenuField(data);
127 
128 	return NULL;
129 }
130 
131 
132 status_t
133 BMenuField::Archive(BMessage *data, bool deep) const
134 {
135 	BView::Archive(data, deep);
136 
137 	if (Label())
138 		data->AddString("_label", Label());
139 
140 	if (!IsEnabled())
141 		data->AddBool("_disable", true);
142 
143 	data->AddInt32("_align", Alignment());
144 	data->AddFloat("_divide", Divider());
145 
146 	if (fFixedSizeMB)
147 		data->AddBool("be:fixeds", true);
148 
149 	BMenuItem *item = fMenuBar->ItemAt(0);
150 	if (!item)
151 		return B_OK;
152 
153 	_BMCItem_ *bmcitem = dynamic_cast<_BMCItem_*>(item);
154 	if (bmcitem && !bmcitem->fShowPopUpMarker)
155 		data->AddBool("be:dmark", false);
156 
157 	return B_OK;
158 }
159 
160 
161 void
162 BMenuField::Draw(BRect update)
163 {
164 	BRect bounds(Bounds());
165 	bool active = false;
166 
167 	if (IsFocus())
168 		active = Window()->IsActive();
169 
170 	DrawLabel(bounds, update);
171 
172 	BRect frame(fMenuBar->Frame());
173 
174 	if (frame.InsetByCopy(-2, -2).Intersects(update)) {
175 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
176 		StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f),
177 			BPoint(frame.left - 1.0f, frame.bottom - 1.0f));
178 		StrokeLine(BPoint(frame.left - 1.0f, frame.top - 1.0f),
179 			BPoint(frame.right - 1.0f, frame.top - 1.0f));
180 
181 		StrokeLine(BPoint(frame.left + 1.0f, frame.bottom + 1.0f),
182 			BPoint(frame.right + 1.0f, frame.bottom + 1.0f));
183 		StrokeLine(BPoint(frame.right + 1.0f, frame.top + 1.0f));
184 
185 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_4_TINT));
186 		StrokeLine(BPoint(frame.left - 1.0f, frame.bottom),
187 			BPoint(frame.left - 1.0f, frame.bottom));
188 		StrokeLine(BPoint(frame.right, frame.top - 1.0f),
189 			BPoint(frame.right, frame.top - 1.0f));
190 	}
191 
192 	if (active || fTransition) {
193 		SetHighColor(active ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) :
194 			ViewColor());
195 		StrokeRect(frame.InsetByCopy(-kVMargin, -kVMargin));
196 
197 		fTransition = false;
198 	}
199 }
200 
201 
202 void
203 BMenuField::AttachedToWindow()
204 {
205 	if (Parent()) {
206 		SetViewColor(Parent()->ViewColor());
207 		SetLowColor(Parent()->ViewColor());
208 	}
209 
210 	if (fLabel)
211 		fStringWidth = StringWidth(fLabel);
212 }
213 
214 
215 void
216 BMenuField::AllAttached()
217 {
218 	ResizeTo(Bounds().Width(),
219 		fMenuBar->Bounds().Height() + kVMargin + kVMargin);
220 }
221 
222 
223 void
224 BMenuField::MouseDown(BPoint where)
225 {
226 	if (where.x > fDivider && !fMenuBar->Frame().Contains(where))
227 		return;
228 
229 	BRect bounds = fMenuBar->ConvertFromParent(Bounds());
230 
231 	fMenuBar->StartMenuBar(0, false, true, &bounds);
232 
233 	fMenuTaskID = spawn_thread((thread_func)MenuTask, "_m_task_",
234 		B_NORMAL_PRIORITY, this);
235 	if (fMenuTaskID)
236 		resume_thread(fMenuTaskID);
237 }
238 
239 
240 void
241 BMenuField::KeyDown(const char *bytes, int32 numBytes)
242 {
243 	switch (bytes[0]) {
244 		case B_SPACE:
245 		case B_RIGHT_ARROW:
246 		case B_DOWN_ARROW:
247 		{
248 			BRect bounds = fMenuBar->ConvertFromParent(Bounds());
249 
250 			fMenuBar->StartMenuBar(0, true, true, &bounds);
251 
252 			fSelected = true;
253 			fTransition = true;
254 
255 			bounds = Bounds();
256 			bounds.right = fDivider;
257 
258 			Invalidate(bounds);
259 		}
260 
261 		default:
262 			BView::KeyDown(bytes, numBytes);
263 	}
264 }
265 
266 
267 void
268 BMenuField::MakeFocus(bool state)
269 {
270 	if (IsFocus() == state)
271 		return;
272 
273 	BView::MakeFocus(state);
274 
275 	if (Window())
276 		Invalidate(); // TODO: use fStringWidth
277 }
278 
279 
280 void
281 BMenuField::MessageReceived(BMessage *msg)
282 {
283 	BView::MessageReceived(msg);
284 }
285 
286 
287 void
288 BMenuField::WindowActivated(bool state)
289 {
290 	BView::WindowActivated(state);
291 
292 	if (IsFocus())
293 		Invalidate();
294 }
295 
296 
297 void
298 BMenuField::MouseUp(BPoint point)
299 {
300 	BView::MouseUp(point);
301 }
302 
303 
304 void
305 BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage *message)
306 {
307 	BView::MouseMoved(point, code, message);
308 }
309 
310 
311 void
312 BMenuField::DetachedFromWindow()
313 {
314 	BView::DetachedFromWindow();
315 }
316 
317 
318 void
319 BMenuField::AllDetached()
320 {
321 	BView::AllDetached();
322 }
323 
324 
325 void
326 BMenuField::FrameMoved(BPoint newPosition)
327 {
328 	BView::FrameMoved(newPosition);
329 }
330 
331 
332 void
333 BMenuField::FrameResized(float newWidth, float newHeight)
334 {
335 	BView::FrameResized(newWidth, newHeight);
336 }
337 
338 
339 BMenu *
340 BMenuField::Menu() const
341 {
342 	return fMenu;
343 }
344 
345 
346 BMenuBar *
347 BMenuField::MenuBar() const
348 {
349 	return fMenuBar;
350 }
351 
352 
353 BMenuItem *
354 BMenuField::MenuItem() const
355 {
356 	return fMenuBar->ItemAt(0);
357 }
358 
359 
360 void
361 BMenuField::SetLabel(const char *label)
362 {
363 	if (fLabel) {
364 		if (label && strcmp(fLabel, label) == 0)
365 			return;
366 
367 		free(fLabel);
368 	}
369 
370 	fLabel = strdup(label);
371 
372 	if (Window()) {
373 		Invalidate();
374 		if (fLabel)
375 			fStringWidth = StringWidth(fLabel);
376 	}
377 }
378 
379 
380 const char *
381 BMenuField::Label() const
382 {
383 	return fLabel;
384 }
385 
386 
387 void
388 BMenuField::SetEnabled(bool on)
389 {
390 	if (fEnabled == on)
391 		return;
392 
393 	fEnabled = on;
394 	fMenuBar->SetEnabled(on);
395 
396 	if (Window()) {
397 		fMenuBar->Invalidate(fMenuBar->Bounds());
398 		Invalidate(Bounds());
399 	}
400 }
401 
402 
403 bool
404 BMenuField::IsEnabled() const
405 {
406 	return fEnabled;
407 }
408 
409 
410 void
411 BMenuField::SetAlignment(alignment label)
412 {
413 	fAlign = label;
414 }
415 
416 
417 alignment
418 BMenuField::Alignment() const
419 {
420 	return fAlign;
421 }
422 
423 
424 void
425 BMenuField::SetDivider(float divider)
426 {
427 	divider = floorf(divider + 0.5);
428 
429 	float dx = fDivider - divider;
430 
431 	if (dx == 0.0f)
432 		return;
433 
434 	fDivider = divider;
435 
436 	MenuBar()->MoveBy(-dx, 0.0f);
437 	MenuBar()->ResizeBy(dx, 0.0f);
438 }
439 
440 
441 float
442 BMenuField::Divider() const
443 {
444 	return fDivider;
445 }
446 
447 
448 void
449 BMenuField::ShowPopUpMarker()
450 {
451 	// TODO:
452 }
453 
454 
455 void
456 BMenuField::HidePopUpMarker()
457 {
458 	// TODO:
459 }
460 
461 
462 BHandler *
463 BMenuField::ResolveSpecifier(BMessage *message, int32 index,
464 	BMessage *specifier, int32 form, const char *property)
465 {
466 	return BView::ResolveSpecifier(message, index, specifier, form, property);
467 }
468 
469 
470 status_t
471 BMenuField::GetSupportedSuites(BMessage *data)
472 {
473 	return BView::GetSupportedSuites(data);
474 }
475 
476 
477 void
478 BMenuField::ResizeToPreferred()
479 {
480 	BView::ResizeToPreferred();
481 }
482 
483 
484 void
485 BMenuField::GetPreferredSize(float *_width, float *_height)
486 {
487 	BView::GetPreferredSize(_width, _height);
488 
489 	if (!fFixedSizeMB) {
490 		BMenu* menu = Menu();
491 		float width = 0;
492 
493 		if (menu != NULL) {
494 			if (menu->IsLabelFromMarked()) {
495 				// find the width of the largest item
496 				for (int32 i = menu->CountItems(); i-- > 0;) {
497 					BMenuItem* item = menu->ItemAt(i);
498 
499 					if (item != NULL && item->Label() != NULL) {
500 						float labelWidth = StringWidth(item->Label());
501 						if (labelWidth > width)
502 							width = labelWidth;
503 					}
504 				}
505 			} else if (menu->Superitem() != NULL)
506 				width = StringWidth(menu->Superitem()->Label());
507 		}
508 
509 		// TODO: fix these values (they should match the visual appearance)
510 		*_width = width + 45 + fDivider;
511 
512 		if (Label())
513 			*_width += StringWidth(Label()) + 5;
514 	}
515 
516 	*_height = fMenuBar->Bounds().Height();
517 }
518 
519 
520 status_t
521 BMenuField::Perform(perform_code d, void *arg)
522 {
523 	return BView::Perform(d, arg);
524 }
525 
526 
527 void BMenuField::_ReservedMenuField1() {}
528 void BMenuField::_ReservedMenuField2() {}
529 void BMenuField::_ReservedMenuField3() {}
530 
531 
532 BMenuField &
533 BMenuField::operator=(const BMenuField &)
534 {
535 	return *this;
536 }
537 
538 
539 void
540 BMenuField::InitObject(const char *label)
541 {
542 	fLabel = NULL;
543 	fMenu = NULL;
544 	fMenuBar = NULL;
545 	fAlign = B_ALIGN_LEFT;
546 	fStringWidth = 0;
547 	fEnabled = true;
548 	fSelected = false;
549 	fTransition = false;
550 	fFixedSizeMB = false;
551 	fMenuTaskID = -1;
552 
553 	SetLabel(label);
554 
555 	if (label)
556 		fDivider = (float)floor(Frame().Width() / 2.0f);
557 	else
558 		fDivider = 0;
559 }
560 
561 
562 void
563 BMenuField::InitObject2()
564 {
565 	// TODO set filter
566 
567 	font_height fontHeight;
568 	GetFontHeight(&fontHeight);
569 
570 	// TODO: fix this calculation
571 	float height = floorf(fontHeight.ascent + fontHeight.descent
572 		+ fontHeight.leading) + 7;
573 
574 	fMenuBar->ResizeTo(Bounds().Width() - fDivider, height);
575 }
576 
577 
578 void
579 BMenuField::DrawLabel(BRect bounds, BRect update)
580 {
581 	font_height fh;
582 	GetFontHeight(&fh);
583 
584 	if (Label()) {
585 		SetLowColor(ViewColor());
586 
587 		float y = (float)ceil(fh.ascent + fh.descent + fh.leading) + 2.0f;
588 		float x;
589 
590 		switch (fAlign) {
591 			case B_ALIGN_RIGHT:
592 				x = fDivider - StringWidth(Label()) - 3.0f;
593 				break;
594 
595 			case B_ALIGN_CENTER:
596 				x = fDivider - StringWidth(Label()) / 2.0f;
597 				break;
598 
599 			default:
600 				x = 3.0f;
601 				break;
602 		}
603 
604 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
605 			IsEnabled() ? B_DARKEN_MAX_TINT : B_DISABLED_LABEL_TINT));
606 		DrawString(Label(), BPoint(x, y));
607 	}
608 }
609 
610 
611 void
612 BMenuField::InitMenu(BMenu *menu)
613 {
614 	menu->SetFont(be_plain_font);
615 
616 	int32 index = 0;
617 	BMenu *subMenu;
618 
619 	while ((subMenu = menu->SubmenuAt(index++)) != NULL)
620 		InitMenu(subMenu);
621 }
622 
623 
624 long
625 BMenuField::MenuTask(void *arg)
626 {
627 	BMenuField *menuField = (BMenuField*)arg;
628 
629 	if (!menuField->LockLooper())
630 		return 0;
631 
632 	menuField->fSelected = true;
633 	menuField->fTransition = true;
634 	menuField->Invalidate();
635 
636 	menuField->UnlockLooper();
637 
638 	bool tracking;
639 
640 	do {
641 		snooze(20000);
642 
643 		if (!menuField->LockLooper())
644 			return 0;
645 
646 		tracking = menuField->fMenuBar->fTracking;
647 
648 		menuField->UnlockLooper();
649 	} while (tracking);
650 
651 	if (!menuField->LockLooper())
652 		return 0;
653 
654 	menuField->fSelected = false;
655 	menuField->fTransition = true;
656 	menuField->Invalidate();
657 
658 	menuField->UnlockLooper();
659 	return 0;
660 }
661 
662