xref: /haiku/src/kits/interface/MenuItem.cpp (revision fc1ca2da5cfcb00ffdf791606d5ae97fdd58a638)
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 <MenuItem.h>
20 #include <Shape.h>
21 #include <String.h>
22 #include <Window.h>
23 
24 #include <MenuPrivate.h>
25 
26 #include "utf8_functions.h"
27 
28 const unsigned char kCtrlBits[] = {
29 	0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x14,
30 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
31 	0x1d,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
32 	0x1d,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,
33 	0x1d,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,
34 	0x1d,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,
35 	0x1d,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,
36 	0x1d,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x04,0x04,0x1a,0x17,0x14,
37 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
38 	0x1d,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x14,
39 	0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14
40 };
41 
42 
43 const unsigned char kAltBits[] = {
44 	0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x14,
45 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
46 	0x1d,0x1a,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
47 	0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x04,0x04,0x04,0x1a,0x17,0x14,
48 	0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
49 	0x1d,0x1a,0x1a,0x04,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
50 	0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
51 	0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x17,0x14,
52 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
53 	0x1d,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x14,
54 	0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14
55 };
56 
57 
58 const unsigned char kShiftBits[] = {
59 	0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x14,
60 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
61 	0x1d,0x1a,0x1a,0x17,0x04,0x04,0x17,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
62 	0x1d,0x1a,0x1a,0x04,0x17,0x17,0x04,0x1a,0x04,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x04,0x04,0x1a,0x17,0x14,
63 	0x1d,0x1a,0x1a,0x17,0x04,0x04,0x17,0x1a,0x04,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
64 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
65 	0x1d,0x1a,0x1a,0x04,0x17,0x17,0x04,0x1a,0x04,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
66 	0x1d,0x1a,0x1a,0x17,0x04,0x04,0x17,0x1a,0x04,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,
67 	0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,
68 	0x1d,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x14,
69 	0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14
70 };
71 
72 const float kLightBGTint = (B_LIGHTEN_1_TINT + B_LIGHTEN_1_TINT + B_NO_TINT) / 3.0;
73 
74 using BPrivate::MenuPrivate;
75 
76 BMenuItem::BMenuItem(const char *label, BMessage *message, char shortcut,
77 					 uint32 modifiers)
78 {
79 	_InitData();
80 	if (label != NULL)
81 		fLabel = strdup(label);
82 
83 	SetMessage(message);
84 
85 	fShortcutChar = shortcut;
86 
87 	if (shortcut != 0)
88 		fModifiers = modifiers | B_COMMAND_KEY;
89 	else
90 		fModifiers = 0;
91 }
92 
93 
94 BMenuItem::BMenuItem(BMenu *menu, BMessage *message)
95 {
96 	_InitData();
97 	SetMessage(message);
98 	_InitMenuData(menu);
99 }
100 
101 
102 BMenuItem::BMenuItem(BMessage *data)
103 {
104 	_InitData();
105 
106 	if (data->HasString("_label")) {
107 		const char *string;
108 
109 		data->FindString("_label", &string);
110 		SetLabel(string);
111 	}
112 
113 	bool disable;
114 	if (data->FindBool("_disable", &disable) == B_OK)
115 		SetEnabled(!disable);
116 
117 	bool marked;
118 	if (data->FindBool("_marked", &marked) == B_OK)
119 		SetMarked(marked);
120 
121 	int32 userTrigger;
122 	if (data->FindInt32("_user_trig", &userTrigger) == B_OK)
123 		SetTrigger(userTrigger);
124 
125 	if (data->HasInt32("_shortcut")) {
126 		int32 shortcut, mods;
127 
128 		data->FindInt32("_shortcut", &shortcut);
129 		data->FindInt32("_mods", &mods);
130 
131 		SetShortcut(shortcut, mods);
132 	}
133 
134 	if (data->HasMessage("_msg")) {
135 		BMessage *msg = new BMessage;
136 
137 		data->FindMessage("_msg", msg);
138 		SetMessage(msg);
139 	}
140 
141 	BMessage subMessage;
142 	if (data->FindMessage("_submenu", &subMessage) == B_OK) {
143 		BArchivable *object = instantiate_object(&subMessage);
144 		if (object != NULL) {
145 			BMenu *menu = dynamic_cast<BMenu *>(object);
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 		MenuPrivate priv(Menu());
255 		priv.ItemMarked(this);
256 	}
257 }
258 
259 
260 void
261 BMenuItem::SetTrigger(char trigger)
262 {
263 	fUserTrigger = trigger;
264 
265 	// try uppercase letters first
266 
267 	const char* pos = strchr(Label(), toupper(trigger));
268 	if (pos == NULL) {
269 		// take lowercase, too
270 		pos = strchr(Label(), trigger);
271 	}
272 	if (pos != NULL) {
273 		fTriggerIndex = UTF8CountChars(Label(), pos - Label());
274 		fTrigger = tolower(UTF8ToCharCode(&pos));
275 	} else {
276 		fTrigger = 0;
277 		fTriggerIndex = -1;
278 	}
279 
280 	if (fSuper != NULL)
281 		fSuper->InvalidateLayout();
282 }
283 
284 
285 void
286 BMenuItem::SetShortcut(char ch, uint32 modifiers)
287 {
288 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
289 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
290 
291 	fShortcutChar = ch;
292 
293 	if (ch != 0)
294 		fModifiers = modifiers | B_COMMAND_KEY;
295 	else
296 		fModifiers = 0;
297 
298 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
299 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
300 
301 	if (fSuper) {
302 		fSuper->InvalidateLayout();
303 
304 		if (fSuper->LockLooper()) {
305 			fSuper->Invalidate();
306 			fSuper->UnlockLooper();
307 		}
308 	}
309 }
310 
311 
312 const char *
313 BMenuItem::Label() const
314 {
315 	return fLabel;
316 }
317 
318 
319 bool
320 BMenuItem::IsEnabled() const
321 {
322 	if (fSubmenu)
323 		return fSubmenu->IsEnabled();
324 
325 	if (!fEnabled)
326 		return false;
327 
328 	return fSuper ? fSuper->IsEnabled() : true;
329 }
330 
331 
332 bool
333 BMenuItem::IsMarked() const
334 {
335 	return fMark;
336 }
337 
338 
339 char
340 BMenuItem::Trigger() const
341 {
342 	return fUserTrigger;
343 }
344 
345 
346 char
347 BMenuItem::Shortcut(uint32 *modifiers) const
348 {
349 	if (modifiers)
350 		*modifiers = fModifiers;
351 
352 	return fShortcutChar;
353 }
354 
355 
356 BMenu *
357 BMenuItem::Submenu() const
358 {
359 	return fSubmenu;
360 }
361 
362 
363 BMenu *
364 BMenuItem::Menu() const
365 {
366 	return fSuper;
367 }
368 
369 
370 BRect
371 BMenuItem::Frame() const
372 {
373 	return fBounds;
374 }
375 
376 
377 void
378 BMenuItem::GetContentSize(float *width, float *height)
379 {
380 	// TODO: Get rid of this. BMenu should handle this
381 	// automatically. Maybe it's not even needed, since our
382 	// BFont::Height() caches the value locally
383 	MenuPrivate(fSuper).CacheFontInfo();
384 
385 	fCachedWidth = fSuper->StringWidth(fLabel);
386 
387 	if (width)
388 		*width = (float)ceil(fCachedWidth);
389 	if (height) {
390 		*height = MenuPrivate(fSuper).FontHeight();
391 	}
392 }
393 
394 
395 void
396 BMenuItem::TruncateLabel(float maxWidth, char *newLabel)
397 {
398 	BFont font;
399 	BString string(fLabel);
400 
401 	font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth);
402 
403 	string.CopyInto(newLabel, 0, string.Length());
404 	newLabel[string.Length()] = '\0';
405 }
406 
407 
408 void
409 BMenuItem::DrawContent()
410 {
411 	MenuPrivate(fSuper).CacheFontInfo();
412 
413 	fSuper->MovePenBy(0, MenuPrivate(fSuper).Ascent());
414 	BPoint lineStart = fSuper->PenLocation();
415 
416 	float labelWidth, labelHeight;
417 	GetContentSize(&labelWidth, &labelHeight);
418 
419 	// truncate if needed
420 	// TODO: Actually, this is still never triggered
421 	if (fBounds.Width() > labelWidth)
422 		fSuper->DrawString(fLabel);
423 	else {
424 		char *truncatedLabel = new char[strlen(fLabel) + 4];
425 		TruncateLabel(fBounds.Width(), truncatedLabel);
426 		fSuper->DrawString(truncatedLabel);
427 		delete[] truncatedLabel;
428 	}
429 
430 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
431 		float escapements[fTriggerIndex + 1];
432 		BFont font;
433 		fSuper->GetFont(&font);
434 
435 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
436 
437 		for (int32 i = 0; i < fTriggerIndex; i++)
438 			lineStart.x += escapements[i] * font.Size();
439 
440 		lineStart.x--;
441 		lineStart.y++;
442 
443 		BPoint lineEnd(lineStart);
444 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
445 
446 		fSuper->StrokeLine(lineStart, lineEnd);
447 	}
448 }
449 
450 
451 void
452 BMenuItem::Draw()
453 {
454 	bool enabled = IsEnabled();
455 	bool selected = IsSelected();
456 
457 //	rgb_color noTint = ui_color(B_MENU_BACKGROUND_COLOR);
458 // TODO: the above is currently broken, because ui_color is
459 // not informed of changes to the app_server palette yet
460 	rgb_color noTint = fSuper->LowColor();
461 	rgb_color bgColor = noTint;
462 
463 	// set low color and fill background if selected
464 	if (selected && (enabled || Submenu()) /*&& fSuper->fRedrawAfterSticky*/) {
465 //		bgColor = ui_color(B_MENU_SELECTED_BACKGROUND_COLOR);
466 // see above
467 		bgColor = tint_color(bgColor, B_DARKEN_3_TINT);
468 		fSuper->SetLowColor(bgColor);
469 		fSuper->FillRect(Frame(), B_SOLID_LOW);
470 	} else
471 		fSuper->SetLowColor(bgColor);
472 
473 	// set high color
474 	if (enabled)
475 		fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
476 	else
477 		fSuper->SetHighColor(tint_color(bgColor, B_DISABLED_LABEL_TINT));
478 
479 	// draw content
480 	fSuper->MovePenTo(ContentLocation());
481 	DrawContent();
482 
483 	// draw extra symbols
484 	const menu_layout layout = MenuPrivate(fSuper).Layout();
485 	if (layout == B_ITEMS_IN_COLUMN) {
486 		if (IsMarked())
487 			_DrawMarkSymbol(bgColor);
488 
489 		if (fShortcutChar)
490 			_DrawShortcutSymbol();
491 
492 		if (Submenu())
493 			_DrawSubmenuSymbol(bgColor);
494 	}
495 
496 	fSuper->SetLowColor(noTint);
497 }
498 
499 
500 void
501 BMenuItem::Highlight(bool flag)
502 {
503 	Menu()->Invalidate(Frame());
504 }
505 
506 
507 bool
508 BMenuItem::IsSelected() const
509 {
510 	return fSelected;
511 }
512 
513 
514 BPoint
515 BMenuItem::ContentLocation() const
516 {
517 	const BRect &padding = MenuPrivate(fSuper).Padding();
518 
519 	return BPoint(fBounds.left + padding.left,
520 		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) {
581 		MenuPrivate(fSubmenu).Install(window);
582 	}
583 
584 	fWindow = window;
585 
586 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
587 		window->AddShortcut(fShortcutChar, fModifiers, this);
588 
589 	if (!Messenger().IsValid())
590 		SetTarget(window);
591 }
592 
593 
594 status_t
595 BMenuItem::Invoke(BMessage *message)
596 {
597 	if (!IsEnabled())
598 		return B_ERROR;
599 
600 	if (fSuper->IsRadioMode())
601 		SetMarked(true);
602 
603 	bool notify = false;
604 	uint32 kind = InvokeKind(&notify);
605 
606 	BMessage clone(kind);
607 	status_t err = B_BAD_VALUE;
608 
609 	if (!message && !notify)
610 		message = Message();
611 
612 	if (!message) {
613 		if (!fSuper->IsWatched())
614 			return err;
615 	} else
616 		clone = *message;
617 
618 	clone.AddInt32("index", Menu()->IndexOf(this));
619 	clone.AddInt64("when", (int64)system_time());
620 	clone.AddPointer("source", this);
621 	clone.AddMessenger("be:sender", BMessenger(fSuper));
622 
623 	if (message)
624 		err = BInvoker::Invoke(&clone);
625 
626 //	TODO: assynchronous messaging
627 //	SendNotices(kind, &clone);
628 
629 	return err;
630 }
631 
632 
633 void
634 BMenuItem::Uninstall()
635 {
636 	if (fSubmenu != NULL) {
637 		MenuPrivate(fSubmenu).Uninstall();
638 	}
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 	fWindow = NULL;
648 }
649 
650 
651 void
652 BMenuItem::SetSuper(BMenu *super)
653 {
654 	if (fSuper != NULL && super != NULL)
655 		debugger("Error - can't add menu or menu item to more than 1 container (either menu or menubar).");
656 
657 	if (fSubmenu != NULL) {
658 		MenuPrivate(fSubmenu).SetSuper(super);
659 	}
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() || IsEnabled()) {
672 		fSelected = selected;
673 		Highlight(selected);
674 	}
675 }
676 
677 
678 void
679 BMenuItem::_DrawMarkSymbol(rgb_color bgColor)
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 	fSuper->SetHighColor(tint_color(bgColor, kLightBGTint));
699 	fSuper->FillRoundRect(r, 2, 2);
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->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT));
711 	fSuper->SetPenSize(2.0);
712 	// NOTE: StrokeShape() offsets the shape by the current pen position,
713 	// it is not documented in the BeBook, but it is true!
714 	fSuper->MovePenTo(B_ORIGIN);
715 	fSuper->StrokeShape(&arrowShape);
716 
717 	fSuper->PopState();
718 }
719 
720 
721 void
722 BMenuItem::_DrawShortcutSymbol()
723 {
724 	BMenu *menu = Menu();
725 	BFont font;
726 	menu->GetFont(&font);
727 	BPoint where = ContentLocation();
728 	where.x = fBounds.right - font.Size();
729 
730 	if (fSubmenu)
731 		where.x -= fBounds.Height() - 3;
732 
733 	const float ascent = MenuPrivate(fSuper).Ascent();
734 	switch (fShortcutChar) {
735 		case B_DOWN_ARROW:
736 		case B_UP_ARROW:
737 		case B_LEFT_ARROW:
738 		case B_RIGHT_ARROW:
739 		case B_ENTER:
740 			_DrawControlChar(fShortcutChar, where + BPoint(0, ascent));
741 			break;
742 
743 		default:
744 			fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
745 			break;
746 	}
747 
748 	where.y += (fBounds.Height() - 11) / 2 - 1;
749 	where.x -= 4;
750 
751 	const bool altCommandKey = MenuPrivate(fSuper).IsAltCommandKey();
752 	if (fModifiers & B_COMMAND_KEY) {
753 		BRect rect(0,0,16,10);
754 		BBitmap control(rect, B_CMAP8);
755 
756 		if (altCommandKey)
757 			control.ImportBits(kAltBits, sizeof(kAltBits), 17, 0, B_CMAP8);
758 		else
759 			control.ImportBits(kCtrlBits, sizeof(kCtrlBits), 17, 0, B_CMAP8);
760 
761 		where.x -= rect.Width() + 1;
762 		fSuper->DrawBitmap(&control, where);
763 	}
764 
765 	if (fModifiers & B_CONTROL_KEY) {
766 		BRect rect(0,0,16,10);
767 		BBitmap control(rect, B_CMAP8);
768 
769 		if (altCommandKey)
770 			control.ImportBits(kCtrlBits, sizeof(kCtrlBits), 17, 0, B_CMAP8);
771 		else
772 			control.ImportBits(kAltBits, sizeof(kAltBits), 17, 0, B_CMAP8);
773 		where.x -= rect.Width() + 1;
774 		fSuper->DrawBitmap(&control, where);
775 	}
776 
777 	if (fModifiers & B_SHIFT_KEY) {
778 		BRect rect(0,0,21,10);
779 		BBitmap shift(rect, B_CMAP8);
780 		shift.ImportBits(kShiftBits, sizeof(kShiftBits), 22, 0, B_CMAP8);
781 		where.x -= rect.Width() + 1;
782 		fSuper->DrawBitmap(&shift, where);
783 	}
784 }
785 
786 
787 void
788 BMenuItem::_DrawSubmenuSymbol(rgb_color bgColor)
789 {
790 	fSuper->PushState();
791 
792 	BRect r(fBounds);
793 	float rightMargin;
794 	MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
795 	r.left = r.right - rightMargin + 3;
796 	r.right -= 1;
797 
798 	BPoint center(floorf((r.left + r.right) / 2.0),
799 		floorf((r.top + r.bottom) / 2.0));
800 
801 	float size = min_c(r.Height() - 2, r.Width());
802 	r.top = floorf(center.y - size / 2 + 0.5);
803 	r.bottom = floorf(center.y + size / 2 + 0.5);
804 	r.left = floorf(center.x - size / 2 + 0.5);
805 	r.right = floorf(center.x + size / 2 + 0.5);
806 
807 	fSuper->SetHighColor(tint_color(bgColor, kLightBGTint));
808 	fSuper->FillRoundRect(r, 2, 2);
809 
810 	BShape arrowShape;
811 	center.x += 0.5;
812 	center.y += 0.5;
813 	size *= 0.25;
814 	float hSize = size * 0.7;
815 	arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size));
816 	arrowShape.LineTo(BPoint(center.x + hSize, center.y));
817 	arrowShape.LineTo(BPoint(center.x - hSize, center.y + size));
818 
819 	fSuper->SetDrawingMode(B_OP_OVER);
820 	fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT));
821 	fSuper->SetPenSize(ceilf(size * 0.4));
822 	// NOTE: StrokeShape() offsets the shape by the current pen position,
823 	// it is not documented in the BeBook, but it is true!
824 	fSuper->MovePenTo(B_ORIGIN);
825 	fSuper->StrokeShape(&arrowShape);
826 
827 	fSuper->PopState();
828 }
829 
830 
831 void
832 BMenuItem::_DrawControlChar(char shortcut, BPoint where)
833 {
834 	// TODO: If needed, take another font for the control characters
835 	//	(or have font overlays in the app_server!)
836 	const char* symbol = " ";
837 
838 	switch (shortcut) {
839 		case B_DOWN_ARROW:
840 			symbol = "\xe2\x86\x93";
841 			break;
842 		case B_UP_ARROW:
843 			symbol = "\xe2\x86\x91";
844 			break;
845 		case B_LEFT_ARROW:
846 			symbol = "\xe2\x86\x90";
847 			break;
848 		case B_RIGHT_ARROW:
849 			symbol = "\xe2\x86\x92";
850 			break;
851 		case B_ENTER:
852 			symbol = "\xe2\x86\xb5";
853 			break;
854 	}
855 
856 	fSuper->DrawString(symbol, where);
857 }
858 
859 
860 void
861 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
862 {
863 	fTriggerIndex = index;
864 	fTrigger = trigger;
865 }
866