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