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