xref: /haiku/src/kits/interface/MenuItem.cpp (revision ff2e520912416877692e36bb130608d1a92df078)
1 /*
2  * Copyright 2001-2013 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 //!	Display item for BMenu class
14 
15 #include <ctype.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 #include <Bitmap.h>
20 #include <ControlLook.h>
21 #include <MenuItem.h>
22 #include <Shape.h>
23 #include <String.h>
24 #include <Window.h>
25 
26 #include <MenuPrivate.h>
27 
28 #include "utf8_functions.h"
29 
30 
31 const float kLightBGTint = (B_LIGHTEN_1_TINT + B_LIGHTEN_1_TINT + B_NO_TINT) / 3.0;
32 
33 // map control key shortcuts to drawable Unicode characters
34 // cf. http://unicode.org/charts/PDF/U2190.pdf
35 const char *kUTF8ControlMap[] = {
36 	NULL,
37 	"\xe2\x86\xb8", /* B_HOME U+21B8 */
38 	NULL, NULL,
39 	NULL, /* B_END */
40 	NULL, /* B_INSERT */
41 	NULL, NULL,
42 	NULL, /* B_BACKSPACE */
43 	"\xe2\x86\xb9", /* B_TAB U+21B9 */
44 	"\xe2\x86\xb5", /* B_ENTER, U+21B5 */
45 	//"\xe2\x8f\x8e", /* B_ENTER, U+23CE it's the official one */
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 using BPrivate::MenuPrivate;
58 
59 BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut,
60 	uint32 modifiers)
61 {
62 	_InitData();
63 	if (label != NULL)
64 		fLabel = strdup(label);
65 
66 	SetMessage(message);
67 
68 	fShortcutChar = shortcut;
69 
70 	if (shortcut != 0)
71 		fModifiers = modifiers | B_COMMAND_KEY;
72 	else
73 		fModifiers = 0;
74 }
75 
76 
77 BMenuItem::BMenuItem(BMenu* menu, BMessage* message)
78 {
79 	_InitData();
80 	SetMessage(message);
81 	_InitMenuData(menu);
82 }
83 
84 
85 BMenuItem::BMenuItem(BMessage* data)
86 {
87 	_InitData();
88 
89 	if (data->HasString("_label")) {
90 		const char *string;
91 
92 		data->FindString("_label", &string);
93 		SetLabel(string);
94 	}
95 
96 	bool disable;
97 	if (data->FindBool("_disable", &disable) == B_OK)
98 		SetEnabled(!disable);
99 
100 	bool marked;
101 	if (data->FindBool("_marked", &marked) == B_OK)
102 		SetMarked(marked);
103 
104 	int32 userTrigger;
105 	if (data->FindInt32("_user_trig", &userTrigger) == B_OK)
106 		SetTrigger(userTrigger);
107 
108 	if (data->HasInt32("_shortcut")) {
109 		int32 shortcut, mods;
110 
111 		data->FindInt32("_shortcut", &shortcut);
112 		data->FindInt32("_mods", &mods);
113 
114 		SetShortcut(shortcut, mods);
115 	}
116 
117 	if (data->HasMessage("_msg")) {
118 		BMessage *msg = new BMessage;
119 		data->FindMessage("_msg", msg);
120 		SetMessage(msg);
121 	}
122 
123 	BMessage subMessage;
124 	if (data->FindMessage("_submenu", &subMessage) == B_OK) {
125 		BArchivable* object = instantiate_object(&subMessage);
126 		if (object != NULL) {
127 			BMenu* menu = dynamic_cast<BMenu *>(object);
128 			if (menu != NULL)
129 				_InitMenuData(menu);
130 		}
131 	}
132 }
133 
134 
135 BArchivable*
136 BMenuItem::Instantiate(BMessage* data)
137 {
138 	if (validate_instantiation(data, "BMenuItem"))
139 		return new BMenuItem(data);
140 
141 	return NULL;
142 }
143 
144 
145 status_t
146 BMenuItem::Archive(BMessage* data, bool deep) const
147 {
148 	status_t ret = BArchivable::Archive(data, deep);
149 
150 	if (ret == B_OK && fLabel)
151 		ret = data->AddString("_label", Label());
152 
153 	if (ret == B_OK && !IsEnabled())
154 		ret = data->AddBool("_disable", true);
155 
156 	if (ret == B_OK && IsMarked())
157 		ret = data->AddBool("_marked", true);
158 
159 	if (ret == B_OK && fUserTrigger)
160 		ret = data->AddInt32("_user_trig", fUserTrigger);
161 
162 	if (ret == B_OK && fShortcutChar) {
163 		ret = data->AddInt32("_shortcut", fShortcutChar);
164 		if (ret == B_OK)
165 			ret = data->AddInt32("_mods", fModifiers);
166 	}
167 
168 	if (ret == B_OK && Message())
169 		ret = data->AddMessage("_msg", Message());
170 
171 	if (ret == B_OK && deep && fSubmenu) {
172 		BMessage submenu;
173 		if (fSubmenu->Archive(&submenu, true) == B_OK)
174 			ret = data->AddMessage("_submenu", &submenu);
175 	}
176 
177 	return ret;
178 }
179 
180 
181 BMenuItem::~BMenuItem()
182 {
183 	free(fLabel);
184 	delete fSubmenu;
185 }
186 
187 
188 void
189 BMenuItem::SetLabel(const char *string)
190 {
191 	if (fLabel != NULL) {
192 		free(fLabel);
193 		fLabel = NULL;
194 	}
195 
196 	if (string != NULL)
197 		fLabel = strdup(string);
198 
199 	if (fSuper != NULL) {
200 		fSuper->InvalidateLayout();
201 
202 		if (fSuper->LockLooper()) {
203 			fSuper->Invalidate();
204 			fSuper->UnlockLooper();
205 		}
206 	}
207 }
208 
209 
210 void
211 BMenuItem::SetEnabled(bool state)
212 {
213 	if (fEnabled == state)
214 		return;
215 
216 	fEnabled = state;
217 
218 	if (fSubmenu != NULL)
219 		fSubmenu->SetEnabled(state);
220 
221 	BMenu* menu = fSuper;
222 	if (menu != NULL && menu->LockLooper()) {
223 		menu->Invalidate(fBounds);
224 		menu->UnlockLooper();
225 	}
226 }
227 
228 
229 void
230 BMenuItem::SetMarked(bool state)
231 {
232 	fMark = state;
233 
234 	if (state && fSuper != NULL) {
235 		MenuPrivate priv(fSuper);
236 		priv.ItemMarked(this);
237 	}
238 }
239 
240 
241 void
242 BMenuItem::SetTrigger(char trigger)
243 {
244 	fUserTrigger = trigger;
245 
246 	// try uppercase letters first
247 
248 	const char* pos = strchr(Label(), toupper(trigger));
249 	trigger = tolower(trigger);
250 
251 	if (pos == NULL) {
252 		// take lowercase, too
253 		pos = strchr(Label(), trigger);
254 	}
255 
256 	if (pos != NULL) {
257 		fTriggerIndex = UTF8CountChars(Label(), pos - Label());
258 		fTrigger = trigger;
259 	} else {
260 		fTrigger = 0;
261 		fTriggerIndex = -1;
262 	}
263 
264 	if (fSuper != NULL)
265 		fSuper->InvalidateLayout();
266 }
267 
268 
269 void
270 BMenuItem::SetShortcut(char ch, uint32 modifiers)
271 {
272 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
273 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
274 
275 	fShortcutChar = ch;
276 
277 	if (ch != 0)
278 		fModifiers = modifiers | B_COMMAND_KEY;
279 	else
280 		fModifiers = 0;
281 
282 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
283 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
284 
285 	if (fSuper) {
286 		fSuper->InvalidateLayout();
287 
288 		if (fSuper->LockLooper()) {
289 			fSuper->Invalidate();
290 			fSuper->UnlockLooper();
291 		}
292 	}
293 }
294 
295 
296 const char*
297 BMenuItem::Label() const
298 {
299 	return fLabel;
300 }
301 
302 
303 bool
304 BMenuItem::IsEnabled() const
305 {
306 	if (fSubmenu)
307 		return fSubmenu->IsEnabled();
308 
309 	if (!fEnabled)
310 		return false;
311 
312 	return fSuper != NULL ? fSuper->IsEnabled() : true;
313 }
314 
315 
316 bool
317 BMenuItem::IsMarked() const
318 {
319 	return fMark;
320 }
321 
322 
323 char
324 BMenuItem::Trigger() const
325 {
326 	return fUserTrigger;
327 }
328 
329 
330 char
331 BMenuItem::Shortcut(uint32* modifiers) const
332 {
333 	if (modifiers)
334 		*modifiers = fModifiers;
335 
336 	return fShortcutChar;
337 }
338 
339 
340 BMenu*
341 BMenuItem::Submenu() const
342 {
343 	return fSubmenu;
344 }
345 
346 
347 BMenu*
348 BMenuItem::Menu() const
349 {
350 	return fSuper;
351 }
352 
353 
354 BRect
355 BMenuItem::Frame() const
356 {
357 	return fBounds;
358 }
359 
360 
361 void
362 BMenuItem::GetContentSize(float* width, float* height)
363 {
364 	// TODO: Get rid of this. BMenu should handle this
365 	// automatically. Maybe it's not even needed, since our
366 	// BFont::Height() caches the value locally
367 	MenuPrivate(fSuper).CacheFontInfo();
368 
369 	fCachedWidth = fSuper->StringWidth(fLabel);
370 
371 	if (width)
372 		*width = (float)ceil(fCachedWidth);
373 	if (height)
374 		*height = MenuPrivate(fSuper).FontHeight();
375 }
376 
377 
378 void
379 BMenuItem::TruncateLabel(float maxWidth, char* newLabel)
380 {
381 	BFont font;
382 	fSuper->GetFont(&font);
383 
384 	BString string(fLabel);
385 
386 	font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth);
387 
388 	string.CopyInto(newLabel, 0, string.Length());
389 	newLabel[string.Length()] = '\0';
390 }
391 
392 
393 void
394 BMenuItem::DrawContent()
395 {
396 	MenuPrivate menuPrivate(fSuper);
397 	menuPrivate.CacheFontInfo();
398 
399 	fSuper->MovePenBy(0, menuPrivate.Ascent());
400 	BPoint lineStart = fSuper->PenLocation();
401 
402 	fSuper->SetDrawingMode(B_OP_OVER);
403 
404 	float labelWidth;
405 	float labelHeight;
406 	GetContentSize(&labelWidth, &labelHeight);
407 
408 	const BRect& padding = menuPrivate.Padding();
409 	float frameWidth = fSuper->Frame().Width() - padding.left - padding.right;
410 
411 	if (roundf(frameWidth) >= roundf(labelWidth))
412 		fSuper->DrawString(fLabel);
413 	else {
414 		// truncate label to fit
415 		char* truncatedLabel = new char[strlen(fLabel) + 4];
416 		TruncateLabel(frameWidth, truncatedLabel);
417 		fSuper->DrawString(truncatedLabel);
418 		delete[] truncatedLabel;
419 	}
420 
421 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
422 		float escapements[fTriggerIndex + 1];
423 		BFont font;
424 		fSuper->GetFont(&font);
425 
426 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
427 
428 		for (int32 i = 0; i < fTriggerIndex; i++)
429 			lineStart.x += escapements[i] * font.Size();
430 
431 		lineStart.x--;
432 		lineStart.y++;
433 
434 		BPoint lineEnd(lineStart);
435 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
436 
437 		fSuper->StrokeLine(lineStart, lineEnd);
438 	}
439 }
440 
441 
442 void
443 BMenuItem::Draw()
444 {
445 	rgb_color lowColor = fSuper->LowColor();
446 
447 	bool enabled = IsEnabled();
448 	bool selected = IsSelected();
449 
450 	// set low color and fill background if selected
451 	bool activated = selected && (enabled || Submenu());
452 	if (activated) {
453 		BRect rect = Frame();
454 		be_control_look->DrawMenuItemBackground(fSuper, rect, rect,
455 			ui_color(B_MENU_SELECTED_BACKGROUND_COLOR),
456 			BControlLook::B_ACTIVATED);
457 	}
458 
459 	// set high color
460 	if (activated)
461 		fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
462 	else if (enabled)
463 		fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
464 	else {
465 		// TODO: Use a lighten tint if the menu uses a dark background
466 		fSuper->SetHighColor(tint_color(lowColor, B_DISABLED_LABEL_TINT));
467 	}
468 
469 	// draw content
470 	fSuper->MovePenTo(ContentLocation());
471 	DrawContent();
472 
473 	// draw extra symbols
474 	const menu_layout layout = MenuPrivate(fSuper).Layout();
475 	if (layout == B_ITEMS_IN_COLUMN) {
476 		if (IsMarked())
477 			_DrawMarkSymbol();
478 
479 		if (fShortcutChar)
480 			_DrawShortcutSymbol();
481 
482 		if (Submenu())
483 			_DrawSubmenuSymbol();
484 	}
485 
486 	fSuper->SetLowColor(lowColor);
487 }
488 
489 
490 void
491 BMenuItem::Highlight(bool flag)
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 void
668 BMenuItem::_DrawMarkSymbol()
669 {
670 	fSuper->PushState();
671 
672 	BRect r(fBounds);
673 	float leftMargin;
674 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
675 	r.right = r.left + leftMargin - 3;
676 	r.left += 1;
677 
678 	BPoint center(floorf((r.left + r.right) / 2.0),
679 		floorf((r.top + r.bottom) / 2.0));
680 
681 	float size = min_c(r.Height() - 2, r.Width());
682 	r.top = floorf(center.y - size / 2 + 0.5);
683 	r.bottom = floorf(center.y + size / 2 + 0.5);
684 	r.left = floorf(center.x - size / 2 + 0.5);
685 	r.right = floorf(center.x + size / 2 + 0.5);
686 
687 	BShape arrowShape;
688 	center.x += 0.5;
689 	center.y += 0.5;
690 	size *= 0.3;
691 	arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25));
692 	arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size));
693 	arrowShape.LineTo(BPoint(center.x + size, center.y - size));
694 
695 	fSuper->SetDrawingMode(B_OP_OVER);
696 	fSuper->SetPenSize(2.0);
697 	// NOTE: StrokeShape() offsets the shape by the current pen position,
698 	// it is not documented in the BeBook, but it is true!
699 	fSuper->MovePenTo(B_ORIGIN);
700 	fSuper->StrokeShape(&arrowShape);
701 
702 	fSuper->PopState();
703 }
704 
705 
706 void
707 BMenuItem::_DrawShortcutSymbol()
708 {
709 	BMenu* menu = fSuper;
710 	BFont font;
711 	menu->GetFont(&font);
712 	BPoint where = ContentLocation();
713 	where.x = fBounds.right - font.Size();
714 
715 	if (fSubmenu)
716 		where.x -= fBounds.Height() - 3;
717 
718 	const float ascent = MenuPrivate(fSuper).Ascent();
719 	if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
720 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
721 	else
722 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
723 
724 	where.y += (fBounds.Height() - 11) / 2 - 1;
725 	where.x -= 4;
726 
727 	// TODO: It would be nice to draw these taking into account the text (low)
728 	// color.
729 	if (fModifiers & B_COMMAND_KEY) {
730 		const BBitmap *command = MenuPrivate::MenuItemCommand();
731 		const BRect &rect = command->Bounds();
732 		where.x -= rect.Width() + 1;
733 		fSuper->DrawBitmap(command, where);
734 	}
735 
736 	if (fModifiers & B_CONTROL_KEY) {
737 		const BBitmap *control = MenuPrivate::MenuItemControl();
738 		const BRect &rect = control->Bounds();
739 		where.x -= rect.Width() + 1;
740 		fSuper->DrawBitmap(control, where);
741 	}
742 
743 	if (fModifiers & B_OPTION_KEY) {
744 		const BBitmap *option = MenuPrivate::MenuItemOption();
745 		const BRect &rect = option->Bounds();
746 		where.x -= rect.Width() + 1;
747 		fSuper->DrawBitmap(option, where);
748 	}
749 
750 	if (fModifiers & B_SHIFT_KEY) {
751 		const BBitmap *shift = MenuPrivate::MenuItemShift();
752 		const BRect &rect = shift->Bounds();
753 		where.x -= rect.Width() + 1;
754 		fSuper->DrawBitmap(shift, where);
755 	}
756 }
757 
758 
759 void
760 BMenuItem::_DrawSubmenuSymbol()
761 {
762 	fSuper->PushState();
763 
764 	BRect r(fBounds);
765 	float rightMargin;
766 	MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
767 	r.left = r.right - rightMargin + 3;
768 	r.right -= 1;
769 
770 	BPoint center(floorf((r.left + r.right) / 2.0),
771 		floorf((r.top + r.bottom) / 2.0));
772 
773 	float size = min_c(r.Height() - 2, r.Width());
774 	r.top = floorf(center.y - size / 2 + 0.5);
775 	r.bottom = floorf(center.y + size / 2 + 0.5);
776 	r.left = floorf(center.x - size / 2 + 0.5);
777 	r.right = floorf(center.x + size / 2 + 0.5);
778 
779 	BShape arrowShape;
780 	center.x += 0.5;
781 	center.y += 0.5;
782 	size *= 0.25;
783 	float hSize = size * 0.7;
784 	arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size));
785 	arrowShape.LineTo(BPoint(center.x + hSize, center.y));
786 	arrowShape.LineTo(BPoint(center.x - hSize, center.y + size));
787 
788 	fSuper->SetDrawingMode(B_OP_OVER);
789 	fSuper->SetPenSize(ceilf(size * 0.4));
790 	// NOTE: StrokeShape() offsets the shape by the current pen position,
791 	// it is not documented in the BeBook, but it is true!
792 	fSuper->MovePenTo(B_ORIGIN);
793 	fSuper->StrokeShape(&arrowShape);
794 
795 	fSuper->PopState();
796 }
797 
798 
799 void
800 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
801 {
802 	// TODO: If needed, take another font for the control characters
803 	//	(or have font overlays in the app_server!)
804 	const char* symbol = " ";
805 	if (kUTF8ControlMap[(int)fShortcutChar])
806 		symbol = kUTF8ControlMap[(int)fShortcutChar];
807 
808 	fSuper->DrawString(symbol, where);
809 }
810 
811 
812 void
813 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
814 {
815 	fTriggerIndex = index;
816 	fTrigger = trigger;
817 }
818