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