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