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