xref: /haiku/src/kits/interface/MenuItem.cpp (revision 2897df967633aab846ff4917b53e2af7d1e54eeb)
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 	if (fSuper != NULL)
185 		fSuper->RemoveItem(this);
186 
187 	free(fLabel);
188 	delete fSubmenu;
189 }
190 
191 
192 void
193 BMenuItem::SetLabel(const char* string)
194 {
195 	if (fLabel != NULL) {
196 		free(fLabel);
197 		fLabel = NULL;
198 	}
199 
200 	if (string != NULL)
201 		fLabel = strdup(string);
202 
203 	if (fSuper != NULL) {
204 		fSuper->InvalidateLayout();
205 
206 		if (fSuper->LockLooper()) {
207 			fSuper->Invalidate();
208 			fSuper->UnlockLooper();
209 		}
210 	}
211 }
212 
213 
214 void
215 BMenuItem::SetEnabled(bool enable)
216 {
217 	if (fEnabled == enable)
218 		return;
219 
220 	fEnabled = enable;
221 
222 	if (fSubmenu != NULL)
223 		fSubmenu->SetEnabled(enable);
224 
225 	BMenu* menu = fSuper;
226 	if (menu != NULL && menu->LockLooper()) {
227 		menu->Invalidate(fBounds);
228 		menu->UnlockLooper();
229 	}
230 }
231 
232 
233 void
234 BMenuItem::SetMarked(bool mark)
235 {
236 	fMark = mark;
237 
238 	if (mark && fSuper != NULL) {
239 		MenuPrivate priv(fSuper);
240 		priv.ItemMarked(this);
241 	}
242 }
243 
244 
245 void
246 BMenuItem::SetTrigger(char trigger)
247 {
248 	fUserTrigger = trigger;
249 
250 	// try uppercase letters first
251 
252 	const char* pos = strchr(Label(), toupper(trigger));
253 	trigger = tolower(trigger);
254 
255 	if (pos == NULL) {
256 		// take lowercase, too
257 		pos = strchr(Label(), trigger);
258 	}
259 
260 	if (pos != NULL) {
261 		fTriggerIndex = UTF8CountChars(Label(), pos - Label());
262 		fTrigger = trigger;
263 	} else {
264 		fTrigger = 0;
265 		fTriggerIndex = -1;
266 	}
267 
268 	if (fSuper != NULL)
269 		fSuper->InvalidateLayout();
270 }
271 
272 
273 void
274 BMenuItem::SetShortcut(char shortcut, uint32 modifiers)
275 {
276 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
277 		&& fWindow != NULL) {
278 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
279 	}
280 
281 	fShortcutChar = shortcut;
282 
283 	if (shortcut != 0)
284 		fModifiers = modifiers | B_COMMAND_KEY;
285 	else
286 		fModifiers = 0;
287 
288 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
289 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
290 
291 	if (fSuper != NULL) {
292 		fSuper->InvalidateLayout();
293 
294 		if (fSuper->LockLooper()) {
295 			fSuper->Invalidate();
296 			fSuper->UnlockLooper();
297 		}
298 	}
299 }
300 
301 
302 const char*
303 BMenuItem::Label() const
304 {
305 	return fLabel;
306 }
307 
308 
309 bool
310 BMenuItem::IsEnabled() const
311 {
312 	if (fSubmenu)
313 		return fSubmenu->IsEnabled();
314 
315 	if (!fEnabled)
316 		return false;
317 
318 	return fSuper != NULL ? fSuper->IsEnabled() : true;
319 }
320 
321 
322 bool
323 BMenuItem::IsMarked() const
324 {
325 	return fMark;
326 }
327 
328 
329 char
330 BMenuItem::Trigger() const
331 {
332 	return fUserTrigger;
333 }
334 
335 
336 char
337 BMenuItem::Shortcut(uint32* modifiers) const
338 {
339 	if (modifiers)
340 		*modifiers = fModifiers;
341 
342 	return fShortcutChar;
343 }
344 
345 
346 BMenu*
347 BMenuItem::Submenu() const
348 {
349 	return fSubmenu;
350 }
351 
352 
353 BMenu*
354 BMenuItem::Menu() const
355 {
356 	return fSuper;
357 }
358 
359 
360 BRect
361 BMenuItem::Frame() const
362 {
363 	return fBounds;
364 }
365 
366 
367 void
368 BMenuItem::GetContentSize(float* _width, float* _height)
369 {
370 	// TODO: Get rid of this. BMenu should handle this
371 	// automatically. Maybe it's not even needed, since our
372 	// BFont::Height() caches the value locally
373 	MenuPrivate(fSuper).CacheFontInfo();
374 
375 	fCachedWidth = fSuper->StringWidth(fLabel);
376 
377 	if (_width)
378 		*_width = (float)ceil(fCachedWidth);
379 	if (_height)
380 		*_height = MenuPrivate(fSuper).FontHeight();
381 }
382 
383 
384 void
385 BMenuItem::TruncateLabel(float maxWidth, char* newLabel)
386 {
387 	BFont font;
388 	fSuper->GetFont(&font);
389 
390 	BString string(fLabel);
391 
392 	font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth);
393 
394 	string.CopyInto(newLabel, 0, string.Length());
395 	newLabel[string.Length()] = '\0';
396 }
397 
398 
399 void
400 BMenuItem::DrawContent()
401 {
402 	MenuPrivate menuPrivate(fSuper);
403 	menuPrivate.CacheFontInfo();
404 
405 	fSuper->MovePenBy(0, menuPrivate.Ascent());
406 	BPoint lineStart = fSuper->PenLocation();
407 
408 	fSuper->SetDrawingMode(B_OP_OVER);
409 
410 	float labelWidth;
411 	float labelHeight;
412 	GetContentSize(&labelWidth, &labelHeight);
413 
414 	const BRect& padding = menuPrivate.Padding();
415 	float maxContentWidth = fSuper->MaxContentWidth();
416 	float frameWidth = maxContentWidth > 0 ? maxContentWidth
417 		: fSuper->Frame().Width() - padding.left - padding.right;
418 
419 	if (roundf(frameWidth) >= roundf(labelWidth))
420 		fSuper->DrawString(fLabel);
421 	else {
422 		// truncate label to fit
423 		char* truncatedLabel = new char[strlen(fLabel) + 4];
424 		TruncateLabel(frameWidth, truncatedLabel);
425 		fSuper->DrawString(truncatedLabel);
426 		delete[] truncatedLabel;
427 	}
428 
429 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
430 		float escapements[fTriggerIndex + 1];
431 		BFont font;
432 		fSuper->GetFont(&font);
433 
434 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
435 
436 		for (int32 i = 0; i < fTriggerIndex; i++)
437 			lineStart.x += escapements[i] * font.Size();
438 
439 		lineStart.x--;
440 		lineStart.y++;
441 
442 		BPoint lineEnd(lineStart);
443 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
444 
445 		fSuper->StrokeLine(lineStart, lineEnd);
446 	}
447 }
448 
449 
450 void
451 BMenuItem::Draw()
452 {
453 	const color_which lowColor = fSuper->LowUIColor();
454 	const color_which highColor = fSuper->HighUIColor();
455 
456 	bool enabled = IsEnabled();
457 	bool selected = IsSelected();
458 	bool activated = selected && (enabled || Submenu() != NULL);
459 
460 	// set low color
461 	if (activated) {
462 		fSuper->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
463 		// fill in the background
464 		BRect rect(Frame());
465 		be_control_look->DrawMenuItemBackground(fSuper, rect, Frame(),
466 			fSuper->LowColor(), BControlLook::B_ACTIVATED);
467 	} else
468 		fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
469 
470 	// set high color
471 	if (activated && enabled)
472 		fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
473 	else if (enabled)
474 		fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
475 	else {
476 		rgb_color bgColor = fSuper->LowColor();
477 		if (bgColor.red + bgColor.green + bgColor.blue > 128 * 3)
478 			fSuper->SetHighColor(tint_color(bgColor, B_DISABLED_LABEL_TINT));
479 		else
480 			fSuper->SetHighColor(tint_color(bgColor, B_LIGHTEN_2_TINT));
481 	}
482 
483 	// draw content
484 	fSuper->MovePenTo(ContentLocation());
485 	DrawContent();
486 
487 	// draw extra symbols
488 	const menu_layout layout = MenuPrivate(fSuper).Layout();
489 	if (layout == B_ITEMS_IN_COLUMN) {
490 		if (IsMarked())
491 			_DrawMarkSymbol();
492 
493 		if (fShortcutChar)
494 			_DrawShortcutSymbol();
495 
496 		if (Submenu() != NULL)
497 			_DrawSubmenuSymbol();
498 	}
499 
500 	// restore the parent menu's low color and high color
501 	fSuper->SetLowUIColor(lowColor);
502 	fSuper->SetHighUIColor(highColor);
503 }
504 
505 
506 void
507 BMenuItem::Highlight(bool highlight)
508 {
509 	fSuper->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, fBounds.top + padding.top);
526 }
527 
528 
529 void BMenuItem::_ReservedMenuItem1() {}
530 void BMenuItem::_ReservedMenuItem2() {}
531 void BMenuItem::_ReservedMenuItem3() {}
532 void BMenuItem::_ReservedMenuItem4() {}
533 
534 
535 BMenuItem::BMenuItem(const BMenuItem &)
536 {
537 }
538 
539 
540 BMenuItem&
541 BMenuItem::operator=(const BMenuItem &)
542 {
543 	return *this;
544 }
545 
546 
547 void
548 BMenuItem::_InitData()
549 {
550 	fLabel = NULL;
551 	fSubmenu = NULL;
552 	fWindow = NULL;
553 	fSuper = NULL;
554 	fModifiers = 0;
555 	fCachedWidth = 0;
556 	fTriggerIndex = -1;
557 	fUserTrigger = 0;
558 	fTrigger = 0;
559 	fShortcutChar = 0;
560 	fMark = false;
561 	fEnabled = true;
562 	fSelected = false;
563 }
564 
565 
566 void
567 BMenuItem::_InitMenuData(BMenu* menu)
568 {
569 	fSubmenu = menu;
570 
571 	MenuPrivate(fSubmenu).SetSuperItem(this);
572 
573 	BMenuItem* item = menu->FindMarked();
574 
575 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
576 		SetLabel(item->Label());
577 	else
578 		SetLabel(menu->Name());
579 }
580 
581 
582 void
583 BMenuItem::Install(BWindow* window)
584 {
585 	if (fSubmenu != NULL)
586 		MenuPrivate(fSubmenu).Install(window);
587 
588 	fWindow = window;
589 
590 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
591 		window->AddShortcut(fShortcutChar, fModifiers, this);
592 
593 	if (!Messenger().IsValid())
594 		SetTarget(window);
595 }
596 
597 
598 status_t
599 BMenuItem::Invoke(BMessage* message)
600 {
601 	if (!IsEnabled())
602 		return B_ERROR;
603 
604 	if (fSuper->IsRadioMode())
605 		SetMarked(true);
606 
607 	bool notify = false;
608 	uint32 kind = InvokeKind(&notify);
609 
610 	BMessage clone(kind);
611 	status_t err = B_BAD_VALUE;
612 
613 	if (message == NULL && !notify)
614 		message = Message();
615 
616 	if (message == NULL) {
617 		if (!fSuper->IsWatched())
618 			return err;
619 	} else
620 		clone = *message;
621 
622 	clone.AddInt32("index", fSuper->IndexOf(this));
623 	clone.AddInt64("when", (int64)system_time());
624 	clone.AddPointer("source", this);
625 	clone.AddMessenger("be:sender", BMessenger(fSuper));
626 
627 	if (message != NULL)
628 		err = BInvoker::Invoke(&clone);
629 
630 //	TODO: assynchronous messaging
631 //	SendNotices(kind, &clone);
632 
633 	return err;
634 }
635 
636 
637 void
638 BMenuItem::Uninstall()
639 {
640 	if (fSubmenu != NULL)
641 		MenuPrivate(fSubmenu).Uninstall();
642 
643 	if (Target() == fWindow)
644 		SetTarget(BMessenger());
645 
646 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
647 		&& fWindow != NULL) {
648 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
649 	}
650 
651 	fWindow = NULL;
652 }
653 
654 
655 void
656 BMenuItem::SetSuper(BMenu* super)
657 {
658 	if (fSuper != NULL && super != NULL) {
659 		debugger("Error - can't add menu or menu item to more than 1 container"
660 			" (either menu or menubar).");
661 	}
662 
663 	if (fSubmenu != NULL)
664 		MenuPrivate(fSubmenu).SetSuper(super);
665 
666 	fSuper = super;
667 }
668 
669 
670 void
671 BMenuItem::Select(bool selected)
672 {
673 	if (fSelected == selected)
674 		return;
675 
676 	if (Submenu() != NULL || IsEnabled()) {
677 		fSelected = selected;
678 		Highlight(selected);
679 	}
680 }
681 
682 
683 void
684 BMenuItem::_DrawMarkSymbol()
685 {
686 	fSuper->PushState();
687 
688 	BRect r(fBounds);
689 	float leftMargin;
690 	MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
691 	float gap = leftMargin / 4;
692 	r.right = r.left + leftMargin - gap;
693 	r.left += gap / 3;
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->SetPenSize(2.0);
714 	// NOTE: StrokeShape() offsets the shape by the current pen position,
715 	// it is not documented in the BeBook, but it is true!
716 	fSuper->MovePenTo(B_ORIGIN);
717 	fSuper->StrokeShape(&arrowShape);
718 
719 	fSuper->PopState();
720 }
721 
722 
723 void
724 BMenuItem::_DrawShortcutSymbol()
725 {
726 	BMenu* menu = fSuper;
727 	BFont font;
728 	menu->GetFont(&font);
729 	BPoint where = ContentLocation();
730 	where.x = fBounds.right - font.Size();
731 
732 	if (fSubmenu)
733 		where.x -= fBounds.Height() - 3;
734 
735 	const float ascent = MenuPrivate(fSuper).Ascent();
736 	if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar])
737 		_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
738 	else
739 		fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
740 
741 	where.y += (fBounds.Height() - 11) / 2 - 1;
742 	where.x -= 4;
743 
744 	// TODO: It would be nice to draw these taking into account the text (low)
745 	// color.
746 	if ((fModifiers & B_COMMAND_KEY) != 0) {
747 		const BBitmap* command = MenuPrivate::MenuItemCommand();
748 		const BRect &rect = command->Bounds();
749 		where.x -= rect.Width() + 1;
750 		fSuper->DrawBitmap(command, where);
751 	}
752 
753 	if ((fModifiers & B_CONTROL_KEY) != 0) {
754 		const BBitmap* control = MenuPrivate::MenuItemControl();
755 		const BRect &rect = control->Bounds();
756 		where.x -= rect.Width() + 1;
757 		fSuper->DrawBitmap(control, where);
758 	}
759 
760 	if ((fModifiers & B_OPTION_KEY) != 0) {
761 		const BBitmap* option = MenuPrivate::MenuItemOption();
762 		const BRect &rect = option->Bounds();
763 		where.x -= rect.Width() + 1;
764 		fSuper->DrawBitmap(option, where);
765 	}
766 
767 	if ((fModifiers & B_SHIFT_KEY) != 0) {
768 		const BBitmap* shift = MenuPrivate::MenuItemShift();
769 		const BRect &rect = shift->Bounds();
770 		where.x -= rect.Width() + 1;
771 		fSuper->DrawBitmap(shift, where);
772 	}
773 }
774 
775 
776 void
777 BMenuItem::_DrawSubmenuSymbol()
778 {
779 	fSuper->PushState();
780 
781 	BRect r(fBounds);
782 	float rightMargin;
783 	MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
784 	r.left = r.right - rightMargin + 3;
785 	r.right -= 1;
786 
787 	BPoint center(floorf((r.left + r.right) / 2.0),
788 		floorf((r.top + r.bottom) / 2.0));
789 
790 	float size = min_c(r.Height() - 2, r.Width());
791 	r.top = floorf(center.y - size / 2 + 0.5);
792 	r.bottom = floorf(center.y + size / 2 + 0.5);
793 	r.left = floorf(center.x - size / 2 + 0.5);
794 	r.right = floorf(center.x + size / 2 + 0.5);
795 
796 	BShape arrowShape;
797 	center.x += 0.5;
798 	center.y += 0.5;
799 	size *= 0.25;
800 	float hSize = size * 0.7;
801 	arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size));
802 	arrowShape.LineTo(BPoint(center.x + hSize, center.y));
803 	arrowShape.LineTo(BPoint(center.x - hSize, center.y + size));
804 
805 	fSuper->SetDrawingMode(B_OP_OVER);
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