xref: /haiku/src/kits/interface/MenuItem.cpp (revision 2141d2fe3a5df2f55f3590f67660573b50d1d1d3)
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_ROW) {
477 		if (IsMarked())
478 			_DrawMarkSymbol();
479 	}
480 
481 	if (layout == B_ITEMS_IN_COLUMN) {
482 		if (fShortcutChar)
483 			_DrawShortcutSymbol(privateAccessor.HasSubmenus());
484 
485 		if (Submenu() != NULL)
486 			_DrawSubmenuSymbol();
487 	}
488 
489 	// restore the parent menu's low color and high color
490 	fSuper->SetLowUIColor(lowColor);
491 	fSuper->SetHighUIColor(highColor);
492 }
493 
494 
495 void
496 BMenuItem::Highlight(bool highlight)
497 {
498 	fSuper->Invalidate(Frame());
499 }
500 
501 
502 bool
503 BMenuItem::IsSelected() const
504 {
505 	return fSelected;
506 }
507 
508 
509 BPoint
510 BMenuItem::ContentLocation() const
511 {
512 	const BRect& padding = MenuPrivate(fSuper).Padding();
513 
514 	return BPoint(fBounds.left + padding.left, fBounds.top + padding.top);
515 }
516 
517 
518 void BMenuItem::_ReservedMenuItem1() {}
519 void BMenuItem::_ReservedMenuItem2() {}
520 void BMenuItem::_ReservedMenuItem3() {}
521 void BMenuItem::_ReservedMenuItem4() {}
522 
523 
524 BMenuItem::BMenuItem(const BMenuItem &)
525 {
526 }
527 
528 
529 BMenuItem&
530 BMenuItem::operator=(const BMenuItem &)
531 {
532 	return *this;
533 }
534 
535 
536 void
537 BMenuItem::_InitData()
538 {
539 	fLabel = NULL;
540 	fSubmenu = NULL;
541 	fWindow = NULL;
542 	fSuper = NULL;
543 	fModifiers = 0;
544 	fCachedWidth = 0;
545 	fTriggerIndex = -1;
546 	fUserTrigger = 0;
547 	fTrigger = 0;
548 	fShortcutChar = 0;
549 	fMark = false;
550 	fEnabled = true;
551 	fSelected = false;
552 }
553 
554 
555 void
556 BMenuItem::_InitMenuData(BMenu* menu)
557 {
558 	fSubmenu = menu;
559 
560 	MenuPrivate(fSubmenu).SetSuperItem(this);
561 
562 	BMenuItem* item = menu->FindMarked();
563 
564 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
565 		SetLabel(item->Label());
566 	else
567 		SetLabel(menu->Name());
568 }
569 
570 
571 void
572 BMenuItem::Install(BWindow* window)
573 {
574 	if (fSubmenu != NULL)
575 		MenuPrivate(fSubmenu).Install(window);
576 
577 	fWindow = window;
578 
579 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
580 		window->AddShortcut(fShortcutChar, fModifiers, this);
581 
582 	if (!Messenger().IsValid())
583 		SetTarget(window);
584 }
585 
586 
587 status_t
588 BMenuItem::Invoke(BMessage* message)
589 {
590 	if (!IsEnabled())
591 		return B_ERROR;
592 
593 	if (fSuper->IsRadioMode())
594 		SetMarked(true);
595 
596 	bool notify = false;
597 	uint32 kind = InvokeKind(&notify);
598 
599 	BMessage clone(kind);
600 	status_t err = B_BAD_VALUE;
601 
602 	if (message == NULL && !notify)
603 		message = Message();
604 
605 	if (message == NULL) {
606 		if (!fSuper->IsWatched())
607 			return err;
608 	} else
609 		clone = *message;
610 
611 	clone.AddInt32("index", fSuper->IndexOf(this));
612 	clone.AddInt64("when", (int64)system_time());
613 	clone.AddPointer("source", this);
614 	clone.AddMessenger("be:sender", BMessenger(fSuper));
615 
616 	if (message != NULL)
617 		err = BInvoker::Invoke(&clone);
618 
619 //	TODO: assynchronous messaging
620 //	SendNotices(kind, &clone);
621 
622 	return err;
623 }
624 
625 
626 void
627 BMenuItem::Uninstall()
628 {
629 	if (fSubmenu != NULL)
630 		MenuPrivate(fSubmenu).Uninstall();
631 
632 	if (Target() == fWindow)
633 		SetTarget(BMessenger());
634 
635 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
636 		&& fWindow != NULL) {
637 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
638 	}
639 
640 	fWindow = NULL;
641 }
642 
643 
644 void
645 BMenuItem::SetSuper(BMenu* super)
646 {
647 	if (fSuper != NULL && super != NULL) {
648 		debugger("Error - can't add menu or menu item to more than 1 container"
649 			" (either menu or menubar).");
650 	}
651 
652 	if (fSubmenu != NULL)
653 		MenuPrivate(fSubmenu).SetSuper(super);
654 
655 	fSuper = super;
656 }
657 
658 
659 void
660 BMenuItem::Select(bool selected)
661 {
662 	if (fSelected == selected)
663 		return;
664 
665 	if (Submenu() != NULL || IsEnabled()) {
666 		fSelected = selected;
667 		Highlight(selected);
668 	}
669 }
670 
671 
672 bool
673 BMenuItem::_IsActivated()
674 {
675 	return IsSelected() && (IsEnabled() || fSubmenu != NULL);
676 }
677 
678 
679 rgb_color
680 BMenuItem::_LowColor()
681 {
682 	return _IsActivated() ? ui_color(B_MENU_SELECTED_BACKGROUND_COLOR)
683 		: ui_color(B_MENU_BACKGROUND_COLOR);
684 }
685 
686 
687 rgb_color
688 BMenuItem::_HighColor()
689 {
690 	rgb_color highColor;
691 
692 	bool isEnabled = IsEnabled();
693 	bool isSelected = IsSelected();
694 
695 	if (isEnabled && isSelected)
696 		highColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR);
697 	else if (isEnabled)
698 		highColor = ui_color(B_MENU_ITEM_TEXT_COLOR);
699 	else {
700 		rgb_color bgColor = fSuper->LowColor();
701 		if (bgColor.red + bgColor.green + bgColor.blue > 128 * 3)
702 			highColor = tint_color(bgColor, B_DISABLED_LABEL_TINT);
703 		else
704 			highColor = tint_color(bgColor, B_LIGHTEN_2_TINT);
705 	}
706 
707 	return highColor;
708 }
709 
710 
711 void
712 BMenuItem::_DrawMarkSymbol()
713 {
714 	fSuper->PushState();
715 
716 	BRect r(fBounds);
717 	float leftMargin;
718 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
719 	float gap = leftMargin / 4;
720 	r.right = r.left + leftMargin - gap;
721 	r.left += gap / 3;
722 
723 	BPoint center(floorf((r.left + r.right) / 2.0),
724 		floorf((r.top + r.bottom) / 2.0));
725 
726 	float size = std::min(r.Height() - 2, r.Width());
727 	r.top = floorf(center.y - size / 2 + 0.5);
728 	r.bottom = floorf(center.y + size / 2 + 0.5);
729 	r.left = floorf(center.x - size / 2 + 0.5);
730 	r.right = floorf(center.x + size / 2 + 0.5);
731 
732 	BShape arrowShape;
733 	center.x += 0.5;
734 	center.y += 0.5;
735 	size *= 0.3;
736 	arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25));
737 	arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size));
738 	arrowShape.LineTo(BPoint(center.x + size, center.y - size));
739 
740 	fSuper->SetHighColor(tint_color(_HighColor(), kMarkTint));
741 	fSuper->SetDrawingMode(B_OP_OVER);
742 	fSuper->SetPenSize(2.0);
743 	// NOTE: StrokeShape() offsets the shape by the current pen position,
744 	// it is not documented in the BeBook, but it is true!
745 	fSuper->MovePenTo(B_ORIGIN);
746 	fSuper->StrokeShape(&arrowShape);
747 
748 	fSuper->PopState();
749 }
750 
751 
752 void
753 BMenuItem::_DrawShortcutSymbol(bool submenus)
754 {
755 	BMenu* menu = fSuper;
756 	BFont font;
757 	menu->GetFont(&font);
758 	BPoint where = ContentLocation();
759 	// Start from the right and walk our way back
760 	where.x = fBounds.right - font.Size();
761 
762 	// Leave space for the submenu arrow if any item in the menu has a submenu
763 	if (submenus)
764 		where.x -= fBounds.Height() / 2;
765 
766 	const float ascent = MenuPrivate(fSuper).Ascent();
767 	if ((fShortcutChar <= B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
768 		|| fShortcutChar == B_DELETE) {
769 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
770 	} else
771 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
772 
773 	where.y += (fBounds.Height() - 11) / 2 - 1;
774 	where.x -= 4;
775 
776 	// TODO: It would be nice to draw these taking into account the text (low)
777 	// color.
778 	if ((fModifiers & B_COMMAND_KEY) != 0) {
779 		const BBitmap* command = MenuPrivate::MenuItemCommand();
780 		const BRect &rect = command->Bounds();
781 		where.x -= rect.Width() + 1;
782 		fSuper->DrawBitmap(command, where);
783 	}
784 
785 	if ((fModifiers & B_CONTROL_KEY) != 0) {
786 		const BBitmap* control = MenuPrivate::MenuItemControl();
787 		const BRect &rect = control->Bounds();
788 		where.x -= rect.Width() + 1;
789 		fSuper->DrawBitmap(control, where);
790 	}
791 
792 	if ((fModifiers & B_OPTION_KEY) != 0) {
793 		const BBitmap* option = MenuPrivate::MenuItemOption();
794 		const BRect &rect = option->Bounds();
795 		where.x -= rect.Width() + 1;
796 		fSuper->DrawBitmap(option, where);
797 	}
798 
799 	if ((fModifiers & B_SHIFT_KEY) != 0) {
800 		const BBitmap* shift = MenuPrivate::MenuItemShift();
801 		const BRect &rect = shift->Bounds();
802 		where.x -= rect.Width() + 1;
803 		fSuper->DrawBitmap(shift, where);
804 	}
805 }
806 
807 
808 void
809 BMenuItem::_DrawSubmenuSymbol()
810 {
811 	fSuper->PushState();
812 
813 	float symbolSize = roundf(Frame().Height() * 2 / 3);
814 
815 	BRect rect(fBounds);
816 	rect.left = rect.right - symbolSize;
817 
818 	// 14px by default, scaled with font size up to right margin - padding
819 	BRect symbolRect(0, 0, symbolSize, symbolSize);
820 	symbolRect.OffsetTo(BPoint(rect.left,
821 		fBounds.top + (fBounds.Height() - symbolSize) / 2));
822 
823 	be_control_look->DrawArrowShape(Menu(), symbolRect, symbolRect,
824 		_HighColor(), BControlLook::B_RIGHT_ARROW, 0, kMarkTint);
825 
826 	fSuper->PopState();
827 }
828 
829 
830 void
831 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
832 {
833 	// TODO: If needed, take another font for the control characters
834 	//	(or have font overlays in the app_server!)
835 	const char* symbol = " ";
836 	if (shortcut == B_DELETE)
837 		symbol = kDeleteShortcutUTF8;
838 	else if (kUTF8ControlMap[(int)fShortcutChar])
839 		symbol = kUTF8ControlMap[(int)fShortcutChar];
840 
841 	fSuper->DrawString(symbol, where);
842 }
843 
844 
845 void
846 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
847 {
848 	fTriggerIndex = index;
849 	fTrigger = trigger;
850 }
851