xref: /haiku/src/kits/interface/MenuItem.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stefano Ceccherini, stefano.ceccherini@gmail.com
7  *		Marc Flerackers, mflerackers@androme.be
8  *		Bill Hayden, haydentech@users.sourceforge.net
9  *		Olivier Milla
10  *		John Scipione, jscipione@gmail.com
11  */
12 
13 
14 #include <ctype.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include <algorithm>
19 
20 #include <Bitmap.h>
21 #include <ControlLook.h>
22 #include <MenuItem.h>
23 #include <Shape.h>
24 #include <String.h>
25 #include <Window.h>
26 
27 #include <MenuPrivate.h>
28 
29 #include "utf8_functions.h"
30 
31 
32 static const float kMarkTint = 0.75f;
33 
34 // map control key shortcuts to drawable Unicode characters
35 // cf. http://unicode.org/charts/PDF/U2190.pdf
36 const char* kUTF8ControlMap[] = {
37 	NULL,
38 	"\xe2\x86\xb8", /* B_HOME U+21B8 */
39 	NULL, NULL,
40 	NULL, /* B_END */
41 	NULL, /* B_INSERT */
42 	NULL, NULL,
43 	NULL, /* B_BACKSPACE */
44 	"\xe2\x86\xb9", /* B_TAB U+21B9 */
45 	"\xe2\x8f\x8e", /* B_ENTER, U+23CE */
46 	NULL, /* B_PAGE_UP */
47 	NULL, /* B_PAGE_DOWN */
48 	NULL, NULL, NULL,
49 	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
50 	NULL, NULL, NULL, NULL,
51 	"\xe2\x86\x90", /* B_LEFT_ARROW */
52 	"\xe2\x86\x92", /* B_RIGHT_ARROW */
53 	"\xe2\x86\x91", /* B_UP_ARROW */
54 	"\xe2\x86\x93", /* B_DOWN_ARROW */
55 };
56 
57 
58 using BPrivate::MenuPrivate;
59 
60 BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut,
61 	uint32 modifiers)
62 {
63 	_InitData();
64 	if (label != NULL)
65 		fLabel = strdup(label);
66 
67 	SetMessage(message);
68 
69 	fShortcutChar = shortcut;
70 
71 	if (shortcut != 0)
72 		fModifiers = modifiers | B_COMMAND_KEY;
73 	else
74 		fModifiers = 0;
75 }
76 
77 
78 BMenuItem::BMenuItem(BMenu* menu, BMessage* message)
79 {
80 	_InitData();
81 	SetMessage(message);
82 	_InitMenuData(menu);
83 }
84 
85 
86 BMenuItem::BMenuItem(BMessage* data)
87 {
88 	_InitData();
89 
90 	if (data->HasString("_label")) {
91 		const char* string;
92 
93 		data->FindString("_label", &string);
94 		SetLabel(string);
95 	}
96 
97 	bool disable;
98 	if (data->FindBool("_disable", &disable) == B_OK)
99 		SetEnabled(!disable);
100 
101 	bool marked;
102 	if (data->FindBool("_marked", &marked) == B_OK)
103 		SetMarked(marked);
104 
105 	int32 userTrigger;
106 	if (data->FindInt32("_user_trig", &userTrigger) == B_OK)
107 		SetTrigger(userTrigger);
108 
109 	if (data->HasInt32("_shortcut")) {
110 		int32 shortcut, mods;
111 
112 		data->FindInt32("_shortcut", &shortcut);
113 		data->FindInt32("_mods", &mods);
114 
115 		SetShortcut(shortcut, mods);
116 	}
117 
118 	if (data->HasMessage("_msg")) {
119 		BMessage* message = new BMessage;
120 		data->FindMessage("_msg", message);
121 		SetMessage(message);
122 	}
123 
124 	BMessage subMessage;
125 	if (data->FindMessage("_submenu", &subMessage) == B_OK) {
126 		BArchivable* object = instantiate_object(&subMessage);
127 		if (object != NULL) {
128 			BMenu* menu = dynamic_cast<BMenu*>(object);
129 			if (menu != NULL)
130 				_InitMenuData(menu);
131 		}
132 	}
133 }
134 
135 
136 BArchivable*
137 BMenuItem::Instantiate(BMessage* data)
138 {
139 	if (validate_instantiation(data, "BMenuItem"))
140 		return new BMenuItem(data);
141 
142 	return NULL;
143 }
144 
145 
146 status_t
147 BMenuItem::Archive(BMessage* data, bool deep) const
148 {
149 	status_t status = BArchivable::Archive(data, deep);
150 
151 	if (status == B_OK && fLabel)
152 		status = data->AddString("_label", Label());
153 
154 	if (status == B_OK && !IsEnabled())
155 		status = data->AddBool("_disable", true);
156 
157 	if (status == B_OK && IsMarked())
158 		status = data->AddBool("_marked", true);
159 
160 	if (status == B_OK && fUserTrigger)
161 		status = data->AddInt32("_user_trig", fUserTrigger);
162 
163 	if (status == B_OK && fShortcutChar) {
164 		status = data->AddInt32("_shortcut", fShortcutChar);
165 		if (status == B_OK)
166 			status = data->AddInt32("_mods", fModifiers);
167 	}
168 
169 	if (status == B_OK && Message() != NULL)
170 		status = data->AddMessage("_msg", Message());
171 
172 	if (status == B_OK && deep && fSubmenu) {
173 		BMessage submenu;
174 		if (fSubmenu->Archive(&submenu, true) == B_OK)
175 			status = data->AddMessage("_submenu", &submenu);
176 	}
177 
178 	return status;
179 }
180 
181 
182 BMenuItem::~BMenuItem()
183 {
184 	if (fSuper != NULL)
185 		fSuper->RemoveItem(this);
186 
187 	free(fLabel);
188 	delete fSubmenu;
189 }
190 
191 
192 void
193 BMenuItem::SetLabel(const char* string)
194 {
195 	if (fLabel != NULL) {
196 		free(fLabel);
197 		fLabel = NULL;
198 	}
199 
200 	if (string != NULL)
201 		fLabel = strdup(string);
202 
203 	if (fSuper != NULL) {
204 		fSuper->InvalidateLayout();
205 
206 		if (fSuper->LockLooper()) {
207 			fSuper->Invalidate();
208 			fSuper->UnlockLooper();
209 		}
210 	}
211 }
212 
213 
214 void
215 BMenuItem::SetEnabled(bool enable)
216 {
217 	if (fEnabled == enable)
218 		return;
219 
220 	fEnabled = enable;
221 
222 	if (fSubmenu != NULL)
223 		fSubmenu->SetEnabled(enable);
224 
225 	BMenu* menu = fSuper;
226 	if (menu != NULL && menu->LockLooper()) {
227 		menu->Invalidate(fBounds);
228 		menu->UnlockLooper();
229 	}
230 }
231 
232 
233 void
234 BMenuItem::SetMarked(bool mark)
235 {
236 	fMark = mark;
237 
238 	if (mark && fSuper != NULL) {
239 		MenuPrivate priv(fSuper);
240 		priv.ItemMarked(this);
241 	}
242 }
243 
244 
245 void
246 BMenuItem::SetTrigger(char trigger)
247 {
248 	fUserTrigger = trigger;
249 
250 	// try uppercase letters first
251 
252 	const char* pos = strchr(Label(), toupper(trigger));
253 	trigger = tolower(trigger);
254 
255 	if (pos == NULL) {
256 		// take lowercase, too
257 		pos = strchr(Label(), trigger);
258 	}
259 
260 	if (pos != NULL) {
261 		fTriggerIndex = UTF8CountChars(Label(), pos - Label());
262 		fTrigger = trigger;
263 	} else {
264 		fTrigger = 0;
265 		fTriggerIndex = -1;
266 	}
267 
268 	if (fSuper != NULL)
269 		fSuper->InvalidateLayout();
270 }
271 
272 
273 void
274 BMenuItem::SetShortcut(char shortcut, uint32 modifiers)
275 {
276 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
277 		&& fWindow != NULL) {
278 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
279 	}
280 
281 	fShortcutChar = shortcut;
282 
283 	if (shortcut != 0)
284 		fModifiers = modifiers | B_COMMAND_KEY;
285 	else
286 		fModifiers = 0;
287 
288 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
289 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
290 
291 	if (fSuper != NULL) {
292 		fSuper->InvalidateLayout();
293 
294 		if (fSuper->LockLooper()) {
295 			fSuper->Invalidate();
296 			fSuper->UnlockLooper();
297 		}
298 	}
299 }
300 
301 
302 const char*
303 BMenuItem::Label() const
304 {
305 	return fLabel;
306 }
307 
308 
309 bool
310 BMenuItem::IsEnabled() const
311 {
312 	if (fSubmenu)
313 		return fSubmenu->IsEnabled();
314 
315 	if (!fEnabled)
316 		return false;
317 
318 	return fSuper != NULL ? fSuper->IsEnabled() : true;
319 }
320 
321 
322 bool
323 BMenuItem::IsMarked() const
324 {
325 	return fMark;
326 }
327 
328 
329 char
330 BMenuItem::Trigger() const
331 {
332 	return fUserTrigger;
333 }
334 
335 
336 char
337 BMenuItem::Shortcut(uint32* modifiers) const
338 {
339 	if (modifiers)
340 		*modifiers = fModifiers;
341 
342 	return fShortcutChar;
343 }
344 
345 
346 BMenu*
347 BMenuItem::Submenu() const
348 {
349 	return fSubmenu;
350 }
351 
352 
353 BMenu*
354 BMenuItem::Menu() const
355 {
356 	return fSuper;
357 }
358 
359 
360 BRect
361 BMenuItem::Frame() const
362 {
363 	return fBounds;
364 }
365 
366 
367 void
368 BMenuItem::GetContentSize(float* _width, float* _height)
369 {
370 	// TODO: Get rid of this. BMenu should handle this
371 	// automatically. Maybe it's not even needed, since our
372 	// BFont::Height() caches the value locally
373 	MenuPrivate(fSuper).CacheFontInfo();
374 
375 	fCachedWidth = fSuper->StringWidth(fLabel);
376 
377 	if (_width)
378 		*_width = (float)ceil(fCachedWidth);
379 	if (_height)
380 		*_height = MenuPrivate(fSuper).FontHeight();
381 }
382 
383 
384 void
385 BMenuItem::TruncateLabel(float maxWidth, char* newLabel)
386 {
387 	BFont font;
388 	fSuper->GetFont(&font);
389 
390 	BString string(fLabel);
391 
392 	font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth);
393 
394 	string.CopyInto(newLabel, 0, string.Length());
395 	newLabel[string.Length()] = '\0';
396 }
397 
398 
399 void
400 BMenuItem::DrawContent()
401 {
402 	MenuPrivate menuPrivate(fSuper);
403 	menuPrivate.CacheFontInfo();
404 
405 	fSuper->MovePenBy(0, menuPrivate.Ascent());
406 	BPoint lineStart = fSuper->PenLocation();
407 
408 	fSuper->SetDrawingMode(B_OP_OVER);
409 
410 	float labelWidth;
411 	float labelHeight;
412 	GetContentSize(&labelWidth, &labelHeight);
413 
414 	const BRect& padding = menuPrivate.Padding();
415 	float maxContentWidth = fSuper->MaxContentWidth();
416 	float frameWidth = maxContentWidth > 0 ? maxContentWidth
417 		: fSuper->Frame().Width() - padding.left - padding.right;
418 
419 	if (roundf(frameWidth) >= roundf(labelWidth))
420 		fSuper->DrawString(fLabel);
421 	else {
422 		// truncate label to fit
423 		char* truncatedLabel = new char[strlen(fLabel) + 4];
424 		TruncateLabel(frameWidth, truncatedLabel);
425 		fSuper->DrawString(truncatedLabel);
426 		delete[] truncatedLabel;
427 	}
428 
429 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
430 		float escapements[fTriggerIndex + 1];
431 		BFont font;
432 		fSuper->GetFont(&font);
433 
434 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
435 
436 		for (int32 i = 0; i < fTriggerIndex; i++)
437 			lineStart.x += escapements[i] * font.Size();
438 
439 		lineStart.x--;
440 		lineStart.y++;
441 
442 		BPoint lineEnd(lineStart);
443 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
444 
445 		fSuper->StrokeLine(lineStart, lineEnd);
446 	}
447 }
448 
449 
450 void
451 BMenuItem::Draw()
452 {
453 	const color_which lowColor = fSuper->LowUIColor();
454 	const color_which highColor = fSuper->HighUIColor();
455 
456 	fSuper->SetLowColor(_LowColor());
457 	fSuper->SetHighColor(_HighColor());
458 
459 	if (_IsActivated()) {
460 		// fill in the background
461 		BRect frame(Frame());
462 		be_control_look->DrawMenuItemBackground(fSuper, frame, frame,
463 			fSuper->LowColor(), BControlLook::B_ACTIVATED);
464 	}
465 
466 	// draw content
467 	fSuper->MovePenTo(ContentLocation());
468 	DrawContent();
469 
470 	// draw extra symbols
471 	MenuPrivate privateAccessor(fSuper);
472 	const menu_layout layout = privateAccessor.Layout();
473 	if (layout == B_ITEMS_IN_COLUMN) {
474 		if (IsMarked())
475 			_DrawMarkSymbol();
476 
477 		if (fShortcutChar)
478 			_DrawShortcutSymbol(privateAccessor.HasSubmenus());
479 
480 		if (Submenu() != NULL)
481 			_DrawSubmenuSymbol();
482 	}
483 
484 	// restore the parent menu's low color and high color
485 	fSuper->SetLowUIColor(lowColor);
486 	fSuper->SetHighUIColor(highColor);
487 }
488 
489 
490 void
491 BMenuItem::Highlight(bool highlight)
492 {
493 	fSuper->Invalidate(Frame());
494 }
495 
496 
497 bool
498 BMenuItem::IsSelected() const
499 {
500 	return fSelected;
501 }
502 
503 
504 BPoint
505 BMenuItem::ContentLocation() const
506 {
507 	const BRect& padding = MenuPrivate(fSuper).Padding();
508 
509 	return BPoint(fBounds.left + padding.left, fBounds.top + padding.top);
510 }
511 
512 
513 void BMenuItem::_ReservedMenuItem1() {}
514 void BMenuItem::_ReservedMenuItem2() {}
515 void BMenuItem::_ReservedMenuItem3() {}
516 void BMenuItem::_ReservedMenuItem4() {}
517 
518 
519 BMenuItem::BMenuItem(const BMenuItem &)
520 {
521 }
522 
523 
524 BMenuItem&
525 BMenuItem::operator=(const BMenuItem &)
526 {
527 	return *this;
528 }
529 
530 
531 void
532 BMenuItem::_InitData()
533 {
534 	fLabel = NULL;
535 	fSubmenu = NULL;
536 	fWindow = NULL;
537 	fSuper = NULL;
538 	fModifiers = 0;
539 	fCachedWidth = 0;
540 	fTriggerIndex = -1;
541 	fUserTrigger = 0;
542 	fTrigger = 0;
543 	fShortcutChar = 0;
544 	fMark = false;
545 	fEnabled = true;
546 	fSelected = false;
547 }
548 
549 
550 void
551 BMenuItem::_InitMenuData(BMenu* menu)
552 {
553 	fSubmenu = menu;
554 
555 	MenuPrivate(fSubmenu).SetSuperItem(this);
556 
557 	BMenuItem* item = menu->FindMarked();
558 
559 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
560 		SetLabel(item->Label());
561 	else
562 		SetLabel(menu->Name());
563 }
564 
565 
566 void
567 BMenuItem::Install(BWindow* window)
568 {
569 	if (fSubmenu != NULL)
570 		MenuPrivate(fSubmenu).Install(window);
571 
572 	fWindow = window;
573 
574 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
575 		window->AddShortcut(fShortcutChar, fModifiers, this);
576 
577 	if (!Messenger().IsValid())
578 		SetTarget(window);
579 }
580 
581 
582 status_t
583 BMenuItem::Invoke(BMessage* message)
584 {
585 	if (!IsEnabled())
586 		return B_ERROR;
587 
588 	if (fSuper->IsRadioMode())
589 		SetMarked(true);
590 
591 	bool notify = false;
592 	uint32 kind = InvokeKind(&notify);
593 
594 	BMessage clone(kind);
595 	status_t err = B_BAD_VALUE;
596 
597 	if (message == NULL && !notify)
598 		message = Message();
599 
600 	if (message == NULL) {
601 		if (!fSuper->IsWatched())
602 			return err;
603 	} else
604 		clone = *message;
605 
606 	clone.AddInt32("index", fSuper->IndexOf(this));
607 	clone.AddInt64("when", (int64)system_time());
608 	clone.AddPointer("source", this);
609 	clone.AddMessenger("be:sender", BMessenger(fSuper));
610 
611 	if (message != NULL)
612 		err = BInvoker::Invoke(&clone);
613 
614 //	TODO: assynchronous messaging
615 //	SendNotices(kind, &clone);
616 
617 	return err;
618 }
619 
620 
621 void
622 BMenuItem::Uninstall()
623 {
624 	if (fSubmenu != NULL)
625 		MenuPrivate(fSubmenu).Uninstall();
626 
627 	if (Target() == fWindow)
628 		SetTarget(BMessenger());
629 
630 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
631 		&& fWindow != NULL) {
632 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
633 	}
634 
635 	fWindow = NULL;
636 }
637 
638 
639 void
640 BMenuItem::SetSuper(BMenu* super)
641 {
642 	if (fSuper != NULL && super != NULL) {
643 		debugger("Error - can't add menu or menu item to more than 1 container"
644 			" (either menu or menubar).");
645 	}
646 
647 	if (fSubmenu != NULL)
648 		MenuPrivate(fSubmenu).SetSuper(super);
649 
650 	fSuper = super;
651 }
652 
653 
654 void
655 BMenuItem::Select(bool selected)
656 {
657 	if (fSelected == selected)
658 		return;
659 
660 	if (Submenu() != NULL || IsEnabled()) {
661 		fSelected = selected;
662 		Highlight(selected);
663 	}
664 }
665 
666 
667 bool
668 BMenuItem::_IsActivated()
669 {
670 	return IsSelected() && (IsEnabled() || fSubmenu != NULL);
671 }
672 
673 
674 rgb_color
675 BMenuItem::_LowColor()
676 {
677 	return _IsActivated() ? ui_color(B_MENU_SELECTED_BACKGROUND_COLOR)
678 		: ui_color(B_MENU_BACKGROUND_COLOR);
679 }
680 
681 
682 rgb_color
683 BMenuItem::_HighColor()
684 {
685 	rgb_color highColor;
686 
687 	bool isEnabled = IsEnabled();
688 	bool isSelected = IsSelected();
689 
690 	if (isEnabled && isSelected)
691 		highColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR);
692 	else if (isEnabled)
693 		highColor = ui_color(B_MENU_ITEM_TEXT_COLOR);
694 	else {
695 		rgb_color bgColor = fSuper->LowColor();
696 		if (bgColor.red + bgColor.green + bgColor.blue > 128 * 3)
697 			highColor = tint_color(bgColor, B_DISABLED_LABEL_TINT);
698 		else
699 			highColor = tint_color(bgColor, B_LIGHTEN_2_TINT);
700 	}
701 
702 	return highColor;
703 }
704 
705 
706 void
707 BMenuItem::_DrawMarkSymbol()
708 {
709 	fSuper->PushState();
710 
711 	BRect r(fBounds);
712 	float leftMargin;
713 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
714 	float gap = leftMargin / 4;
715 	r.right = r.left + leftMargin - gap;
716 	r.left += gap / 3;
717 
718 	BPoint center(floorf((r.left + r.right) / 2.0),
719 		floorf((r.top + r.bottom) / 2.0));
720 
721 	float size = std::min(r.Height() - 2, r.Width());
722 	r.top = floorf(center.y - size / 2 + 0.5);
723 	r.bottom = floorf(center.y + size / 2 + 0.5);
724 	r.left = floorf(center.x - size / 2 + 0.5);
725 	r.right = floorf(center.x + size / 2 + 0.5);
726 
727 	BShape arrowShape;
728 	center.x += 0.5;
729 	center.y += 0.5;
730 	size *= 0.3;
731 	arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25));
732 	arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size));
733 	arrowShape.LineTo(BPoint(center.x + size, center.y - size));
734 
735 	fSuper->SetHighColor(tint_color(_HighColor(), kMarkTint));
736 	fSuper->SetDrawingMode(B_OP_OVER);
737 	fSuper->SetPenSize(2.0);
738 	// NOTE: StrokeShape() offsets the shape by the current pen position,
739 	// it is not documented in the BeBook, but it is true!
740 	fSuper->MovePenTo(B_ORIGIN);
741 	fSuper->StrokeShape(&arrowShape);
742 
743 	fSuper->PopState();
744 }
745 
746 
747 void
748 BMenuItem::_DrawShortcutSymbol(bool submenus)
749 {
750 	BMenu* menu = fSuper;
751 	BFont font;
752 	menu->GetFont(&font);
753 	BPoint where = ContentLocation();
754 	// Start from the right and walk our way back
755 	where.x = fBounds.right - font.Size();
756 
757 	// Leave space for the submenu arrow if any item in the menu has a submenu
758 	if (submenus)
759 		where.x -= fBounds.Height() / 2;
760 
761 	const float ascent = MenuPrivate(fSuper).Ascent();
762 	if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
763 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
764 	else
765 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
766 
767 	where.y += (fBounds.Height() - 11) / 2 - 1;
768 	where.x -= 4;
769 
770 	// TODO: It would be nice to draw these taking into account the text (low)
771 	// color.
772 	if ((fModifiers & B_COMMAND_KEY) != 0) {
773 		const BBitmap* command = MenuPrivate::MenuItemCommand();
774 		const BRect &rect = command->Bounds();
775 		where.x -= rect.Width() + 1;
776 		fSuper->DrawBitmap(command, where);
777 	}
778 
779 	if ((fModifiers & B_CONTROL_KEY) != 0) {
780 		const BBitmap* control = MenuPrivate::MenuItemControl();
781 		const BRect &rect = control->Bounds();
782 		where.x -= rect.Width() + 1;
783 		fSuper->DrawBitmap(control, where);
784 	}
785 
786 	if ((fModifiers & B_OPTION_KEY) != 0) {
787 		const BBitmap* option = MenuPrivate::MenuItemOption();
788 		const BRect &rect = option->Bounds();
789 		where.x -= rect.Width() + 1;
790 		fSuper->DrawBitmap(option, where);
791 	}
792 
793 	if ((fModifiers & B_SHIFT_KEY) != 0) {
794 		const BBitmap* shift = MenuPrivate::MenuItemShift();
795 		const BRect &rect = shift->Bounds();
796 		where.x -= rect.Width() + 1;
797 		fSuper->DrawBitmap(shift, where);
798 	}
799 }
800 
801 
802 void
803 BMenuItem::_DrawSubmenuSymbol()
804 {
805 	fSuper->PushState();
806 
807 	float symbolSize = roundf(Frame().Height() * 2 / 3);
808 
809 	BRect rect(fBounds);
810 	rect.left = rect.right - symbolSize;
811 
812 	// 14px by default, scaled with font size up to right margin - padding
813 	BRect symbolRect(0, 0, symbolSize, symbolSize);
814 	symbolRect.OffsetTo(BPoint(rect.left,
815 		fBounds.top + (fBounds.Height() - symbolSize) / 2));
816 
817 	be_control_look->DrawArrowShape(Menu(), symbolRect, symbolRect,
818 		_HighColor(), BControlLook::B_RIGHT_ARROW, 0, kMarkTint);
819 
820 	fSuper->PopState();
821 }
822 
823 
824 void
825 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
826 {
827 	// TODO: If needed, take another font for the control characters
828 	//	(or have font overlays in the app_server!)
829 	const char* symbol = " ";
830 	if (kUTF8ControlMap[(int)fShortcutChar])
831 		symbol = kUTF8ControlMap[(int)fShortcutChar];
832 
833 	fSuper->DrawString(symbol, where);
834 }
835 
836 
837 void
838 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
839 {
840 	fTriggerIndex = index;
841 	fTrigger = trigger;
842 }
843