2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Stefano Ceccherini, stefano.ceccherini@gmail.com
7 * Marc Flerackers, mflerackers@androme.be
8 * Bill Hayden, haydentech@users.sourceforge.net
10 * John Scipione, jscipione@gmail.com
19 #include <ControlLook.h>
25 #include <MenuPrivate.h>
27 #include "utf8_functions.h"
30 const float kLightBGTint
31 = (B_LIGHTEN_1_TINT
+ B_LIGHTEN_1_TINT
+ B_NO_TINT
) / 3.0;
33 // map control key shortcuts to drawable Unicode characters
34 // cf. http://unicode.org/charts/PDF/U2190.pdf
35 const char* kUTF8ControlMap
[] = {
37 "\xe2\x86\xb8", /* B_HOME U+21B8 */
42 NULL
, /* B_BACKSPACE */
43 "\xe2\x86\xb9", /* B_TAB U+21B9 */
44 "\xe2\x86\xb5", /* B_ENTER, U+21B5 */
45 //"\xe2\x8f\x8e", /* B_ENTER, U+23CE it's the official one */
47 NULL
, /* B_PAGE_DOWN */
49 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
50 NULL
, NULL
, NULL
, NULL
,
51 "\xe2\x86\x90", /* B_LEFT_ARROW */
52 "\xe2\x86\x92", /* B_RIGHT_ARROW */
53 "\xe2\x86\x91", /* B_UP_ARROW */
54 "\xe2\x86\x93", /* B_DOWN_ARROW */
58 using BPrivate::MenuPrivate
;
60 BMenuItem::BMenuItem(const char* label
, BMessage
* message
, char shortcut
,
65 fLabel
= strdup(label
);
69 fShortcutChar
= shortcut
;
72 fModifiers
= modifiers
| B_COMMAND_KEY
;
78 BMenuItem::BMenuItem(BMenu
* menu
, BMessage
* message
)
86 BMenuItem::BMenuItem(BMessage
* data
)
90 if (data
->HasString("_label")) {
93 data
->FindString("_label", &string
);
98 if (data
->FindBool("_disable", &disable
) == B_OK
)
102 if (data
->FindBool("_marked", &marked
) == B_OK
)
106 if (data
->FindInt32("_user_trig", &userTrigger
) == B_OK
)
107 SetTrigger(userTrigger
);
109 if (data
->HasInt32("_shortcut")) {
110 int32 shortcut
, mods
;
112 data
->FindInt32("_shortcut", &shortcut
);
113 data
->FindInt32("_mods", &mods
);
115 SetShortcut(shortcut
, mods
);
118 if (data
->HasMessage("_msg")) {
119 BMessage
* message
= new BMessage
;
120 data
->FindMessage("_msg", message
);
125 if (data
->FindMessage("_submenu", &subMessage
) == B_OK
) {
126 BArchivable
* object
= instantiate_object(&subMessage
);
127 if (object
!= NULL
) {
128 BMenu
* menu
= dynamic_cast<BMenu
*>(object
);
137 BMenuItem::Instantiate(BMessage
* data
)
139 if (validate_instantiation(data
, "BMenuItem"))
140 return new BMenuItem(data
);
147 BMenuItem::Archive(BMessage
* data
, bool deep
) const
149 status_t status
= BArchivable::Archive(data
, deep
);
151 if (status
== B_OK
&& fLabel
)
152 status
= data
->AddString("_label", Label());
154 if (status
== B_OK
&& !IsEnabled())
155 status
= data
->AddBool("_disable", true);
157 if (status
== B_OK
&& IsMarked())
158 status
= data
->AddBool("_marked", true);
160 if (status
== B_OK
&& fUserTrigger
)
161 status
= data
->AddInt32("_user_trig", fUserTrigger
);
163 if (status
== B_OK
&& fShortcutChar
) {
164 status
= data
->AddInt32("_shortcut", fShortcutChar
);
166 status
= data
->AddInt32("_mods", fModifiers
);
169 if (status
== B_OK
&& Message() != NULL
)
170 status
= data
->AddMessage("_msg", Message());
172 if (status
== B_OK
&& deep
&& fSubmenu
) {
174 if (fSubmenu
->Archive(&submenu
, true) == B_OK
)
175 status
= data
->AddMessage("_submenu", &submenu
);
182 BMenuItem::~BMenuItem()
190 BMenuItem::SetLabel(const char* string
)
192 if (fLabel
!= NULL
) {
198 fLabel
= strdup(string
);
200 if (fSuper
!= NULL
) {
201 fSuper
->InvalidateLayout();
203 if (fSuper
->LockLooper()) {
204 fSuper
->Invalidate();
205 fSuper
->UnlockLooper();
212 BMenuItem::SetEnabled(bool enable
)
214 if (fEnabled
== enable
)
219 if (fSubmenu
!= NULL
)
220 fSubmenu
->SetEnabled(enable
);
222 BMenu
* menu
= fSuper
;
223 if (menu
!= NULL
&& menu
->LockLooper()) {
224 menu
->Invalidate(fBounds
);
225 menu
->UnlockLooper();
231 BMenuItem::SetMarked(bool mark
)
235 if (mark
&& fSuper
!= NULL
) {
236 MenuPrivate
priv(fSuper
);
237 priv
.ItemMarked(this);
243 BMenuItem::SetTrigger(char trigger
)
245 fUserTrigger
= trigger
;
247 // try uppercase letters first
249 const char* pos
= strchr(Label(), toupper(trigger
));
250 trigger
= tolower(trigger
);
253 // take lowercase, too
254 pos
= strchr(Label(), trigger
);
258 fTriggerIndex
= UTF8CountChars(Label(), pos
- Label());
266 fSuper
->InvalidateLayout();
271 BMenuItem::SetShortcut(char shortcut
, uint32 modifiers
)
273 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) != 0
274 && fWindow
!= NULL
) {
275 fWindow
->RemoveShortcut(fShortcutChar
, fModifiers
);
278 fShortcutChar
= shortcut
;
281 fModifiers
= modifiers
| B_COMMAND_KEY
;
285 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) && fWindow
)
286 fWindow
->AddShortcut(fShortcutChar
, fModifiers
, this);
288 if (fSuper
!= NULL
) {
289 fSuper
->InvalidateLayout();
291 if (fSuper
->LockLooper()) {
292 fSuper
->Invalidate();
293 fSuper
->UnlockLooper();
300 BMenuItem::Label() const
307 BMenuItem::IsEnabled() const
310 return fSubmenu
->IsEnabled();
315 return fSuper
!= NULL
? fSuper
->IsEnabled() : true;
320 BMenuItem::IsMarked() const
327 BMenuItem::Trigger() const
334 BMenuItem::Shortcut(uint32
* modifiers
) const
337 *modifiers
= fModifiers
;
339 return fShortcutChar
;
344 BMenuItem::Submenu() const
351 BMenuItem::Menu() const
358 BMenuItem::Frame() const
365 BMenuItem::GetContentSize(float* _width
, float* _height
)
367 // TODO: Get rid of this. BMenu should handle this
368 // automatically. Maybe it's not even needed, since our
369 // BFont::Height() caches the value locally
370 MenuPrivate(fSuper
).CacheFontInfo();
372 fCachedWidth
= fSuper
->StringWidth(fLabel
);
375 *_width
= (float)ceil(fCachedWidth
);
377 *_height
= MenuPrivate(fSuper
).FontHeight();
382 BMenuItem::TruncateLabel(float maxWidth
, char* newLabel
)
385 fSuper
->GetFont(&font
);
387 BString
string(fLabel
);
389 font
.TruncateString(&string
, B_TRUNCATE_MIDDLE
, maxWidth
);
391 string
.CopyInto(newLabel
, 0, string
.Length());
392 newLabel
[string
.Length()] = '\0';
397 BMenuItem::DrawContent()
399 MenuPrivate
menuPrivate(fSuper
);
400 menuPrivate
.CacheFontInfo();
402 fSuper
->MovePenBy(0, menuPrivate
.Ascent());
403 BPoint lineStart
= fSuper
->PenLocation();
405 fSuper
->SetDrawingMode(B_OP_OVER
);
409 GetContentSize(&labelWidth
, &labelHeight
);
411 const BRect
& padding
= menuPrivate
.Padding();
412 float maxContentWidth
= fSuper
->MaxContentWidth();
413 float frameWidth
= maxContentWidth
> 0 ? maxContentWidth
414 : fSuper
->Frame().Width() - padding
.left
- padding
.right
;
416 if (roundf(frameWidth
) >= roundf(labelWidth
))
417 fSuper
->DrawString(fLabel
);
419 // truncate label to fit
420 char* truncatedLabel
= new char[strlen(fLabel
) + 4];
421 TruncateLabel(frameWidth
, truncatedLabel
);
422 fSuper
->DrawString(truncatedLabel
);
423 delete[] truncatedLabel
;
426 if (fSuper
->AreTriggersEnabled() && fTriggerIndex
!= -1) {
427 float escapements
[fTriggerIndex
+ 1];
429 fSuper
->GetFont(&font
);
431 font
.GetEscapements(fLabel
, fTriggerIndex
+ 1, escapements
);
433 for (int32 i
= 0; i
< fTriggerIndex
; i
++)
434 lineStart
.x
+= escapements
[i
] * font
.Size();
439 BPoint
lineEnd(lineStart
);
440 lineEnd
.x
+= escapements
[fTriggerIndex
] * font
.Size();
442 fSuper
->StrokeLine(lineStart
, lineEnd
);
450 const color_which lowColor
= fSuper
->LowUIColor();
451 const color_which highColor
= fSuper
->HighUIColor();
453 bool enabled
= IsEnabled();
454 bool selected
= IsSelected();
455 bool activated
= selected
&& (enabled
|| Submenu() != NULL
);
459 fSuper
->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR
));
460 // fill in the background
462 be_control_look
->DrawMenuItemBackground(fSuper
, rect
, Frame(),
463 fSuper
->LowColor(), BControlLook::B_ACTIVATED
);
465 fSuper
->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR
));
468 if (activated
&& enabled
)
469 fSuper
->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR
));
471 fSuper
->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR
));
473 rgb_color bgColor
= fSuper
->LowColor();
474 if (bgColor
.red
+ bgColor
.green
+ bgColor
.blue
> 128 * 3)
475 fSuper
->SetHighColor(tint_color(bgColor
, B_DISABLED_LABEL_TINT
));
477 fSuper
->SetHighColor(tint_color(bgColor
, B_LIGHTEN_2_TINT
));
481 fSuper
->MovePenTo(ContentLocation());
484 // draw extra symbols
485 const menu_layout layout
= MenuPrivate(fSuper
).Layout();
486 if (layout
== B_ITEMS_IN_COLUMN
) {
491 _DrawShortcutSymbol();
493 if (Submenu() != NULL
)
494 _DrawSubmenuSymbol();
497 // restore the parent menu's low color and high color
498 fSuper
->SetLowUIColor(lowColor
);
499 fSuper
->SetHighUIColor(highColor
);
504 BMenuItem::Highlight(bool highlight
)
506 fSuper
->Invalidate(Frame());
511 BMenuItem::IsSelected() const
518 BMenuItem::ContentLocation() const
520 const BRect
& padding
= MenuPrivate(fSuper
).Padding();
522 return BPoint(fBounds
.left
+ padding
.left
, fBounds
.top
+ padding
.top
);
526 void BMenuItem::_ReservedMenuItem1() {}
527 void BMenuItem::_ReservedMenuItem2() {}
528 void BMenuItem::_ReservedMenuItem3() {}
529 void BMenuItem::_ReservedMenuItem4() {}
532 BMenuItem::BMenuItem(const BMenuItem
&)
538 BMenuItem::operator=(const BMenuItem
&)
545 BMenuItem::_InitData()
564 BMenuItem::_InitMenuData(BMenu
* menu
)
568 MenuPrivate(fSubmenu
).SetSuperItem(this);
570 BMenuItem
* item
= menu
->FindMarked();
572 if (menu
->IsRadioMode() && menu
->IsLabelFromMarked() && item
!= NULL
)
573 SetLabel(item
->Label());
575 SetLabel(menu
->Name());
580 BMenuItem::Install(BWindow
* window
)
582 if (fSubmenu
!= NULL
)
583 MenuPrivate(fSubmenu
).Install(window
);
587 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) && fWindow
)
588 window
->AddShortcut(fShortcutChar
, fModifiers
, this);
590 if (!Messenger().IsValid())
596 BMenuItem::Invoke(BMessage
* message
)
601 if (fSuper
->IsRadioMode())
605 uint32 kind
= InvokeKind(¬ify
);
607 BMessage
clone(kind
);
608 status_t err
= B_BAD_VALUE
;
610 if (message
== NULL
&& !notify
)
613 if (message
== NULL
) {
614 if (!fSuper
->IsWatched())
619 clone
.AddInt32("index", fSuper
->IndexOf(this));
620 clone
.AddInt64("when", (int64
)system_time());
621 clone
.AddPointer("source", this);
622 clone
.AddMessenger("be:sender", BMessenger(fSuper
));
625 err
= BInvoker::Invoke(&clone
);
627 // TODO: assynchronous messaging
628 // SendNotices(kind, &clone);
635 BMenuItem::Uninstall()
637 if (fSubmenu
!= NULL
)
638 MenuPrivate(fSubmenu
).Uninstall();
640 if (Target() == fWindow
)
641 SetTarget(BMessenger());
643 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) != 0
644 && fWindow
!= NULL
) {
645 fWindow
->RemoveShortcut(fShortcutChar
, fModifiers
);
653 BMenuItem::SetSuper(BMenu
* super
)
655 if (fSuper
!= NULL
&& super
!= NULL
) {
656 debugger("Error - can't add menu or menu item to more than 1 container"
657 " (either menu or menubar).");
660 if (fSubmenu
!= NULL
)
661 MenuPrivate(fSubmenu
).SetSuper(super
);
668 BMenuItem::Select(bool selected
)
670 if (fSelected
== selected
)
673 if (Submenu() != NULL
|| IsEnabled()) {
674 fSelected
= selected
;
681 BMenuItem::_DrawMarkSymbol()
687 MenuPrivate(fSuper
).GetItemMargins(&leftMargin
, NULL
, NULL
, NULL
);
688 float gap
= leftMargin
/ 4;
689 r
.right
= r
.left
+ leftMargin
- gap
;
692 BPoint
center(floorf((r
.left
+ r
.right
) / 2.0),
693 floorf((r
.top
+ r
.bottom
) / 2.0));
695 float size
= min_c(r
.Height() - 2, r
.Width());
696 r
.top
= floorf(center
.y
- size
/ 2 + 0.5);
697 r
.bottom
= floorf(center
.y
+ size
/ 2 + 0.5);
698 r
.left
= floorf(center
.x
- size
/ 2 + 0.5);
699 r
.right
= floorf(center
.x
+ size
/ 2 + 0.5);
705 arrowShape
.MoveTo(BPoint(center
.x
- size
, center
.y
- size
* 0.25));
706 arrowShape
.LineTo(BPoint(center
.x
- size
* 0.25, center
.y
+ size
));
707 arrowShape
.LineTo(BPoint(center
.x
+ size
, center
.y
- size
));
709 fSuper
->SetDrawingMode(B_OP_OVER
);
710 fSuper
->SetPenSize(2.0);
711 // NOTE: StrokeShape() offsets the shape by the current pen position,
712 // it is not documented in the BeBook, but it is true!
713 fSuper
->MovePenTo(B_ORIGIN
);
714 fSuper
->StrokeShape(&arrowShape
);
721 BMenuItem::_DrawShortcutSymbol()
723 BMenu
* menu
= fSuper
;
725 menu
->GetFont(&font
);
726 BPoint where
= ContentLocation();
727 where
.x
= fBounds
.right
- font
.Size();
730 where
.x
-= fBounds
.Height() - 3;
732 const float ascent
= MenuPrivate(fSuper
).Ascent();
733 if (fShortcutChar
< B_SPACE
&& kUTF8ControlMap
[(int)fShortcutChar
])
734 _DrawControlChar(fShortcutChar
, where
+ BPoint(0, ascent
));
736 fSuper
->DrawChar(fShortcutChar
, where
+ BPoint(0, ascent
));
738 where
.y
+= (fBounds
.Height() - 11) / 2 - 1;
741 // TODO: It would be nice to draw these taking into account the text (low)
743 if ((fModifiers
& B_COMMAND_KEY
) != 0) {
744 const BBitmap
* command
= MenuPrivate::MenuItemCommand();
745 const BRect
&rect
= command
->Bounds();
746 where
.x
-= rect
.Width() + 1;
747 fSuper
->DrawBitmap(command
, where
);
750 if ((fModifiers
& B_CONTROL_KEY
) != 0) {
751 const BBitmap
* control
= MenuPrivate::MenuItemControl();
752 const BRect
&rect
= control
->Bounds();
753 where
.x
-= rect
.Width() + 1;
754 fSuper
->DrawBitmap(control
, where
);
757 if ((fModifiers
& B_OPTION_KEY
) != 0) {
758 const BBitmap
* option
= MenuPrivate::MenuItemOption();
759 const BRect
&rect
= option
->Bounds();
760 where
.x
-= rect
.Width() + 1;
761 fSuper
->DrawBitmap(option
, where
);
764 if ((fModifiers
& B_SHIFT_KEY
) != 0) {
765 const BBitmap
* shift
= MenuPrivate::MenuItemShift();
766 const BRect
&rect
= shift
->Bounds();
767 where
.x
-= rect
.Width() + 1;
768 fSuper
->DrawBitmap(shift
, where
);
774 BMenuItem::_DrawSubmenuSymbol()
780 MenuPrivate(fSuper
).GetItemMargins(NULL
, NULL
, &rightMargin
, NULL
);
781 r
.left
= r
.right
- rightMargin
+ 3;
784 BPoint
center(floorf((r
.left
+ r
.right
) / 2.0),
785 floorf((r
.top
+ r
.bottom
) / 2.0));
787 float size
= min_c(r
.Height() - 2, r
.Width());
788 r
.top
= floorf(center
.y
- size
/ 2 + 0.5);
789 r
.bottom
= floorf(center
.y
+ size
/ 2 + 0.5);
790 r
.left
= floorf(center
.x
- size
/ 2 + 0.5);
791 r
.right
= floorf(center
.x
+ size
/ 2 + 0.5);
797 float hSize
= size
* 0.7;
798 arrowShape
.MoveTo(BPoint(center
.x
- hSize
, center
.y
- size
));
799 arrowShape
.LineTo(BPoint(center
.x
+ hSize
, center
.y
));
800 arrowShape
.LineTo(BPoint(center
.x
- hSize
, center
.y
+ size
));
802 fSuper
->SetDrawingMode(B_OP_OVER
);
803 fSuper
->SetPenSize(ceilf(size
* 0.4));
804 // NOTE: StrokeShape() offsets the shape by the current pen position,
805 // it is not documented in the BeBook, but it is true!
806 fSuper
->MovePenTo(B_ORIGIN
);
807 fSuper
->StrokeShape(&arrowShape
);
814 BMenuItem::_DrawControlChar(char shortcut
, BPoint where
)
816 // TODO: If needed, take another font for the control characters
817 // (or have font overlays in the app_server!)
818 const char* symbol
= " ";
819 if (kUTF8ControlMap
[(int)fShortcutChar
])
820 symbol
= kUTF8ControlMap
[(int)fShortcutChar
];
822 fSuper
->DrawString(symbol
, where
);
827 BMenuItem::SetAutomaticTrigger(int32 index
, uint32 trigger
)
829 fTriggerIndex
= index
;