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