xref: /haiku/src/kits/interface/MenuItem.cpp (revision c90684742e7361651849be4116d0e5de3a817194)
1 /*
2  * Copyright 2001-2008, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Bill Hayden (haydentech@users.sourceforge.net)
8  *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
9  *		Olivier Milla
10  */
11 
12 //!	Display item for BMenu class
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 state)
211 {
212 	if (fEnabled == state)
213 		return;
214 
215 	fEnabled = state;
216 
217 	if (fSubmenu != NULL)
218 		fSubmenu->SetEnabled(state);
219 
220 	BMenu *menu = Menu();
221 	if (menu != NULL && menu->LockLooper()) {
222 		menu->Invalidate(fBounds);
223 		menu->UnlockLooper();
224 	}
225 }
226 
227 
228 void
229 BMenuItem::SetMarked(bool state)
230 {
231 	fMark = state;
232 
233 	if (state && Menu() != NULL) {
234 		MenuPrivate priv(Menu());
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 ch, uint32 modifiers)
270 {
271 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
272 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
273 
274 	fShortcutChar = ch;
275 
276 	if (ch != 0)
277 		fModifiers = modifiers | B_COMMAND_KEY;
278 	else
279 		fModifiers = 0;
280 
281 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
282 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
283 
284 	if (fSuper) {
285 		fSuper->InvalidateLayout();
286 
287 		if (fSuper->LockLooper()) {
288 			fSuper->Invalidate();
289 			fSuper->UnlockLooper();
290 		}
291 	}
292 }
293 
294 
295 const char *
296 BMenuItem::Label() const
297 {
298 	return fLabel;
299 }
300 
301 
302 bool
303 BMenuItem::IsEnabled() const
304 {
305 	if (fSubmenu)
306 		return fSubmenu->IsEnabled();
307 
308 	if (!fEnabled)
309 		return false;
310 
311 	return fSuper ? fSuper->IsEnabled() : true;
312 }
313 
314 
315 bool
316 BMenuItem::IsMarked() const
317 {
318 	return fMark;
319 }
320 
321 
322 char
323 BMenuItem::Trigger() const
324 {
325 	return fUserTrigger;
326 }
327 
328 
329 char
330 BMenuItem::Shortcut(uint32 *modifiers) const
331 {
332 	if (modifiers)
333 		*modifiers = fModifiers;
334 
335 	return fShortcutChar;
336 }
337 
338 
339 BMenu *
340 BMenuItem::Submenu() const
341 {
342 	return fSubmenu;
343 }
344 
345 
346 BMenu *
347 BMenuItem::Menu() const
348 {
349 	return fSuper;
350 }
351 
352 
353 BRect
354 BMenuItem::Frame() const
355 {
356 	return fBounds;
357 }
358 
359 
360 void
361 BMenuItem::GetContentSize(float *width, float *height)
362 {
363 	// TODO: Get rid of this. BMenu should handle this
364 	// automatically. Maybe it's not even needed, since our
365 	// BFont::Height() caches the value locally
366 	MenuPrivate(fSuper).CacheFontInfo();
367 
368 	fCachedWidth = fSuper->StringWidth(fLabel);
369 
370 	if (width)
371 		*width = (float)ceil(fCachedWidth);
372 	if (height) {
373 		*height = MenuPrivate(fSuper).FontHeight();
374 	}
375 }
376 
377 
378 void
379 BMenuItem::TruncateLabel(float maxWidth, char *newLabel)
380 {
381 	BFont font;
382 	BString string(fLabel);
383 
384 	font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth);
385 
386 	string.CopyInto(newLabel, 0, string.Length());
387 	newLabel[string.Length()] = '\0';
388 }
389 
390 
391 void
392 BMenuItem::DrawContent()
393 {
394 	MenuPrivate menuPrivate(fSuper);
395 	menuPrivate.CacheFontInfo();
396 
397 	fSuper->MovePenBy(0, menuPrivate.Ascent());
398 	BPoint lineStart = fSuper->PenLocation();
399 
400 	float labelWidth, labelHeight;
401 	GetContentSize(&labelWidth, &labelHeight);
402 
403 	fSuper->SetDrawingMode(B_OP_OVER);
404 
405 	float frameWidth = fBounds.Width();
406 	if (menuPrivate.State() == MENU_STATE_CLOSED) {
407 		float rightMargin, leftMargin;
408 		menuPrivate.GetItemMargins(&leftMargin, NULL, &rightMargin, NULL);
409 		frameWidth = fSuper->Frame().Width() - (rightMargin + leftMargin);
410 	}
411 
412 	// truncate if needed
413 	if (frameWidth > labelWidth)
414 		fSuper->DrawString(fLabel);
415 	else {
416 		char *truncatedLabel = new char[strlen(fLabel) + 4];
417 		TruncateLabel(frameWidth, truncatedLabel);
418 		fSuper->DrawString(truncatedLabel);
419 		delete[] truncatedLabel;
420 	}
421 
422 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
423 		float escapements[fTriggerIndex + 1];
424 		BFont font;
425 		fSuper->GetFont(&font);
426 
427 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
428 
429 		for (int32 i = 0; i < fTriggerIndex; i++)
430 			lineStart.x += escapements[i] * font.Size();
431 
432 		lineStart.x--;
433 		lineStart.y++;
434 
435 		BPoint lineEnd(lineStart);
436 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
437 
438 		fSuper->StrokeLine(lineStart, lineEnd);
439 	}
440 }
441 
442 
443 void
444 BMenuItem::Draw()
445 {
446 	bool enabled = IsEnabled();
447 	bool selected = IsSelected();
448 
449 	rgb_color noTint = fSuper->LowColor();
450 	rgb_color bgColor = noTint;
451 
452 	// set low color and fill background if selected
453 	bool activated = selected && (enabled || Submenu())
454 		/*&& fSuper->fRedrawAfterSticky*/;
455 	if (activated) {
456 		bgColor = tint_color(bgColor, B_DARKEN_3_TINT);
457 		if (be_control_look != NULL) {
458 			BRect rect = Frame();
459 			be_control_look->DrawMenuItemBackground(fSuper, rect, rect,
460 				noTint, BControlLook::B_ACTIVATED);
461 		} else {
462 			fSuper->SetLowColor(bgColor);
463 			fSuper->FillRect(Frame(), B_SOLID_LOW);
464 		}
465 	} else {
466 		fSuper->SetLowColor(bgColor);
467 	}
468 
469 	// set high color
470 	if (be_control_look != NULL) {
471 		if (enabled) {
472 			fSuper->SetHighColor(tint_color(fSuper->LowColor(),
473 				B_DARKEN_MAX_TINT));
474 		} else {
475 			fSuper->SetHighColor(tint_color(fSuper->LowColor(),
476 				B_DISABLED_LABEL_TINT));
477 		}
478 	} else {
479 		if (enabled)
480 			fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
481 		else
482 			fSuper->SetHighColor(tint_color(bgColor, B_DISABLED_LABEL_TINT));
483 	}
484 
485 	// draw content
486 	fSuper->MovePenTo(ContentLocation());
487 	DrawContent();
488 
489 	// draw extra symbols
490 	const menu_layout layout = MenuPrivate(fSuper).Layout();
491 	if (layout == B_ITEMS_IN_COLUMN) {
492 		if (IsMarked())
493 			_DrawMarkSymbol(bgColor);
494 
495 		if (fShortcutChar)
496 			_DrawShortcutSymbol();
497 
498 		if (Submenu())
499 			_DrawSubmenuSymbol(bgColor);
500 	}
501 
502 	fSuper->SetLowColor(noTint);
503 }
504 
505 
506 void
507 BMenuItem::Highlight(bool flag)
508 {
509 	Menu()->Invalidate(Frame());
510 }
511 
512 
513 bool
514 BMenuItem::IsSelected() const
515 {
516 	return fSelected;
517 }
518 
519 
520 BPoint
521 BMenuItem::ContentLocation() const
522 {
523 	const BRect &padding = MenuPrivate(fSuper).Padding();
524 
525 	return BPoint(fBounds.left + padding.left,
526 		fBounds.top + padding.top);
527 }
528 
529 
530 void BMenuItem::_ReservedMenuItem1() {}
531 void BMenuItem::_ReservedMenuItem2() {}
532 void BMenuItem::_ReservedMenuItem3() {}
533 void BMenuItem::_ReservedMenuItem4() {}
534 
535 
536 BMenuItem::BMenuItem(const BMenuItem &)
537 {
538 }
539 
540 
541 BMenuItem &
542 BMenuItem::operator=(const BMenuItem &)
543 {
544 	return *this;
545 }
546 
547 
548 void
549 BMenuItem::_InitData()
550 {
551 	fLabel = NULL;
552 	fSubmenu = NULL;
553 	fWindow = NULL;
554 	fSuper = NULL;
555 	fModifiers = 0;
556 	fCachedWidth = 0;
557 	fTriggerIndex = -1;
558 	fUserTrigger = 0;
559 	fTrigger = 0;
560 	fShortcutChar = 0;
561 	fMark = false;
562 	fEnabled = true;
563 	fSelected = false;
564 }
565 
566 
567 void
568 BMenuItem::_InitMenuData(BMenu *menu)
569 {
570 	fSubmenu = menu;
571 
572 	MenuPrivate(fSubmenu).SetSuperItem(this);
573 
574 	BMenuItem *item = menu->FindMarked();
575 
576 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
577 		SetLabel(item->Label());
578 	else
579 		SetLabel(menu->Name());
580 }
581 
582 
583 void
584 BMenuItem::Install(BWindow *window)
585 {
586 	if (fSubmenu) {
587 		MenuPrivate(fSubmenu).Install(window);
588 	}
589 
590 	fWindow = window;
591 
592 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
593 		window->AddShortcut(fShortcutChar, fModifiers, this);
594 
595 	if (!Messenger().IsValid())
596 		SetTarget(window);
597 }
598 
599 
600 status_t
601 BMenuItem::Invoke(BMessage *message)
602 {
603 	if (!IsEnabled())
604 		return B_ERROR;
605 
606 	if (fSuper->IsRadioMode())
607 		SetMarked(true);
608 
609 	bool notify = false;
610 	uint32 kind = InvokeKind(&notify);
611 
612 	BMessage clone(kind);
613 	status_t err = B_BAD_VALUE;
614 
615 	if (!message && !notify)
616 		message = Message();
617 
618 	if (!message) {
619 		if (!fSuper->IsWatched())
620 			return err;
621 	} else
622 		clone = *message;
623 
624 	clone.AddInt32("index", Menu()->IndexOf(this));
625 	clone.AddInt64("when", (int64)system_time());
626 	clone.AddPointer("source", this);
627 	clone.AddMessenger("be:sender", BMessenger(fSuper));
628 
629 	if (message)
630 		err = BInvoker::Invoke(&clone);
631 
632 //	TODO: assynchronous messaging
633 //	SendNotices(kind, &clone);
634 
635 	return err;
636 }
637 
638 
639 void
640 BMenuItem::Uninstall()
641 {
642 	if (fSubmenu != NULL) {
643 		MenuPrivate(fSubmenu).Uninstall();
644 	}
645 
646 	if (Target() == fWindow)
647 		SetTarget(BMessenger());
648 
649 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
650 		&& fWindow != NULL)
651 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
652 
653 	fWindow = NULL;
654 }
655 
656 
657 void
658 BMenuItem::SetSuper(BMenu *super)
659 {
660 	if (fSuper != NULL && super != NULL)
661 		debugger("Error - can't add menu or menu item to more than 1 container (either menu or menubar).");
662 
663 	if (fSubmenu != NULL) {
664 		MenuPrivate(fSubmenu).SetSuper(super);
665 	}
666 
667 	fSuper = super;
668 }
669 
670 
671 void
672 BMenuItem::Select(bool selected)
673 {
674 	if (fSelected == selected)
675 		return;
676 
677 	if (Submenu() || IsEnabled()) {
678 		fSelected = selected;
679 		Highlight(selected);
680 	}
681 }
682 
683 
684 void
685 BMenuItem::_DrawMarkSymbol(rgb_color bgColor)
686 {
687 	fSuper->PushState();
688 
689 	BRect r(fBounds);
690 	float leftMargin;
691 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
692 	r.right = r.left + leftMargin - 3;
693 	r.left += 1;
694 
695 	BPoint center(floorf((r.left + r.right) / 2.0),
696 		floorf((r.top + r.bottom) / 2.0));
697 
698 	float size = min_c(r.Height() - 2, r.Width());
699 	r.top = floorf(center.y - size / 2 + 0.5);
700 	r.bottom = floorf(center.y + size / 2 + 0.5);
701 	r.left = floorf(center.x - size / 2 + 0.5);
702 	r.right = floorf(center.x + size / 2 + 0.5);
703 
704 	BShape arrowShape;
705 	center.x += 0.5;
706 	center.y += 0.5;
707 	size *= 0.3;
708 	arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25));
709 	arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size));
710 	arrowShape.LineTo(BPoint(center.x + size, center.y - size));
711 
712 	fSuper->SetDrawingMode(B_OP_OVER);
713 	fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT));
714 	fSuper->SetPenSize(2.0);
715 	// NOTE: StrokeShape() offsets the shape by the current pen position,
716 	// it is not documented in the BeBook, but it is true!
717 	fSuper->MovePenTo(B_ORIGIN);
718 	fSuper->StrokeShape(&arrowShape);
719 
720 	fSuper->PopState();
721 }
722 
723 
724 void
725 BMenuItem::_DrawShortcutSymbol()
726 {
727 	BMenu *menu = Menu();
728 	BFont font;
729 	menu->GetFont(&font);
730 	BPoint where = ContentLocation();
731 	where.x = fBounds.right - font.Size();
732 
733 	if (fSubmenu)
734 		where.x -= fBounds.Height() - 3;
735 
736 	const float ascent = MenuPrivate(fSuper).Ascent();
737 	if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
738 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
739 	else
740 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
741 
742 	where.y += (fBounds.Height() - 11) / 2 - 1;
743 	where.x -= 4;
744 
745 	if (fModifiers & B_COMMAND_KEY) {
746 		const BBitmap *command = MenuPrivate::MenuItemCommand();
747 		const BRect &rect = command->Bounds();
748 		where.x -= rect.Width() + 1;
749 		fSuper->DrawBitmap(command, where);
750 	}
751 
752 	if (fModifiers & B_CONTROL_KEY) {
753 		const BBitmap *control = MenuPrivate::MenuItemControl();
754 		const BRect &rect = control->Bounds();
755 		where.x -= rect.Width() + 1;
756 		fSuper->DrawBitmap(control, where);
757 	}
758 
759 	if (fModifiers & B_OPTION_KEY) {
760 		const BBitmap *option = MenuPrivate::MenuItemOption();
761 		const BRect &rect = option->Bounds();
762 		where.x -= rect.Width() + 1;
763 		fSuper->DrawBitmap(option, where);
764 	}
765 
766 	if (fModifiers & B_SHIFT_KEY) {
767 		const BBitmap *shift = MenuPrivate::MenuItemShift();
768 		const BRect &rect = shift->Bounds();
769 		where.x -= rect.Width() + 1;
770 		fSuper->DrawBitmap(shift, where);
771 	}
772 }
773 
774 
775 void
776 BMenuItem::_DrawSubmenuSymbol(rgb_color bgColor)
777 {
778 	fSuper->PushState();
779 
780 	BRect r(fBounds);
781 	float rightMargin;
782 	MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
783 	r.left = r.right - rightMargin + 3;
784 	r.right -= 1;
785 
786 	BPoint center(floorf((r.left + r.right) / 2.0),
787 		floorf((r.top + r.bottom) / 2.0));
788 
789 	float size = min_c(r.Height() - 2, r.Width());
790 	r.top = floorf(center.y - size / 2 + 0.5);
791 	r.bottom = floorf(center.y + size / 2 + 0.5);
792 	r.left = floorf(center.x - size / 2 + 0.5);
793 	r.right = floorf(center.x + size / 2 + 0.5);
794 
795 	BShape arrowShape;
796 	center.x += 0.5;
797 	center.y += 0.5;
798 	size *= 0.25;
799 	float hSize = size * 0.7;
800 	arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size));
801 	arrowShape.LineTo(BPoint(center.x + hSize, center.y));
802 	arrowShape.LineTo(BPoint(center.x - hSize, center.y + size));
803 
804 	fSuper->SetDrawingMode(B_OP_OVER);
805 	fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT));
806 	fSuper->SetPenSize(ceilf(size * 0.4));
807 	// NOTE: StrokeShape() offsets the shape by the current pen position,
808 	// it is not documented in the BeBook, but it is true!
809 	fSuper->MovePenTo(B_ORIGIN);
810 	fSuper->StrokeShape(&arrowShape);
811 
812 	fSuper->PopState();
813 }
814 
815 
816 void
817 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
818 {
819 	// TODO: If needed, take another font for the control characters
820 	//	(or have font overlays in the app_server!)
821 	const char* symbol = " ";
822 	if (kUTF8ControlMap[(int)fShortcutChar])
823 		symbol = kUTF8ControlMap[(int)fShortcutChar];
824 
825 	fSuper->DrawString(symbol, where);
826 }
827 
828 
829 void
830 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
831 {
832 	fTriggerIndex = index;
833 	fTrigger = trigger;
834 }
835