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