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