xref: /haiku/src/kits/interface/MenuItem.cpp (revision 95bac3fda53a4cb21880712d7b43f8c21db32a2e)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2002, OpenBeOS
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		MenuItem.cpp
23 //	Author:			Marc Flerackers (mflerackers@androme.be)
24 //					Bill Hayden (haydentech@users.sourceforge.net)
25 //	Description:	Display item for BMenu class
26 //
27 //------------------------------------------------------------------------------
28 
29 #include <string.h>
30 #include <stdlib.h>
31 
32 #include <Bitmap.h>
33 #include <MenuItem.h>
34 #include <String.h>
35 #include <Window.h>
36 
37 
38 BMenuItem::BMenuItem(const char *label, BMessage *message, char shortcut,
39 					 uint32 modifiers)
40 {
41 	InitData();
42 	if (label != NULL)
43 		fLabel = strdup(label);
44 
45 	SetMessage(message);
46 
47 	fShortcutChar = shortcut;
48 
49 	if (shortcut != 0)
50 		fModifiers = modifiers | B_COMMAND_KEY;
51 	else
52 		fModifiers = 0;
53 }
54 
55 
56 BMenuItem::BMenuItem(BMenu *menu, BMessage *message)
57 {
58 	InitData();
59 	SetMessage(message);
60 	InitMenuData(menu);
61 }
62 
63 
64 BMenuItem::BMenuItem(BMessage *data)
65 {
66 	InitData();
67 
68 	if (data->HasString("_label")) {
69 		const char *string;
70 
71 		data->FindString("_label", &string);
72 		SetLabel(string);
73 	}
74 
75 	bool disable;
76 	if (data->FindBool("_disable", &disable) == B_OK)
77 		SetEnabled(!disable);
78 
79 	bool marked;
80 	if (data->FindBool("_marked", &marked) == B_OK)
81 		SetMarked(marked);
82 
83 	if (data->HasInt32("_user_trig")) {
84 		int32 user_trig;
85 
86 		data->FindInt32("_user_trig", &user_trig);
87 
88 		SetTrigger(user_trig);
89 	}
90 
91 	if (data->HasInt32("_shortcut")) {
92 		int32 shortcut, mods;
93 
94 		data->FindInt32("_shortcut", &shortcut);
95 		data->FindInt32("_mods", &mods);
96 
97 		SetShortcut(shortcut, mods);
98 	}
99 
100 	if (data->HasMessage("_msg")) {
101 		BMessage *msg = new BMessage;
102 
103 		data->FindMessage("_msg", msg);
104 		SetMessage(msg);
105 	}
106 
107 	BMessage subMessage;
108 	if (data->FindMessage("_submenu", &subMessage) == B_OK) {
109 		BArchivable *object = instantiate_object(&subMessage);
110 
111 		if (object != NULL) {
112 			BMenu *menu = dynamic_cast<BMenu *>(object);
113 
114 			if (menu != NULL)
115 				InitMenuData(menu);
116 		}
117 	}
118 }
119 
120 
121 BArchivable *
122 BMenuItem::Instantiate(BMessage *data)
123 {
124 	if (validate_instantiation(data, "BMenuItem"))
125 		return new BMenuItem(data);
126 	else
127 		return NULL;
128 }
129 
130 
131 status_t
132 BMenuItem::Archive(BMessage *data, bool deep) const
133 {
134 	if (fLabel)
135 		data->AddString("_label", Label());
136 
137 	if (!IsEnabled())
138 		data->AddBool("_disable", true);
139 
140 	if (IsMarked())
141 		data->AddBool("_marked", true);
142 
143 	if (fUserTrigger)
144 		data->AddInt32("_user_trig", fUserTrigger);
145 
146 	if (fShortcutChar) {
147 		data->AddInt32("_shortcut", fShortcutChar);
148 		data->AddInt32("_mods", fModifiers);
149 	}
150 
151 	if (Message())
152 		data->AddMessage("_msg", Message());
153 
154 	if (deep && fSubmenu) {
155 		BMessage submenu;
156 
157 		if (fSubmenu->Archive(&submenu, true) == B_OK)
158 			data->AddMessage("_submenu", &submenu);
159 	}
160 
161 	return B_OK;
162 }
163 
164 
165 BMenuItem::~BMenuItem()
166 {
167 	free(fLabel);
168 	delete fSubmenu;
169 }
170 
171 
172 void
173 BMenuItem::SetLabel(const char *string)
174 {
175 	if (fLabel != NULL) {
176 		free(fLabel);
177 		fLabel = NULL;
178 	}
179 
180 	if (string != NULL)
181 		fLabel = strdup(string);
182 
183 	if (fSuper != NULL) {
184 		fSuper->InvalidateLayout();
185 
186 		if (fSuper->LockLooper()) {
187 			fSuper->Invalidate();
188 			fSuper->UnlockLooper();
189 		}
190 	}
191 }
192 
193 
194 void
195 BMenuItem::SetEnabled(bool state)
196 {
197 	if (fSubmenu != NULL)
198 		fSubmenu->SetEnabled(state);
199 
200 	fEnabled = state;
201 
202 	BMenu *menu = Menu();
203 	if (menu != NULL && menu->LockLooper()) {
204 		menu->Invalidate(fBounds);
205 		menu->UnlockLooper();
206 	}
207 }
208 
209 
210 void
211 BMenuItem::SetMarked(bool state)
212 {
213 	fMark = state;
214 
215 	if (state && Menu() != NULL)
216 		Menu()->ItemMarked(this);
217 }
218 
219 
220 void
221 BMenuItem::SetTrigger(char ch)
222 {
223 	fUserTrigger = ch;
224 
225 	if (strchr(fLabel, ch) != 0)
226 		fSysTrigger = ch;
227 	else
228 		fSysTrigger = -1;
229 
230 	if (fSuper != NULL)
231 		fSuper->InvalidateLayout();
232 }
233 
234 
235 void
236 BMenuItem::SetShortcut(char ch, uint32 modifiers)
237 {
238 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
239 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
240 
241 	fShortcutChar = ch;
242 
243 	if (ch != 0)
244 		fModifiers = modifiers | B_COMMAND_KEY;
245 	else
246 		fModifiers = 0;
247 
248 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
249 		fWindow->AddShortcut(fShortcutChar, fModifiers, this);
250 
251 	if (fSuper) {
252 		fSuper->InvalidateLayout();
253 
254 		if (fSuper->LockLooper()) {
255 			fSuper->Invalidate();
256 			fSuper->UnlockLooper();
257 		}
258 	}
259 }
260 
261 
262 const char *
263 BMenuItem::Label() const
264 {
265 	return fLabel;
266 }
267 
268 
269 bool
270 BMenuItem::IsEnabled() const
271 {
272 	if (fSubmenu)
273 		return fSubmenu->IsEnabled();
274 
275 	if (!fEnabled)
276 		return false;
277 
278 	return fSuper ? fSuper->IsEnabled() : true;
279 }
280 
281 
282 bool
283 BMenuItem::IsMarked() const
284 {
285 	return fMark;
286 }
287 
288 
289 char
290 BMenuItem::Trigger() const
291 {
292 	return fUserTrigger;
293 }
294 
295 
296 char
297 BMenuItem::Shortcut(uint32 *modifiers) const
298 {
299 	if (modifiers)
300 		*modifiers = fModifiers;
301 
302 	return fShortcutChar;
303 }
304 
305 
306 BMenu *
307 BMenuItem::Submenu() const
308 {
309 	return fSubmenu;
310 }
311 
312 
313 BMenu *
314 BMenuItem::Menu() const
315 {
316 	return fSuper;
317 }
318 
319 
320 BRect
321 BMenuItem::Frame() const
322 {
323 	return fBounds;
324 }
325 
326 
327 void
328 BMenuItem::GetContentSize(float *width, float *height)
329 {
330 	fSuper->CacheFontInfo();
331 
332 	fCachedWidth = fSuper->StringWidth(fLabel);
333 
334 	if (width)
335 		*width = (float)ceil(fCachedWidth);
336 	if (height)
337 		*height = fSuper->fFontHeight;
338 }
339 
340 
341 void
342 BMenuItem::TruncateLabel(float maxWidth, char *newLabel)
343 {
344 	// ToDo: implement me!
345 }
346 
347 
348 void
349 BMenuItem::DrawContent()
350 {
351 	fSuper->MovePenBy(0, fSuper->fAscent);
352 	BPoint lineStart = fSuper->PenLocation();
353 
354 	fSuper->DrawString(fLabel);
355 
356 	// ToDo: label truncation is missing
357 
358 	if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) {
359 		float escapements[128]; // TODO: this doesn't look nice
360 		BFont font;
361 		fSuper->GetFont(&font);
362 		font.GetEscapements(fLabel, fTriggerIndex + 1, escapements);
363 		for (int32 i = 0; i < fTriggerIndex; i++)
364 			lineStart.x += escapements[i] * font.Size();
365 
366 		lineStart.x--;
367 		lineStart.y++;
368 
369 		BPoint lineEnd(lineStart);
370 		lineEnd.x += escapements[fTriggerIndex] * font.Size();
371 
372 		fSuper->StrokeLine(lineStart, lineEnd);
373 	}
374 }
375 
376 
377 void
378 BMenuItem::Draw()
379 {
380 	// TODO: Cleanup
381 	bool enabled = IsEnabled();
382 
383 	fSuper->CacheFontInfo();
384 
385 	if (IsSelected() && (enabled || Submenu()) /*&& fSuper->fRedrawAfterSticky*/) {
386 		fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
387 			B_DARKEN_2_TINT));
388 		fSuper->SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
389 			B_DARKEN_2_TINT));
390 		fSuper->FillRect(Frame());
391 	}
392 
393 	if (IsEnabled())
394 		fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
395 	else if (IsSelected())
396 		fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
397 			B_LIGHTEN_1_TINT));
398 	else
399 		fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
400 			B_DISABLED_LABEL_TINT));
401 
402 	fSuper->MovePenTo(ContentLocation());
403 
404 	DrawContent();
405 
406 	if (fSuper->Layout() == B_ITEMS_IN_COLUMN) {
407 		if (IsMarked())
408 			DrawMarkSymbol();
409 
410 		if (fShortcutChar)
411 			DrawShortcutSymbol();
412 
413 		if (Submenu())
414 			DrawSubmenuSymbol();
415 	}
416 
417 	fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
418 }
419 
420 
421 void
422 BMenuItem::Highlight(bool flag)
423 {
424 	Menu()->Draw(Frame());
425 }
426 
427 
428 bool
429 BMenuItem::IsSelected() const
430 {
431 	return fSelected;
432 }
433 
434 
435 BPoint
436 BMenuItem::ContentLocation() const
437 {
438 	return BPoint(fBounds.left + Menu()->fPad.left,
439 		fBounds.top + Menu()->fPad.top);
440 }
441 
442 
443 void BMenuItem::_ReservedMenuItem1() {}
444 void BMenuItem::_ReservedMenuItem2() {}
445 void BMenuItem::_ReservedMenuItem3() {}
446 void BMenuItem::_ReservedMenuItem4() {}
447 
448 
449 BMenuItem::BMenuItem(const BMenuItem &)
450 {
451 }
452 
453 
454 BMenuItem &
455 BMenuItem::operator=(const BMenuItem &)
456 {
457 	return *this;
458 }
459 
460 
461 void
462 BMenuItem::InitData()
463 {
464 	fLabel = NULL;
465 	fSubmenu = NULL;
466 	fWindow = NULL;
467 	fSuper = NULL;
468 	fModifiers = 0;
469 	fCachedWidth = 0;
470 	fTriggerIndex = -1;
471 	fUserTrigger = 0;
472 	fSysTrigger = 0;
473 	fShortcutChar = 0;
474 	fMark = false;
475 	fEnabled = true;
476 	fSelected = false;
477 }
478 
479 
480 void
481 BMenuItem::InitMenuData(BMenu *menu)
482 {
483 	fSubmenu = menu;
484 	fSubmenu->fSuperitem = this;
485 
486 	BMenuItem *item = menu->FindMarked();
487 
488 	if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
489 		SetLabel(item->Label());
490 	else
491 		SetLabel(menu->Name());
492 }
493 
494 
495 void
496 BMenuItem::Install(BWindow *window)
497 {
498 	if (fSubmenu)
499 		fSubmenu->Install(window);
500 
501 	fWindow = window;
502 
503 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
504 		window->AddShortcut(fShortcutChar, fModifiers, this);
505 
506 	if (!Messenger().IsValid())
507 		SetTarget(window);
508 }
509 
510 
511 status_t
512 BMenuItem::Invoke(BMessage *message)
513 {
514 	if (!IsEnabled())
515 		return B_ERROR;
516 
517 	if (fSuper->IsRadioMode())
518 		SetMarked(true);
519 
520 	bool notify = false;
521 	uint32 kind = InvokeKind(&notify);
522 
523 	BMessage clone(kind);
524 	status_t err = B_BAD_VALUE;
525 
526 	if (!message && !notify)
527 		message = Message();
528 
529 	if (!message) {
530 		if (!fSuper->IsWatched())
531 			return err;
532 	} else
533 		clone = *message;
534 
535 	clone.AddInt32("index", Menu()->IndexOf(this));
536 	clone.AddInt64("when", (int64)system_time());
537 	clone.AddPointer("source", this);
538 	clone.AddMessenger("be:sender", BMessenger(fSuper));
539 
540 	if (message)
541 		err = BInvoker::Invoke(&clone);
542 
543 //	TODO: assynchronous messaging
544 //	SendNotices(kind, &clone);
545 
546 	return err;
547 }
548 
549 
550 void
551 BMenuItem::Uninstall()
552 {
553 	if (fSubmenu != NULL)
554 		fSubmenu->Uninstall();
555 
556 	if (Target() == fWindow)
557 		SetTarget(BMessenger());
558 
559 	// TODO: I'm not sure about B_COMMAND_KEY
560 	if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow != NULL)
561 		fWindow->RemoveShortcut(fShortcutChar, fModifiers);
562 
563 	fWindow = NULL;
564 }
565 
566 
567 void
568 BMenuItem::SetSuper(BMenu *super)
569 {
570 	if (fSuper != NULL && super != NULL)
571 		debugger("Error - can't add menu or menu item to more than 1 container (either menu or menubar).");
572 
573 	fSuper = super;
574 
575 	if (fSubmenu != NULL)
576 		fSubmenu->fSuper = super;
577 }
578 
579 
580 void
581 BMenuItem::Select(bool on)
582 {
583 	if (Submenu()) {
584 		fSelected = on;
585 		Highlight(on);
586 	} else if (IsEnabled()) {
587 		fSelected = on;
588 		Highlight(on);
589 	}
590 
591 }
592 
593 
594 void
595 BMenuItem::DrawMarkSymbol()
596 {
597 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
598 		B_DARKEN_1_TINT));
599 	fSuper->StrokeLine(BPoint(fBounds.left + 6.0f, fBounds.bottom - 3.0f),
600 		BPoint(fBounds.left + 10.0f, fBounds.bottom - 12.0f));
601 	fSuper->StrokeLine(BPoint(fBounds.left + 7.0f, fBounds.bottom - 3.0f),
602 		BPoint(fBounds.left + 11.0f, fBounds.bottom - 12.0f));
603 
604 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
605 		B_DARKEN_4_TINT));
606 
607 	fSuper->StrokeLine(BPoint(fBounds.left + 6.0f, fBounds.bottom - 4.0f),
608 		BPoint(fBounds.left + 10.0f, fBounds.bottom - 13.0f));
609 	fSuper->StrokeLine(BPoint(fBounds.left + 5.0f, fBounds.bottom - 4.0f),
610 		BPoint(fBounds.left + 9.0f, fBounds.bottom - 13.0f));
611 	fSuper->StrokeLine(BPoint(fBounds.left + 5.0f, fBounds.bottom - 3.0f),
612 		BPoint(fBounds.left + 3.0f, fBounds.bottom - 9.0f));
613 	fSuper->StrokeLine(BPoint(fBounds.left + 4.0f, fBounds.bottom - 4.0f),
614 		BPoint(fBounds.left + 2.0f, fBounds.bottom - 9.0f));
615 }
616 
617 
618 void
619 BMenuItem::DrawShortcutSymbol()
620 {
621 	BString shortcut("");
622 
623 	if (fModifiers & B_CONTROL_KEY)
624 		shortcut += "ctl+";
625 
626 	shortcut += fShortcutChar;
627 
628 	fSuper->DrawString(shortcut.String(), ContentLocation() +
629 		BPoint(fBounds.Width() - 14.0f - 32.0f, fBounds.Height() - 4.0f));
630 }
631 
632 
633 void
634 BMenuItem::DrawSubmenuSymbol()
635 {
636 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
637 		B_LIGHTEN_MAX_TINT));
638 	fSuper->FillTriangle(BPoint(fBounds.right - 14.0f, fBounds.bottom - 4.0f),
639 		BPoint(fBounds.right - 14.0f, fBounds.bottom - 12.0f),
640 		BPoint(fBounds.right - 5.0f, fBounds.bottom - 8.0f));
641 
642 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
643 		B_DARKEN_2_TINT));
644 	fSuper->StrokeLine(BPoint(fBounds.right - 14.0f, fBounds.bottom - 5),
645 		BPoint(fBounds.right - 9.0f, fBounds.bottom - 7));
646 	fSuper->StrokeLine(BPoint(fBounds.right - 7.0f, fBounds.bottom - 8),
647 		BPoint(fBounds.right - 7.0f, fBounds.bottom - 8));
648 
649 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
650 		B_DARKEN_3_TINT));
651 	fSuper->StrokeTriangle(BPoint(fBounds.right - 14.0f, fBounds.bottom - 4.0f),
652 		BPoint(fBounds.right - 14.0f, fBounds.bottom - 12.0f),
653 		BPoint(fBounds.right - 5.0f, fBounds.bottom - 8.0f));
654 
655 	fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
656 		B_LIGHTEN_1_TINT));
657 	fSuper->StrokeTriangle(BPoint(fBounds.right - 12.0f, fBounds.bottom - 7.0f),
658 		BPoint(fBounds.right - 12.0f, fBounds.bottom - 9.0f),
659 		BPoint(fBounds.right - 9.0f, fBounds.bottom - 8.0f));
660 	fSuper->FillTriangle(BPoint(fBounds.right - 12.0f, fBounds.bottom - 7.0f),
661 		BPoint(fBounds.right - 12.0f, fBounds.bottom - 9.0f),
662 		BPoint(fBounds.right - 9.0f, fBounds.bottom - 8.0f));
663 }
664 
665 
666 void
667 BMenuItem::DrawControlChar(const char *control)
668 {
669 }
670 
671 
672 void
673 BMenuItem::SetSysTrigger(char ch)
674 {
675 	fSysTrigger = ch;
676 }
677