xref: /haiku/src/kits/interface/MenuItem.cpp (revision 372a66634410cf0450e426716c14ad42d40c0da4)
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 maxContentWidth = fSuper->MaxContentWidth();
410 	float frameWidth = maxContentWidth > 0 ? maxContentWidth
411 		: fSuper->Frame().Width() - padding.left - padding.right;
412 
413 	if (roundf(frameWidth) >= roundf(labelWidth))
414 		fSuper->DrawString(fLabel);
415 	else {
416 		// truncate label to fit
417 		char* truncatedLabel = new char[strlen(fLabel) + 4];
418 		TruncateLabel(frameWidth, truncatedLabel);
419 		fSuper->DrawString(truncatedLabel);
420 		delete[] truncatedLabel;
421 	}
422 
423 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
424 		float escapements[fTriggerIndex + 1];
425 		BFont font;
426 		fSuper->GetFont(&font);
427 
428 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
429 
430 		for (int32 i = 0; i < fTriggerIndex; i++)
431 			lineStart.x += escapements[i] * font.Size();
432 
433 		lineStart.x--;
434 		lineStart.y++;
435 
436 		BPoint lineEnd(lineStart);
437 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
438 
439 		fSuper->StrokeLine(lineStart, lineEnd);
440 	}
441 }
442 
443 
444 void
445 BMenuItem::Draw()
446 {
447 	const rgb_color lowColor = fSuper->LowColor();
448 	const rgb_color highColor = fSuper->HighColor();
449 
450 	bool enabled = IsEnabled();
451 	bool selected = IsSelected();
452 	bool activated = selected && (enabled || Submenu() != NULL);
453 
454 	// set low color
455 	if (activated) {
456 		fSuper->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
457 		// fill in the background
458 		BRect rect(Frame());
459 		be_control_look->DrawMenuItemBackground(fSuper, rect, Frame(),
460 			fSuper->LowColor(), BControlLook::B_ACTIVATED);
461 	} else
462 		fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
463 
464 	// set high color
465 	if (activated && enabled)
466 		fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
467 	else if (enabled)
468 		fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
469 	else {
470 		rgb_color bgColor = fSuper->LowColor();
471 		if (bgColor.red + bgColor.green + bgColor.blue > 128 * 3)
472 			fSuper->SetHighColor(tint_color(bgColor, B_DISABLED_LABEL_TINT));
473 		else
474 			fSuper->SetHighColor(tint_color(bgColor, B_LIGHTEN_2_TINT));
475 	}
476 
477 	// draw content
478 	fSuper->MovePenTo(ContentLocation());
479 	DrawContent();
480 
481 	// draw extra symbols
482 	const menu_layout layout = MenuPrivate(fSuper).Layout();
483 	if (layout == B_ITEMS_IN_COLUMN) {
484 		if (IsMarked())
485 			_DrawMarkSymbol();
486 
487 		if (fShortcutChar)
488 			_DrawShortcutSymbol();
489 
490 		if (Submenu())
491 			_DrawSubmenuSymbol();
492 	}
493 
494 	// restore the parent menu's low color and high color
495 	fSuper->SetLowColor(lowColor);
496 	fSuper->SetHighColor(highColor);
497 }
498 
499 
500 void
501 BMenuItem::Highlight(bool flag)
502 {
503 	fSuper->Invalidate(Frame());
504 }
505 
506 
507 bool
508 BMenuItem::IsSelected() const
509 {
510 	return fSelected;
511 }
512 
513 
514 BPoint
515 BMenuItem::ContentLocation() const
516 {
517 	const BRect& padding = MenuPrivate(fSuper).Padding();
518 
519 	return BPoint(fBounds.left + padding.left, fBounds.top + padding.top);
520 }
521 
522 
523 void BMenuItem::_ReservedMenuItem1() {}
524 void BMenuItem::_ReservedMenuItem2() {}
525 void BMenuItem::_ReservedMenuItem3() {}
526 void BMenuItem::_ReservedMenuItem4() {}
527 
528 
529 BMenuItem::BMenuItem(const BMenuItem &)
530 {
531 }
532 
533 
534 BMenuItem&
535 BMenuItem::operator=(const BMenuItem &)
536 {
537 	return *this;
538 }
539 
540 
541 void
542 BMenuItem::_InitData()
543 {
544 	fLabel = NULL;
545 	fSubmenu = NULL;
546 	fWindow = NULL;
547 	fSuper = NULL;
548 	fModifiers = 0;
549 	fCachedWidth = 0;
550 	fTriggerIndex = -1;
551 	fUserTrigger = 0;
552 	fTrigger = 0;
553 	fShortcutChar = 0;
554 	fMark = false;
555 	fEnabled = true;
556 	fSelected = false;
557 }
558 
559 
560 void
561 BMenuItem::_InitMenuData(BMenu* menu)
562 {
563 	fSubmenu = menu;
564 
565 	MenuPrivate(fSubmenu).SetSuperItem(this);
566 
567 	BMenuItem* item = menu->FindMarked();
568 
569 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
570 		SetLabel(item->Label());
571 	else
572 		SetLabel(menu->Name());
573 }
574 
575 
576 void
577 BMenuItem::Install(BWindow* window)
578 {
579 	if (fSubmenu != NULL)
580 		MenuPrivate(fSubmenu).Install(window);
581 
582 	fWindow = window;
583 
584 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
585 		window->AddShortcut(fShortcutChar, fModifiers, this);
586 
587 	if (!Messenger().IsValid())
588 		SetTarget(window);
589 }
590 
591 
592 status_t
593 BMenuItem::Invoke(BMessage* message)
594 {
595 	if (!IsEnabled())
596 		return B_ERROR;
597 
598 	if (fSuper->IsRadioMode())
599 		SetMarked(true);
600 
601 	bool notify = false;
602 	uint32 kind = InvokeKind(&notify);
603 
604 	BMessage clone(kind);
605 	status_t err = B_BAD_VALUE;
606 
607 	if (message == NULL && !notify)
608 		message = Message();
609 
610 	if (message == NULL) {
611 		if (!fSuper->IsWatched())
612 			return err;
613 	} else
614 		clone = *message;
615 
616 	clone.AddInt32("index", fSuper->IndexOf(this));
617 	clone.AddInt64("when", (int64)system_time());
618 	clone.AddPointer("source", this);
619 	clone.AddMessenger("be:sender", BMessenger(fSuper));
620 
621 	if (message != NULL)
622 		err = BInvoker::Invoke(&clone);
623 
624 //	TODO: assynchronous messaging
625 //	SendNotices(kind, &clone);
626 
627 	return err;
628 }
629 
630 
631 void
632 BMenuItem::Uninstall()
633 {
634 	if (fSubmenu != NULL)
635 		MenuPrivate(fSubmenu).Uninstall();
636 
637 	if (Target() == fWindow)
638 		SetTarget(BMessenger());
639 
640 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
641 		&& fWindow != NULL) {
642 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
643 	}
644 
645 	fWindow = NULL;
646 }
647 
648 
649 void
650 BMenuItem::SetSuper(BMenu* super)
651 {
652 	if (fSuper != NULL && super != NULL) {
653 		debugger("Error - can't add menu or menu item to more than 1 container"
654 			" (either menu or menubar).");
655 	}
656 
657 	if (fSubmenu != NULL)
658 		MenuPrivate(fSubmenu).SetSuper(super);
659 
660 	fSuper = super;
661 }
662 
663 
664 void
665 BMenuItem::Select(bool selected)
666 {
667 	if (fSelected == selected)
668 		return;
669 
670 	if (Submenu() != NULL || IsEnabled()) {
671 		fSelected = selected;
672 		Highlight(selected);
673 	}
674 }
675 
676 
677 void
678 BMenuItem::_DrawMarkSymbol()
679 {
680 	fSuper->PushState();
681 
682 	BRect r(fBounds);
683 	float leftMargin;
684 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
685 	r.right = r.left + leftMargin - 3;
686 	r.left += 1;
687 
688 	BPoint center(floorf((r.left + r.right) / 2.0),
689 		floorf((r.top + r.bottom) / 2.0));
690 
691 	float size = min_c(r.Height() - 2, r.Width());
692 	r.top = floorf(center.y - size / 2 + 0.5);
693 	r.bottom = floorf(center.y + size / 2 + 0.5);
694 	r.left = floorf(center.x - size / 2 + 0.5);
695 	r.right = floorf(center.x + size / 2 + 0.5);
696 
697 	BShape arrowShape;
698 	center.x += 0.5;
699 	center.y += 0.5;
700 	size *= 0.3;
701 	arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25));
702 	arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size));
703 	arrowShape.LineTo(BPoint(center.x + size, center.y - size));
704 
705 	fSuper->SetDrawingMode(B_OP_OVER);
706 	fSuper->SetPenSize(2.0);
707 	// NOTE: StrokeShape() offsets the shape by the current pen position,
708 	// it is not documented in the BeBook, but it is true!
709 	fSuper->MovePenTo(B_ORIGIN);
710 	fSuper->StrokeShape(&arrowShape);
711 
712 	fSuper->PopState();
713 }
714 
715 
716 void
717 BMenuItem::_DrawShortcutSymbol()
718 {
719 	BMenu* menu = fSuper;
720 	BFont font;
721 	menu->GetFont(&font);
722 	BPoint where = ContentLocation();
723 	where.x = fBounds.right - font.Size();
724 
725 	if (fSubmenu)
726 		where.x -= fBounds.Height() - 3;
727 
728 	const float ascent = MenuPrivate(fSuper).Ascent();
729 	if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
730 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
731 	else
732 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
733 
734 	where.y += (fBounds.Height() - 11) / 2 - 1;
735 	where.x -= 4;
736 
737 	// TODO: It would be nice to draw these taking into account the text (low)
738 	// color.
739 	if (fModifiers & B_COMMAND_KEY) {
740 		const BBitmap *command = MenuPrivate::MenuItemCommand();
741 		const BRect &rect = command->Bounds();
742 		where.x -= rect.Width() + 1;
743 		fSuper->DrawBitmap(command, where);
744 	}
745 
746 	if (fModifiers & B_CONTROL_KEY) {
747 		const BBitmap *control = MenuPrivate::MenuItemControl();
748 		const BRect &rect = control->Bounds();
749 		where.x -= rect.Width() + 1;
750 		fSuper->DrawBitmap(control, where);
751 	}
752 
753 	if (fModifiers & B_OPTION_KEY) {
754 		const BBitmap *option = MenuPrivate::MenuItemOption();
755 		const BRect &rect = option->Bounds();
756 		where.x -= rect.Width() + 1;
757 		fSuper->DrawBitmap(option, where);
758 	}
759 
760 	if (fModifiers & B_SHIFT_KEY) {
761 		const BBitmap *shift = MenuPrivate::MenuItemShift();
762 		const BRect &rect = shift->Bounds();
763 		where.x -= rect.Width() + 1;
764 		fSuper->DrawBitmap(shift, where);
765 	}
766 }
767 
768 
769 void
770 BMenuItem::_DrawSubmenuSymbol()
771 {
772 	fSuper->PushState();
773 
774 	BRect r(fBounds);
775 	float rightMargin;
776 	MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
777 	r.left = r.right - rightMargin + 3;
778 	r.right -= 1;
779 
780 	BPoint center(floorf((r.left + r.right) / 2.0),
781 		floorf((r.top + r.bottom) / 2.0));
782 
783 	float size = min_c(r.Height() - 2, r.Width());
784 	r.top = floorf(center.y - size / 2 + 0.5);
785 	r.bottom = floorf(center.y + size / 2 + 0.5);
786 	r.left = floorf(center.x - size / 2 + 0.5);
787 	r.right = floorf(center.x + size / 2 + 0.5);
788 
789 	BShape arrowShape;
790 	center.x += 0.5;
791 	center.y += 0.5;
792 	size *= 0.25;
793 	float hSize = size * 0.7;
794 	arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size));
795 	arrowShape.LineTo(BPoint(center.x + hSize, center.y));
796 	arrowShape.LineTo(BPoint(center.x - hSize, center.y + size));
797 
798 	fSuper->SetDrawingMode(B_OP_OVER);
799 	fSuper->SetPenSize(ceilf(size * 0.4));
800 	// NOTE: StrokeShape() offsets the shape by the current pen position,
801 	// it is not documented in the BeBook, but it is true!
802 	fSuper->MovePenTo(B_ORIGIN);
803 	fSuper->StrokeShape(&arrowShape);
804 
805 	fSuper->PopState();
806 }
807 
808 
809 void
810 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
811 {
812 	// TODO: If needed, take another font for the control characters
813 	//	(or have font overlays in the app_server!)
814 	const char* symbol = " ";
815 	if (kUTF8ControlMap[(int)fShortcutChar])
816 		symbol = kUTF8ControlMap[(int)fShortcutChar];
817 
818 	fSuper->DrawString(symbol, where);
819 }
820 
821 
822 void
823 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
824 {
825 	fTriggerIndex = index;
826 	fTrigger = trigger;
827 }
828