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