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